# STM32: Why can't I use scan mode in interrupt-driven ADC?

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:

• Scan conversion mode: Enabled
• Continuous mode: Enabled
• Discontinuous mode: Disabled
• 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.

• 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;


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
}

}
}


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 ------------------------------------------------------------------*/

/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

{

/** Common config
*/
{
Error_Handler();
}
/** Configure Regular Channel
*/
{
Error_Handler();
}
/** Configure Regular Channel
*/
{
Error_Handler();
}
/** Configure Regular Channel
*/
{
Error_Handler();
}

}

{

GPIO_InitTypeDef GPIO_InitStruct = {0};
{
/* USER CODE BEGIN ADC1_MspInit 0 */

/* USER CODE END ADC1_MspInit 0 */

__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
*/
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);

/* USER CODE BEGIN ADC1_MspInit 1 */

/* USER CODE END ADC1_MspInit 1 */
}
}

{

{
/* USER CODE BEGIN ADC1_MspDeInit 0 */

/* USER CODE END ADC1_MspDeInit 0 */
/* Peripheral clock disable */

*/
HAL_GPIO_DeInit(Xin_GPIO_Port, Xin_Pin);

HAL_GPIO_DeInit(GPIOB, Zin_Pin|Yin_Pin);

/* USER CODE BEGIN ADC1_MspDeInit 1 */

/* USER CODE END ADC1_MspDeInit 1 */
}
}

/* USER CODE BEGIN 1 */

/* USER CODE END 1 */

• What does the Reference Manual say? Jun 7 '20 at 0:17
• @P__J__ Besides the quote I've mentioned, I couldn't find any detail on how successive conversions relate to the groups of channels. I would've accepted the fact that scan and discontinuous modes only make sense with a DMA if there wasn't a way to step through channels in polling mode as well, which is not documented in the referece manual. Jun 7 '20 at 5:09
• Everything is documented there. But for sure not in the HAL documentation or infile help (you took the quote from there). Use registers and everything "magically" will start to work as intended. Abandon HAL for such a simple peripherals!! Jun 7 '20 at 8:33
• @P__J__ Are you saying that interrupt-based scanning should be possible? I'd love to be proven wrong about what's in the reference manual about this, but from multiple reads over, the manual really doesn't say anything aside from "set scan mode = 1" to get channels to increment. Jun 7 '20 at 18:03
• What is the value of ADC->CR2 : EOCS bit? Apr 2 at 17:20

Unlike F4 series, ADC in F1 series can generate only one (single) interrupt at the end of the whole scanning sequence. That's why DMA is a must when using scanning mode.

However, I think it's still possible to configure ADC for interrupt based scanning, but you have to do this manually:

1. Disable scanning and continuous mode. Configure ADC for single conversion.
2. Select the ADC channel using ADC->SQR3 : SQ1 bits (1st conversion in regular sequence)
3. Enable EOC interrupt using ADC->CR1 : EOCIE bit
4. In interrupt service routine (ISR), collect the result from ADC->DR.
5. In ISR, Repeat step 2 to configure ADC for the next channel.
6. ADC needs to be triggered manually or by using an external event.

For periodic sampling of multiple channels, you will probably need 2 TIM modules and some TIM interrupts. One timer (Slave) controls the timing between each channel of a scanning group and triggers the ADC module. This timer needs to be aware of its number of overflows (using an interrupt) so it can disable itself after all the channels in the sequence are sampled and converted. Another timer (Master) controls the timing between the scanning sequences, and it activates the slave timer (and probably the first scan in the sequence).

I don't know how busy your DMA is, but I would definitely try using DMA before I try the method I described above.

• Figures, that would explain why I can only ever get the last channel in the sequence using scan mode. In my case I convert the channels continuously (i.e. no delay between scanning sequences) so I think I should only ever need the "slave" timer, but I agree this is probably the best it gets on F1. Good to know this exists in F4! Apr 11 at 6:36