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
}