Hi,
We want to use the ADC Repeat Sequence Conversion feature. However, when implement this, we encounter serval issues:
We recognize that in MSPM0L chip, total conversion register is 4 (MEMRES0:3)
Does it mean that in non-FIFO mode, the max configurable channel is 4 and must use MEMCTL0:3 to configure them, then get results from MEMRES0:3 ?
Later, if we use FIFO with DMA, we see that in 9.2.12.2 DMA and FIFO Operation: ADC-DMA/CPU Operation in FIFO Mode (FIFOEN=1),
"SAMPCNT must be programmed by SW to a suitable value based on threshold setting for DMA operation" What is the "suitable" value?
Do you have equation to calculate it if total channel number is N, we want M samples per channel (does not count hardware averaging oversample here, just assume MEMRESx will be actually written M times per channel) and either MEMRESIFG1 or MEMRESIFG3 is selected. What are the function parameters should be use for DL_ADC12_setDMASamplesCnt() and DL_DMA_setTransferSize() ?
It seems that if I configure the MEMCTL0:7 (total 8 channels), and set DMA trigger to MEMRESIFG1, sample count to 1, transfer size to M*N/2, the ADC raised OVERFLOW interrupt.
If I set DMA trigger to MEMRESIFG3, sample count to 2, when channel count is not 4, results are incorrect. (root of cause: DMA always fill ch0, ch1, ch2, ch3, ch0, ch1, ... and cycle)
I attached our code in this context.
Also, some typos and/or unclear things in the current reference manual slau847c:
- Multiple occurrence of "ASC", e.g, ASCRES register, ASCDONE / ASCVRSEL / ASCACT / ... bitfield(s). Are they actually mean "ADC"?
- Multiple register that can configure the reference buffer: ADC0->REFCFG and VREF->CTL0
- follow-up question: Sometime, the ADC0->STATUS.REFBUFRDY flag doesn't work when polling it
- If we are cycling channels including the mix of using VDDA and Internal Reference, will CONVCLK automatically switch to satisfy the 4MHz max clock if internal ref is used?
- Is it controlled by ASCVRSEL bits globally , or channel specific MEMCTL[y].VRSEL bits?
Bests,
static bool gCheckADC; static uint32_t gADCError; __attribute__((aligned(4))) static uint16_t gADCSamples[256]; /* ADC12_0 Initialization */ static const DL_ADC12_ClockConfig gADC12_0ClockConfig = { .clockSel = DL_ADC12_CLOCK_SYSOSC, .divideRatio = DL_ADC12_CLOCK_DIVIDE_8, //Select suitable clock divide ratio .freqRange = DL_ADC12_CLOCK_FREQ_RANGE_24_TO_32, }; uint32_t Driver_ADCInitAndSampling(uint32_t adcChannel, uint32_t vrefSource, uint8_t added_bits) { uint32_t u32AdcSum = 0; uint16_t adc_samples = 0; DL_ADC12_reset(ADC0); DL_ADC12_enablePower(ADC0); while (!DL_ADC12_isPowerEnabled(ADC0)) { delay_cycles(5 * 32); } // DL_ADC12_disableConversions(ADC0); DL_ADC12_setClockConfig(ADC0, (DL_ADC12_ClockConfig *) &gADC12_0ClockConfig); DL_ADC12_configConversionMem( ADC0, DL_ADC12_MEM_IDX_0, adcChannel, vrefSource, DL_ADC12_SAMPLE_TIMER_SOURCE_SCOMP0, added_bits ? DL_ADC12_AVERAGING_MODE_ENABLED : DL_ADC12_AVERAGING_MODE_DISABLED, DL_ADC12_BURN_OUT_SOURCE_DISABLED, DL_ADC12_TRIGGER_MODE_AUTO_NEXT, DL_ADC12_WINDOWS_COMP_MODE_DISABLED); if (added_bits) { uint32_t hw_avg_numerator; uint32_t hw_avg_denominator; if (added_bits > 3) { hw_avg_numerator = ADC12_CTL1_AVGN_AVG_128; hw_avg_denominator = ADC12_CTL1_AVGD_SHIFT3; added_bits -= 3; adc_samples = 1 << ((uint16_t)added_bits * 2 - 1); for (uint16_t i = 0; i < adc_samples; i++) { gADCSamples[i] = 0; } } else { hw_avg_numerator = ((uint32_t)added_bits << (1 + ADC12_CTL1_AVGN_OFS)) & ADC12_CTL1_AVGN_MASK; hw_avg_denominator = ((uint32_t)added_bits << ADC12_CTL1_AVGD_OFS) & ADC12_CTL1_AVGD_MASK; } DL_ADC12_configHwAverage(ADC0, hw_avg_numerator, hw_avg_denominator); } DL_ADC12_setPowerDownMode(ADC0, DL_ADC12_POWER_DOWN_MODE_MANUAL); DL_ADC12_setSampleTime0(ADC0, ADC_SAMPLE_TIME); DL_Common_updateReg(&ADC0->ULLMEM.REFCFG, ADC12_REFCFG_REFEN_ENABLE | ADC12_REFCFG_REFVSEL_V1P4, ADC12_REFCFG_REFEN_MASK | ADC12_REFCFG_REFVSEL_MASK | ADC12_REFCFG_IBPROG_MASK); DL_ADC12_initSingleSample( ADC0, adc_samples ? DL_ADC12_REPEAT_MODE_ENABLED : DL_ADC12_REPEAT_MODE_DISABLED, DL_ADC12_SAMPLING_SOURCE_AUTO, DL_ADC12_TRIG_SRC_SOFTWARE, DL_ADC12_SAMP_CONV_RES_12_BIT, DL_ADC12_SAMP_CONV_DATA_FORMAT_UNSIGNED); if (adc_samples) { DL_ADC12_enableFIFO(ADC0); DL_ADC12_setDMASamplesCnt(ADC0, 1); //single channel channel repeat, 1 32-bit sample per transfer DL_ADC12_enableDMATrigger(ADC0, DL_ADC12_DMA_MEM1_RESULT_LOADED); /* Enable ADC12 interrupt */ DL_ADC12_clearInterruptStatus(ADC0, (DL_ADC12_INTERRUPT_DMA_DONE | DL_ADC12_INTERRUPT_UNDERFLOW)); DL_ADC12_enableInterrupt(ADC0, (DL_ADC12_INTERRUPT_DMA_DONE | DL_ADC12_INTERRUPT_UNDERFLOW)); DL_DMA_disableChannel(DMA, DMA_CH0_SEL); DL_DMA_initChannel(DMA, DMA_CH0_SEL, (DL_DMA_Config *) &gDMA_CH0Config); /* Configure DMA source, destination and size */ DL_DMA_setSrcAddr(DMA, DMA_CH0_SEL, (uint32_t) DL_ADC12_getFIFOAddress(ADC0)); DL_DMA_setDestAddr(DMA, DMA_CH0_SEL, (uint32_t)gADCSamples); /* When FIFO is enabled 2 samples are compacted in a single word */ DL_DMA_setTransferSize(DMA, DMA_CH0_SEL, adc_samples / 2); DL_DMA_enableChannel(DMA, DMA_CH0_SEL); DL_ADC12_enableDMA(ADC0); } else { /* Enable ADC12 interrupt */ DL_ADC12_clearInterruptStatus(ADC0, DL_ADC12_INTERRUPT_MEM0_RESULT_LOADED); DL_ADC12_enableInterrupt(ADC0, DL_ADC12_INTERRUPT_MEM0_RESULT_LOADED); } gCheckADC = false; gADCError = 0; DL_ADC12_enableConversions(ADC0); /* Setup interrupts on device */ NVIC_EnableIRQ(ADC0_INT_IRQn); DL_ADC12_startConversion(ADC0); while (DL_ADC12_REFERENCE_VOLTAGE_INTREF == vrefSource && DL_ADC12_STATUS_REFERENCE_READY != (DL_ADC12_getStatus(ADC0) & DL_ADC12_STATUS_REFERENCE_READY)) { delay_cycles(5 * 32); } // if (adc_samples) { // while (DL_ADC12_STATUS_CONVERSION_ACTIVE != // (DL_ADC12_getStatus(ADC0) & DL_ADC12_STATUS_CONVERSION_ACTIVE)) { // delay_cycles(1); // } // DL_ADC12_stopConversion(ADC0); // } uint16_t remain_last = DL_DMA_getTransferSize(DMA, DMA_CH0_SEL); uint16_t timeout = 20; do { __WFE(); if (adc_samples) { uint16_t remain = DL_DMA_getTransferSize(DMA, DMA_CH0_SEL); if (0 == remain) { break; } if (remain_last == remain) { timeout--; } remain_last = remain; } else { delay_cycles(1); } } while (!(gCheckADC || gADCError || 0 == timeout)); NVIC_DisableIRQ(ADC0_INT_IRQn); if (0 == timeout) { printf("ADC timeout"); } if (gADCError) { printf("interrupt_flag: %08x", gADCError); return 0; } if (adc_samples) { uint16_t u16Counter; for (u16Counter = 0; u16Counter < adc_samples; u16Counter++) { u32AdcSum += gADCSamples[u16Counter]; printf("gADCSamples[%u]=0x%04x", u16Counter, gADCSamples[u16Counter]); } u32AdcSum >>= added_bits; } else { u32AdcSum = DL_ADC12_getMemResult(ADC0, DL_ADC12_MEM_IDX_0); } DL_ADC12_disablePower(ADC0); return u32AdcSum; } void Driver_ADCInitAndSamplingMulti(uint8_t total_channel, const uint32_t *adcChannel, const uint32_t *vrefSource, uint16_t added_bits, uint32_t *results) { const uint8_t adc_multi_ch_start_ch = 0; uint8_t adjust_total_channel = total_channel; uint32_t u32AdcSum = 0; uint16_t adc_samples = 0; DL_ADC12_reset(ADC0); DL_ADC12_enablePower(ADC0); while (!DL_ADC12_isPowerEnabled(ADC0)) { delay_cycles(5 * 32); } // DL_ADC12_disableConversions(ADC0); DL_ADC12_setClockConfig(ADC0, (DL_ADC12_ClockConfig *) &gADC12_0ClockConfig); uint8_t ch = 0; while (ch < total_channel) { uint32_t stime = DL_ADC12_REFERENCE_VOLTAGE_VDDA == vrefSource[ch] ? DL_ADC12_SAMPLE_TIMER_SOURCE_SCOMP0 : DL_ADC12_SAMPLE_TIMER_SOURCE_SCOMP1; DL_ADC12_configConversionMem( ADC0, DL_ADC12_MEM_IDX_0 + adc_multi_ch_start_ch + ch, adcChannel[ch], vrefSource[ch], stime, added_bits ? DL_ADC12_AVERAGING_MODE_ENABLED : DL_ADC12_AVERAGING_MODE_DISABLED, DL_ADC12_BURN_OUT_SOURCE_DISABLED, DL_ADC12_TRIGGER_MODE_AUTO_NEXT, DL_ADC12_WINDOWS_COMP_MODE_DISABLED); ch++; } if (added_bits) { uint32_t hw_avg_numerator; uint32_t hw_avg_denominator; if (added_bits > 3) { hw_avg_numerator = ADC12_CTL1_AVGN_AVG_128; hw_avg_denominator = ADC12_CTL1_AVGD_SHIFT3; added_bits -= 3; adc_samples = 1 << (added_bits * 2 - 1); } else { hw_avg_numerator = (added_bits << (1 + ADC12_CTL1_AVGN_OFS)) & ADC12_CTL1_AVGN_MASK; hw_avg_denominator = (added_bits << ADC12_CTL1_AVGD_OFS) & ADC12_CTL1_AVGD_MASK; added_bits = 0; } printf("added_bits: %d, hw_avg_numerator: 0x%08x, hw_avg_denominator: 0x%08x", added_bits, hw_avg_numerator, hw_avg_denominator); DL_ADC12_configHwAverage(ADC0, hw_avg_numerator, hw_avg_denominator); } DL_ADC12_setPowerDownMode(ADC0, DL_ADC12_POWER_DOWN_MODE_MANUAL); DL_ADC12_setSampleTime0(ADC0, ADC_SAMPLE_TIME); DL_ADC12_setSampleTime1(ADC0, ADC_SAMPLE_TIME); DL_Common_updateReg(&ADC0->ULLMEM.REFCFG, ADC12_REFCFG_REFEN_ENABLE | ADC12_REFCFG_REFVSEL_V1P4, ADC12_REFCFG_REFEN_MASK | ADC12_REFCFG_REFVSEL_MASK | ADC12_REFCFG_IBPROG_MASK); if (added_bits || total_channel > 4) { if (total_channel & 1) { printf("Adjust channel, >4 and odd"); //When using DMA, the total channel should not be odd number, use duplicate last channel adjust_total_channel = total_channel + 1; ch = total_channel - 1; uint32_t stime = DL_ADC12_REFERENCE_VOLTAGE_VDDA == vrefSource[ch] ? DL_ADC12_SAMPLE_TIMER_SOURCE_SCOMP0 : DL_ADC12_SAMPLE_TIMER_SOURCE_SCOMP1; DL_ADC12_configConversionMem( ADC0, DL_ADC12_MEM_IDX_0 + adc_multi_ch_start_ch + total_channel, adcChannel[ch], vrefSource[ch], stime, added_bits ? DL_ADC12_AVERAGING_MODE_ENABLED : DL_ADC12_AVERAGING_MODE_DISABLED, DL_ADC12_BURN_OUT_SOURCE_DISABLED, DL_ADC12_TRIGGER_MODE_AUTO_NEXT, DL_ADC12_WINDOWS_COMP_MODE_DISABLED); } if (0 == added_bits) { adc_samples = 1; } for (uint16_t i = 0; i < adc_samples * adjust_total_channel; i++) { gADCSamples[i] = 0; } DL_ADC12_enableFIFO(ADC0); DL_ADC12_setDMASamplesCnt(ADC0, 2); //DL_ADC12_DMA_MEM1_RESULT_LOADED, 1 32-bit sample per transfer DL_ADC12_enableDMATrigger(ADC0, DL_ADC12_DMA_MEM3_RESULT_LOADED); /* Enable ADC12 interrupt */ DL_ADC12_clearInterruptStatus(ADC0, (DL_ADC12_INTERRUPT_DMA_DONE | DL_ADC12_INTERRUPT_OVERFLOW | DL_ADC12_INTERRUPT_TRIG_OVF | DL_ADC12_INTERRUPT_UNDERFLOW)); DL_ADC12_enableInterrupt(ADC0, (DL_ADC12_INTERRUPT_DMA_DONE | DL_ADC12_INTERRUPT_OVERFLOW | DL_ADC12_INTERRUPT_TRIG_OVF | DL_ADC12_INTERRUPT_UNDERFLOW)); DL_DMA_disableChannel(DMA, DMA_CH0_SEL); DL_DMA_initChannel(DMA, DMA_CH0_SEL, (DL_DMA_Config *) &gDMA_CH0Config); /* Configure DMA source, destination and size */ DL_DMA_setSrcAddr(DMA, DMA_CH0_SEL, (uint32_t) DL_ADC12_getFIFOAddress(ADC0)); DL_DMA_setDestAddr(DMA, DMA_CH0_SEL, (uint32_t)gADCSamples); /* When FIFO is enabled 2 samples are compacted in a single word, adc_samples has already considered this */ DL_DMA_setTransferSize(DMA, DMA_CH0_SEL, adc_samples * adjust_total_channel / 2); DL_DMA_enableChannel(DMA, DMA_CH0_SEL); DL_ADC12_enableDMA(ADC0); } else { /* Enable ADC12 interrupt */ uint32_t int_mask = 1 << ((ADC12_INT_EVENT0_IMASK_MEMRESIFG0_OFS + adc_multi_ch_start_ch) + adjust_total_channel - 1); DL_ADC12_clearInterruptStatus(ADC0, int_mask); DL_ADC12_enableInterrupt(ADC0, int_mask); } DL_ADC12_initSeqSample( ADC0, added_bits ? ADC12_CTL1_CONSEQ_REPEATSEQUENCE : ADC12_CTL1_CONSEQ_SEQUENCE, DL_ADC12_SAMPLING_SOURCE_AUTO, DL_ADC12_TRIG_SRC_SOFTWARE, ((DL_ADC12_MEM_IDX_0 + adc_multi_ch_start_ch) << ADC12_CTL2_STARTADD_OFS) & ADC12_CTL2_STARTADD_MASK, ((DL_ADC12_MEM_IDX_0 + adc_multi_ch_start_ch + adjust_total_channel) << ADC12_CTL2_ENDADD_OFS) & ADC12_CTL2_ENDADD_MASK, DL_ADC12_SAMP_CONV_RES_12_BIT, DL_ADC12_SAMP_CONV_DATA_FORMAT_UNSIGNED); gCheckADC = false; gADCError = 0; DL_ADC12_enableConversions(ADC0); /* Setup interrupts on device */ NVIC_EnableIRQ(ADC0_INT_IRQn); DL_ADC12_startConversion(ADC0); while (DL_ADC12_STATUS_REFERENCE_READY != (DL_ADC12_getStatus(ADC0) & DL_ADC12_STATUS_REFERENCE_READY)) { delay_cycles(5 * 32); printf("wait ref ready"); } // if (adc_samples) { // while (DL_ADC12_STATUS_CONVERSION_ACTIVE != // (DL_ADC12_getStatus(ADC0) & DL_ADC12_STATUS_CONVERSION_ACTIVE)) { // delay_cycles(1); // } // DL_ADC12_stopConversion(ADC0); // } uint16_t remain_last = 0; uint16_t timeout = 10 * adjust_total_channel; do { printf("wait ADC event"); __WFE(); if (adc_samples) { uint16_t remain = DL_DMA_getTransferSize(DMA, DMA_CH0_SEL); if (0 == remain) { break; } if (remain_last == remain) { printf("ADC timeout: %u, remain %u", timeout, remain); timeout--; } remain_last = remain; } else { delay_cycles(50); timeout--; } } while (!(gCheckADC || gADCError || 0 == timeout)); NVIC_DisableIRQ(ADC0_INT_IRQn); if (0 == timeout) { printf("ADC timeout"); } if (gADCError) { printf("interrupt_flag: %08x", gADCError); return; } if (adc_samples) { printf("u16_arr"); for (uint16_t i = 0; i < adc_samples * adjust_total_channel; i++) { printf("gADCSamples[%u]=0x%04x", i, gADCSamples[i]); } } printf("channel-specific"); for (ch = 0; ch < total_channel; ch++) { if (adc_samples) { uint16_t u16Counter; u32AdcSum = 0; for (u16Counter = 0; u16Counter < adc_samples; u16Counter++) { uint16_t idx = u16Counter * adjust_total_channel + ch; printf("ch=%u, cnt=%u, gADCSamples[%u]=0x%04x", ch, u16Counter, idx, gADCSamples[idx]); u32AdcSum += (uint32_t)gADCSamples[idx]; } results[ch] = u32AdcSum >> added_bits; } else { results[ch] = DL_ADC12_getMemResult(ADC0, DL_ADC12_MEM_IDX_0 + adc_multi_ch_start_ch + ch); } } DL_ADC12_disablePower(ADC0); } uint8_t max_ch = 4; //DMA limitation: channel must be even number uint8_t extra_bits = 0; static void dump_adcs(void) { struct { const char *desc; uint32_t channel; uint32_t vrefSource; } const adc_meas [] = { // {"FIXED_LENGTH_TO______END", ADC_CHANNEL, ADC_REF_SOURCE} {"M0 core Temp", DL_ADC12_INPUT_CHAN_11, DL_ADC12_REFERENCE_VOLTAGE_INTREF}, {"OPA0 int_out", DL_ADC12_INPUT_CHAN_12, DL_ADC12_REFERENCE_VOLTAGE_VDDA}, {"OPA1 int_out", DL_ADC12_INPUT_CHAN_13, DL_ADC12_REFERENCE_VOLTAGE_VDDA}, {"1/3 VDDA", DL_ADC12_INPUT_CHAN_15, DL_ADC12_REFERENCE_VOLTAGE_INTREF}, {"Ext NTC temp", MEASURE_TEMPERATURE_NTC_ADC_CH, DL_ADC12_REFERENCE_VOLTAGE_VDDA}, #if defined(FRANKEN_SYSTEM) || defined(PROTO1_BOARD) {"GPAMP int_out", MEASURE_VCELL_GAMP_BUF_ADC_CH, DL_ADC12_REFERENCE_VOLTAGE_INTREF}, {"VCELL direct", MEASURE_VCELL_ADC_CH, DL_ADC12_REFERENCE_VOLTAGE_INTREF}, #else {"OPA0 P0 in", DL_ADC12_INPUT_CHAN_2, DL_ADC12_REFERENCE_VOLTAGE_VDDA}, {"OPA0 N0 in", DL_ADC12_INPUT_CHAN_3, DL_ADC12_REFERENCE_VOLTAGE_VDDA}, #endif }; #if defined(GPIO_REF_PORT) && defined(GPIO_REF_PIN) DL_GPIO_setPins(GPIO_REF_PORT, GPIO_REF_PIN); delay_cycles(300); #endif printf("\nChannel name \t\tVoltage\t\tRaw sum\t\tReference"); for (uint8_t i = 0; i < _itemsof(adc_meas); i++) { uint32_t sum = 0; sum = Driver_ADCInitAndSampling(adc_meas[i].channel, adc_meas[i].vrefSource, ADC_OVERSAMPLE_EXTRA_BITS_DEFAULT); printf("%s \t\t%4dmV\t\t%9d \t%s", adc_meas[i].desc, Driver_ADC_val_to_mV(sum, ADC_OVERSAMPLE_EXTRA_BITS_DEFAULT, adc_meas[i].vrefSource), sum, DL_ADC12_REFERENCE_VOLTAGE_VDDA == adc_meas[i].vrefSource ? "VDDA, nominal 1.8V, see '1/3 VDDA' actual value" : "INTREF, nominal 1.4V"); } printf(">>multi-channel<<"); uint32_t adc_raw[_itemsof(adc_meas)] = {0}; uint32_t adc_channel[_itemsof(adc_meas)] = {0}; uint32_t adc_ref[_itemsof(adc_meas)] = {0}; if (max_ch > _itemsof(adc_meas) || 0 == max_ch) { max_ch = _itemsof(adc_meas); } if (extra_bits > ADC_OVERSAMPLE_EXTRA_BITS_MAX) { extra_bits = ADC_OVERSAMPLE_EXTRA_BITS_MAX; } for (uint8_t ch = 0; ch < max_ch; ch++) { adc_channel[ch] = adc_meas[ch].channel; adc_ref[ch] = adc_meas[ch].vrefSource; } Driver_ADCInitAndSamplingMulti(max_ch, adc_channel, adc_ref, extra_bits, adc_raw); for (uint8_t ch = 0; ch < max_ch; ch++) { printf("%s \t\t%4dmV\t\t%9d \t%s", adc_meas[ch].desc, Driver_ADC_val_to_mV( adc_raw[ch], extra_bits, adc_meas[ch].vrefSource), adc_raw[ch], DL_ADC12_REFERENCE_VOLTAGE_VDDA == adc_meas[ch].vrefSource ? "VDDA, nominal 1.8V, see '1/3 VDDA' actual value" : "INTREF, nominal 1.4V"); } #if defined(GPIO_REF_PORT) && defined(GPIO_REF_PIN) DL_GPIO_clearPins(GPIO_REF_PORT, GPIO_REF_PIN); #endif }