Channel results of ADC in scan mode with DMA sometimes switch places. (STM32F103)
I want to calculate the resistance/power of an AC heating element by measuring voltage (via a voltage divider) and current (using an ACS71240 current sensor). The hardware setup is fine. I get the expected values. I checked this when using Arduino Framework's analogRead(). But that call is so slow (because it does the whole ADC initialization and deinitialization every time), that there's too much time between the voltage and current measurement.
So while I'm still using the Arduino Framework for most of the code, I've used CubeMX to configure the ADC with DMA and integrated that code into mine. I initialize the ADC/DMA, run one ADC calibration, then in a loop call HAL_ADC_Start_DMA(), wait on a dma_complete variable, that is set in the HAL_ADC_ConvCpltCallback(). This works ... almost.
At some point the values of the channels end up in the wrong order in the DMA buffer. So the second channel's conversion result is first in buffer and the first's in the second place. This seems to happen randomly, but with a low chance. Like 100 results in the correct order, then switches and gives 100 results in the wrong order (the 100 is random; I've even seen single "switches"). The result when printed afterwards the looks something like this:
V/A: 514 2087
V/A: 573 2095
V/A: 635 2095
V/A: 691 2099
V/A: 747 2102
V/A: 797 2105
V/A: 840 2107
V/A: 874 2110
V/A: 904 2111
V/A: 929 2113
V/A: 943 2114
V/A: 953 2114
V/A: 962 2114
V/A: 964 2115
V/A: 967 2115
V/A: 968 2114
V/A: 969 2115
V/A: 968 2118
V/A: 968 2118
V/A: 2116 959
V/A: 2117 951
V/A: 2115 931
V/A: 2115 909
V/A: 2114 879
V/A: 2112 841
V/A: 2110 795
V/A: 2106 745
V/A: 2106 690
V/A: 2102 631
V/A: 2098 563
V/A: 2097 497
V/A: 2092 432The ADC results are exactly what I expect to see (shows the sine on the voltage channel - the one that is less than 1000 - and the current channel, which has an offset of ideally 2048), it's just that they suddenly appear in the wrong order.
Here's the (abbreviated) code I'm using. Ignore the parts that come from Arduino. I'm not using it for analog reads.
The main code:
ADC_HandleTypeDef hadc;
DMA_HandleTypeDef hdma_adc;
uint8_t dma_complete = 0;
v_c_measurement_t measurements_raw_dbg[1 << OVER_SAMPLING_EXP];
void setup() {
hal_specific_setup::ADC_DMA_Init(&hadc);
HAL_ADCEx_Calibration_Start(&hadc);
}
void loop() {
v_c_measurement_t measurement_raw;
measure_U_I(measurement_raw);
// printing results happens here
}
void measure_U_I(v_c_measurement_t &result) {
v_c_measurement_t adc_dma_result = {0, 0};
for (uint16_t i = 0; i < (1 << OVER_SAMPLING_EXP); i++) {
dma_complete = 0;
HAL_ADC_Start_DMA(&hadc, (uint32_t*) &adc_dma_result, 2);
while(dma_complete == 0) {
__NOP();
}
measurements_raw_dbg[i].voltage = adc_dma_result.voltage;
measurements_raw_dbg[i].current = adc_dma_result.current;
delayMicroseconds(200);
}
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) {
dma_complete = 1;
}The initialization functions:
void hal_specific_setup::ADC_DMA_Init(ADC_HandleTypeDef* hadc) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_ADC1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = analogInputToPinName(_ADC_VOLTAGE_PIN) | analogInputToPinName(_ADC_CURRENT_PIN);
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
hadc->Instance = ADC1;
hadc->Init.ScanConvMode = ADC_SCAN_ENABLE;
hadc->Init.ContinuousConvMode = DISABLE;
hadc->Init.DiscontinuousConvMode = DISABLE;
hadc->Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc->Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc->Init.NbrOfConversion = 2;
if (HAL_ADC_Init(hadc) != HAL_OK)
{
Error_Handler();
}
ADC_ChannelConfTypeDef channel_config = {0};
uint32_t rbank = 0;
channel_config.Channel = get_adc_channel(analogInputToPinName(_ADC_VOLTAGE_PIN), &rbank);
channel_config.Rank = ADC_REGULAR_RANK_1;
channel_config.SamplingTime = ADC_SAMPLETIME_13CYCLES_5;
if (HAL_ADC_ConfigChannel(hadc, &channel_config) != HAL_OK) {
Error_Handler();
}
channel_config.Channel = get_adc_channel(analogInputToPinName(_ADC_CURRENT_PIN), &rbank);
channel_config.Rank = ADC_REGULAR_RANK_2;
if (HAL_ADC_ConfigChannel(hadc, &channel_config) != HAL_OK) {
Error_Handler();
}
/* DMA controller clock enable */
__HAL_RCC_DMA1_CLK_ENABLE();
/* DMA interrupt init */
/* DMA1_Channel1_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
/* ADC1 DMA Init */
hdma_adc.Instance = DMA1_Channel1;
hdma_adc.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_adc.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_adc.Init.MemInc = DMA_MINC_ENABLE;
hdma_adc.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
hdma_adc.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
hdma_adc.Init.Mode = DMA_NORMAL;
hdma_adc.Init.Priority = DMA_PRIORITY_LOW;
if (HAL_DMA_Init(&hdma_adc) != HAL_OK) {
Error_Handler();
}
__HAL_LINKDMA(hadc, DMA_Handle, hdma_adc);
}
/**
* @brief This function handles DMA1 channel1 global interrupt.
*/
void DMA1_Channel1_IRQHandler(void)
{
/* USER CODE BEGIN DMA1_Channel1_IRQn 0 */
/* USER CODE END DMA1_Channel1_IRQn 0 */
HAL_DMA_IRQHandler(&hdma_adc);
/* USER CODE BEGIN DMA1_Channel1_IRQn 1 */
/* USER CODE END DMA1_Channel1_IRQn 1 */
}Do you have any idea, what is happening here?
