I'm using an STM32F103C8 to read 3 ADC channels, and have used CubeMX + HAL to configure the ADC to shuttle the ADC values into a buffer.
I've been able to accomplish this with DMA and polling: I've seen both as generally acceptable ways to do this online. However, I haven't been able to find a configuration that will let me scan through the channels by manually starting conversions in the EOC interrupt. In all my attempts, either it doesn't advance channels, or the interrupt isn't fired at all.
I'd prefer to use interrupts because my one DMA channel on the device is used to buffer some fairly high-frequency audio data, but I'm also just disturbed that I can't understand the way the ADC interrupts work in conjunction with scan mode. I've used the following approaches:
DMA: This seems to be the authoritative way to scan multiple channels and store their respective results. Notably, the user manual in §11.3.8 ¶3 says:
When using scan mode, DMA bit must be set and the direct memory access controller is used to transfer the converted data of regular group channels to SRAM after each update of the ADC_DR register.
I was able to get it to work with the intuitive settings in CubeMX:
- ADC_Settings:
- Scan conversion mode: Enabled
- Continuous mode: Enabled
- Discontinuous mode: Disabled
- ADC_Regular_Conversion_Mode
- Enable Regular Conversions: Enable
- Number of Conversion: 3
- External Trigger Conversion Source: Regular Conversion launched by Software
- <channel configurations and ranks...>
plus a circular halfword-aligned DMA, and a straightforward call to
HAL_ADC_Start_DMA()
in the source.- ADC_Settings:
Polling: I tried following this answer which disables both continuous and discontinuous modes, and is able to step through the channels with successive calls to
HAL_ADC_PollForConversion
alone. I found that I needed to enable discontinuous mode with group sizes of 1, i.e.:hadc1.Init.DiscontinuousConvMode = ENABLE; hadc1.Init.NbrOfDiscConversion = 1;
Then stepping through the channels with
HAL_ADC_PollForConversion
worked without a hitch.Interrupts: I've tried every permutation of scan mode, discontinuous mode and number of discontinuous conversions, and none of them will let me step through the channels in the
HAL_ADC_ConvCpltCallback
interrupt routine. Here is the routine I'm using:#define NUM_ADC_BUF 8 #define NUM_ADC_CH 3 volatile uint16_t adc_buf[NUM_ADC_BUF][NUM_ADC_CH]; void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) { if(hadc->Instance == ADC1) { adc_buf[adc_buf_idx & (NUM_ADC_BUF - 1)][adc_ch++] = HAL_ADC_GetValue(hadc); if(adc_ch == NUM_ADC_CH) { adc_ch = 0; adc_buf_idx++; } HAL_ADC_Start_IT(hadc); } }
Within the ADC_Settings in CubeMX, here are my attempts and their outcomes:
+-------------------+-----------+--------------------+---------------------+------------------------------------------------------+ | (Continuous mode) | Scan mode | Discontinuous mode | Number of | Outcome | | | | | discontinuous conv. | | +-------------------+-----------+--------------------+---------------------+------------------------------------------------------+ | DISABLED | ENABLED | ENABLED | 3 | Only highest-rank-# (rank 3) channel result is given | | DISABLED | ENABLED | ENABLED | 1 | Interrupt never fires: EOC never set | | DISABLED | ENABLED | DISABLED | N/A | Only highest-rank-# (rank 3) channel result is given | | DISABLED | DISABLED | ENABLED | 3 | Only lowest-rank-# (rank 1) channel result is given | | DISABLED | DISABLED | ENABLED | 1 | Only lowest-rank-# (rank 1) channel result is given | | DISABLED | DISABLED | DISABLED | N/A | Only lowest-rank-# (rank 1) channel result is given | +-------------------+-----------+--------------------+---------------------+------------------------------------------------------+
As you can see, no combinations work properly. Is this just impossible? I suppose I can take the excerpt I quoted from the user manual to mean that the only way to use scan is to DMA, and that the polling works as a feature not formally supported. Is this true?
For reference, here is my untouched auto-generated
adc.c
from CubeMX:/* Includes ------------------------------------------------------------------*/ #include "adc.h" /* USER CODE BEGIN 0 */ /* USER CODE END 0 */ ADC_HandleTypeDef hadc1; /* ADC1 init function */ void MX_ADC1_Init(void) { ADC_ChannelConfTypeDef sConfig = {0}; /** Common config */ hadc1.Instance = ADC1; hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE; hadc1.Init.ContinuousConvMode = DISABLE; hadc1.Init.DiscontinuousConvMode = ENABLE; hadc1.Init.NbrOfDiscConversion = 3; hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START; hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; hadc1.Init.NbrOfConversion = 3; if (HAL_ADC_Init(&hadc1) != HAL_OK) { Error_Handler(); } /** Configure Regular Channel */ sConfig.Channel = ADC_CHANNEL_4; sConfig.Rank = ADC_REGULAR_RANK_1; sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) { Error_Handler(); } /** Configure Regular Channel */ sConfig.Channel = ADC_CHANNEL_8; sConfig.Rank = ADC_REGULAR_RANK_2; if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) { Error_Handler(); } /** Configure Regular Channel */ sConfig.Channel = ADC_CHANNEL_9; sConfig.Rank = ADC_REGULAR_RANK_3; if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) { Error_Handler(); } } void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle) { GPIO_InitTypeDef GPIO_InitStruct = {0}; if(adcHandle->Instance==ADC1) { /* USER CODE BEGIN ADC1_MspInit 0 */ /* USER CODE END ADC1_MspInit 0 */ /* ADC1 clock enable */ __HAL_RCC_ADC1_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); /**ADC1 GPIO Configuration PA4 ------> ADC1_IN4 PB0 ------> ADC1_IN8 PB1 ------> ADC1_IN9 */ GPIO_InitStruct.Pin = Xin_Pin; GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; HAL_GPIO_Init(Xin_GPIO_Port, &GPIO_InitStruct); GPIO_InitStruct.Pin = Zin_Pin|Yin_Pin; GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); /* ADC1 interrupt Init */ HAL_NVIC_SetPriority(ADC1_2_IRQn, 0, 0); HAL_NVIC_EnableIRQ(ADC1_2_IRQn); /* USER CODE BEGIN ADC1_MspInit 1 */ /* USER CODE END ADC1_MspInit 1 */ } } void HAL_ADC_MspDeInit(ADC_HandleTypeDef* adcHandle) { if(adcHandle->Instance==ADC1) { /* USER CODE BEGIN ADC1_MspDeInit 0 */ /* USER CODE END ADC1_MspDeInit 0 */ /* Peripheral clock disable */ __HAL_RCC_ADC1_CLK_DISABLE(); /**ADC1 GPIO Configuration PA4 ------> ADC1_IN4 PB0 ------> ADC1_IN8 PB1 ------> ADC1_IN9 */ HAL_GPIO_DeInit(Xin_GPIO_Port, Xin_Pin); HAL_GPIO_DeInit(GPIOB, Zin_Pin|Yin_Pin); /* ADC1 interrupt Deinit */ HAL_NVIC_DisableIRQ(ADC1_2_IRQn); /* USER CODE BEGIN ADC1_MspDeInit 1 */ /* USER CODE END ADC1_MspDeInit 1 */ } } /* USER CODE BEGIN 1 */ /* USER CODE END 1 */