What is the best method of taking multiple measurements using ADC+DMA interrupts and averaging them? Currently I have an STM32F303 with ADC2 initialized with channels 3 and 18 (Vrefint). My aim is to take 16 measurements and then average the result. I need to take these measurements with a relatively low frequency and for this test I've set up main to trigger the ADC DMA conversion every 250ms. The problems I have are:
- What is the best method of sharing data between the interrupt handler and the main loop
- How to ensure interrupt is not triggered after 16 times
Part of my code is below. The ADC/DMA callback function is supposed to trigger the next conversion 16 times and then signal to the main loop via a flag that the conversion is complete. I can see the callback being called and the ADC conversion performed; measurements are correct.
// ADC Data structure, also accessed by ADC callback
typedef struct ADC_Data {
volatile uint32_t adc_value_channel_3;
volatile uint32_t adc_value_vrefint_channel;
volatile bool adc_conversion_complete;
uint16_t adc_value_vrefint_register;
}
ADC_Data;
int main(void) {
// Initialize peripherals
HAL_Init();
SystemClock_Config();
MX_DMA_Init();
MX_ADC2_Init();
MX_OPAMP2_Init();
MX_GPIO_Init();
MX_USART2_UART_Init();
HAL_OPAMP_Start( & hopamp2);
adc_data.adc_conversion_complete = false;
adc_data.adc_value_channel_3 = 0;
adc_data.adc_value_vrefint_channel = 0;
adc_data.adc_value_vrefint_register = * VREFINT_CAL_ADDR;
HAL_ADC_Start_DMA( & hadc2, (uint32_t * ) adc_buffer, 2);
while (1) {
HAL_Delay(250);
if (adc_data.adc_conversion_complete == true) {
double adc_value = get_adc_value(&adc_data);
HAL_ADC_Start_IT(&hadc2);
}
}
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef * hadc) {
adc_data.adc_conversion_complete = false;
static uint32_t conv_count = 0;
static uint32_t temp_adc_value = 0;
static uint32_t temp_vrefint_value = 0;
temp_adc_value += adc_buffer[0];
temp_vrefint_value += adc_buffer[1];
if (++conv_count == 16) {
conv_count = 0;
adc_data.adc_value_channel_3 = temp_adc_value >> 4;
adc_data.adc_value_vrefint_channel = temp_vrefint_value >> 4;
adc_data.adc_conversion_complete = true;
temp_adc_value = 0;
temp_vrefint_value = 0;
} else {
HAL_ADC_Start_IT(hadc);
}
}
double get_adc_value(ADC_Data * data) {
return (3.3 * data - > adc_value_vrefint_register * data - > adc_value_channel_3) / (data - > adc_value_vrefint_channel * 4095);
}
***********************************EDIT*******************************
I've modified the ADC initialization to allow continuous conversion (for 3 channels) and trigger an interrupt at the end of sequence conversion: static void MX_ADC2_Init(void) { ADC_ChannelConfTypeDef sConfig = {0};
hadc2.Instance = ADC2;
hadc2.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
hadc2.Init.Resolution = ADC_RESOLUTION_12B;
hadc2.Init.ScanConvMode = ADC_SCAN_ENABLE;
hadc2.Init.ContinuousConvMode = ENABLE;
hadc2.Init.DiscontinuousConvMode = DISABLE;
hadc2.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc2.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc2.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc2.Init.NbrOfConversion = 3;
hadc2.Init.DMAContinuousRequests = DISABLE;
hadc2.Init.EOCSelection = ADC_EOC_SEQ_CONV;
hadc2.Init.LowPowerAutoWait = DISABLE;
hadc2.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;
if (HAL_ADC_Init(&hadc2) != HAL_OK)
{
Error_Handler();
}
sConfig.Channel = ADC_CHANNEL_3;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SingleDiff = ADC_SINGLE_ENDED;
sConfig.SamplingTime = ADC_SAMPLETIME_181CYCLES_5;
sConfig.OffsetNumber = ADC_OFFSET_NONE;
sConfig.Offset = 0;
if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
{
Error_Handler();
}
sConfig.Channel = ADC_CHANNEL_VREFINT;
sConfig.Rank = ADC_REGULAR_RANK_2;
sConfig.SamplingTime = ADC_SAMPLETIME_181CYCLES_5;
sConfig.OffsetNumber = ADC_OFFSET_NONE;
sConfig.Offset = 0;
if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
{
Error_Handler();
}
sConfig.Channel = ADC_CHANNEL_4;
sConfig.Rank = ADC_REGULAR_RANK_3;
sConfig.SamplingTime = ADC_SAMPLETIME_181CYCLES_5;
sConfig.OffsetNumber = ADC_OFFSET_NONE;
sConfig.Offset = 0;
if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
{
Error_Handler();
}
// Calibration
ADC2->CR &= ~ADC_CR_ADEN; // Disable ADC
ADC2->CR |= ADC_CR_ADCAL; // Start calibration
while ( (ADC2->CR & ADC_CR_ADCAL) != 0);
}
The conversion is started with HAL_ADC_Start_DMA(&hadc2, (uint32_t*)adc_buffer, 48);
. The interrupt callback is called when the buffer is filled with 48 samples (3 samples from each channel), and it sets a flag, which is then polled and reset from another function:
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
adc_data.adc_conversion_complete = true;
}
Then in the polling function I loop through the filled buffer and average its contents for each of the three channels:
adc_data.adc_value_channel_3 = 0;
adc_data.adc_vrefint_data = 0;
adc_data.adc_value_channel4 = 0;
uint32_t *tempbuf = adc_buffer;
for (int x = 0; x < 16; x++) {
adc_data.adc_value_channel_3 += *tempbuf++;
adc_data.adc_vrefint_data += *tempbuf++;
adc_data.adc_value_channel4+= *tempbuf++;
}
// Averaging by shifting each value to the right by 4 places...