STM32 HAL Tutorial: Efficient Multi-Channel Analog Data Acquisition
Abstract
Learn how to configure STM32 ADC with interrupts and multi-channel sequencing using HAL. Step-by-step guide for efficient analog data acquisition and processing.
1. Introduction
For applications with multiple sensors or analog inputs:
- Reading channels sequentially is required
- Using interrupts avoids CPU busy-waiting
- Ideal for sensor fusion, multi-input monitoring, and control systems
By the end of this episode, you’ll be able to:
- Configure ADC for multiple channels
- Use interrupts to handle completed conversions
- Process data efficiently without polling
2. Prerequisites
- STM32 board with ADC
- STM32CubeIDE installed
- Knowledge of HAL, Interrupts and ADC
3. ADC Multi-Channel Sequencing
- ADC can scan multiple channels in either Sequencer fully configurable or not fully configurable
- As not fully configurable, the readings will be either forward or backward on the channels. Example: Channels 0, 1, 2 → measured in order automatically
- As fully configurable, the user can select the order of channels to be read and individual sample time per channel using the Rank system. The main limitation is that this mode allows only up to 8 ranks. Example: Channels 0, 1, 2 → measured in order rank order, fully customizable
- Conversion completes → interrupt triggered or DMA management
4. CubeMX Configuration
- Enable ADC
- Set Scan Conversion Mode = Enabled
- Add multiple channels in the regular sequence
- Enable End of Sequence interrupt
- Optionally use DMA for continuous acquisition
5. HAL Example: Initialization
ADC_HandleTypeDef hadc1;
void MX_ADC1_Init(void)
{
ADC_ChannelConfTypeDef sConfig = {0};
hadc1.Instance = ADC1;
hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 3;
HAL_ADC_Init(&hadc1);
// Configure Channel 0
sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_7CYCLES_5;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
// Configure Channel 1
sConfig.Channel = ADC_CHANNEL_1;
sConfig.Rank = ADC_REGULAR_RANK_2;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
// Configure Channel 2
sConfig.Channel = ADC_CHANNEL_2;
sConfig.Rank = ADC_REGULAR_RANK_3;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
// Enable ADC interrupt
HAL_NVIC_SetPriority(ADC1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(ADC1_IRQn);
}
6. Handling ADC Interrupts
1. Define End of Conversion callback
uint16_t adcValues[3];
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
// Read converted values sequentially
adcValues[0] = HAL_ADC_GetValue(hadc); // Channel 0
adcValues[1] = HAL_ADC_GetValue(hadc); // Channel 1
adcValues[2] = HAL_ADC_GetValue(hadc); // Channel 2
// Process the data (e.g., sensor fusion)
}
2. Start conversion in main loop:
HAL_ADC_Start_IT(&hadc1); // Triggers ADC sequence
7. Hands-On Lab Example
1. Connect three analog sensors to channels 0, 1 and 4
2. Initialize ADC with Continuous conversion, Sequencer fully configurable, 3 conversions with DMA
3. Trigger conversion with software start
/* USER CODE BEGIN PV */
uint16_t u16ADCBuffer[11];
/* USER CODE END PV */
/* USER CODE BEGIN 2 */
HAL_ADCEx_Calibration_Start(&hadc1);
HAL_ADC_Start_DMA(&hadc1, (uint32_t *) u16ADCBuffer, 11);
/* USER CODE END 2 */
4. Print or process ADC readings
5. Connect 11 analog channels, from 0..10 and change the Sequencer to not fully configurable
6. Recompile and test it again
Tip: Combine with DMA and timers for continuous multi-channel acquisition without CPU load
8. Advantages
- Efficient multi-channel data acquisition
- Reduces CPU load via interrupts
- Compatible with DMA and timer-triggered ADC
- Essential for sensor fusion, control loops, and embedded measurement systems


