STM32 HAL ADC: configuration, sampling strategies, and data movement with polling, interrupts, and DMA
1. Fundamentals
- ADC converts an analog voltage into a digital code.
- Resolution defines the number of quantization steps:
- 8-bit: 0–255
- 12-bit: 0–4095 (2^12 − 1)
- A single ADC controller typicallly multiplexes multiple input channels. When reading more than one channel, enable scanning or explicitly select each channel.
- DMA can transfer ADC results directly into memory without CPU involvement, improving throughput and reducing latency.
- Reference voltages (Vref+ and Vref−) define the conversion range. Many STM32 designs use Vref+ = VCC and Vref− = GND. Some devices support an external reference pin.
- Internal channels are available for on-chip temperature sensor and VBAT/RTC supply measurement.
- Sampling time affects accuracy and source loading. Longer sampling times improve acquisition for high-impedance sources. The total time per conversion is a function of the ADC clock and the selected sampling cycles.
- Example: with ADC clock = 12 MHz and sampling time = 239.5 cycles, one conversion duration ≈ (SamplingCycles + ConversionCycles)/Fadc.
Conversion modes
- Single conversion: performs one conversion for the current channel/rank.
- Continuous conversion: repeats conversions without software re-trigger.
- Scan mode: steps through a configured sequence of ranks (multiple channels).
Operation strategies
- Polling (blocking): CPU waits for completion via HAL_ADC_PollForConversion.
- Interrupt-driven: conversion-complete (EOC) triggers HAL callback.
- DMA-driven: hardware fills a memory buffer automatically.
2. CubeMX essentials
- System clock: configure an external crystal if required and set SWD for debugging.
- ADC prescaler and clock: target 12 MHz ADC clock (device-dependent; ensure within datasheet limits).
- Enable the desired ADC instance (e.g., ADC1) and set:
- Resolution (e.g., 12-bit)
- Data alignment (left or right; right-aligned is typical)
- Scan mode if sampling multiple channels
- Continuous vs. discontinuous mode
- Number of conversions (ranks) when scanning
- External trigger (software, timer, etc.)
- Sampling time for each channel (e.g., 239.5 cycles)
- Optional: Analog Watchdog thresholds for out-of-range detection
Configure pins for anallog mode for the selected channels. If using internal channels (temperature sensor, VBAT), enable them where applicable.
3. Single-channel acquisition using polling (blocking)
Use when CPU time is available and sample rate is low.
- Steps:
- Start conversion
- Poll for completion
- Read the result
Example (assuming ADC1 and one configured regular channel):
#include "main.h"
extern ADC_HandleTypeDef hadc1;
static uint16_t adc_read_once(void) {
uint16_t result = 0;
if (HAL_ADC_Start(&hadc1) != HAL_OK) {
return 0;
}
if (HAL_ADC_PollForConversion(&hadc1, 100) == HAL_OK) {
result = (uint16_t)HAL_ADC_GetValue(&hadc1);
}
HAL_ADC_Stop(&hadc1);
return result;
}
// Usage
volatile uint16_t adc_sample = 0;
void app_single_read(void) {
adc_sample = adc_read_once();
// For 12-bit and Vref+ = 3.3V: Vin ≈ (adc_sample / 4095.0f) * 3.3f
}
4. Multi-channel acquisition using polling (scan + discontinuous)
When multiple channels are configured as a scan sequence, enabling discontinuous mode with a count of 1 allows one rank per software start. Each start-convert-read advances to the next rank.
- CubeMX:
- Enable Scan Conversion Mode
- Enable Discontinuous Mode; set count = 1
- Number of Conversions = 3 (example)
- Trigger = Software start
- Right alignmant
Example to read three ranks into an array:
#include "main.h"
extern ADC_HandleTypeDef hadc1;
static void adc_read_sequence_blocking(uint16_t *dst, uint8_t count) {
for (uint8_t i = 0; i < count; ++i) {
if (HAL_ADC_Start(&hadc1) != HAL_OK) {
dst[i] = 0;
continue;
}
if (HAL_ADC_PollForConversion(&hadc1, 100) == HAL_OK) {
dst[i] = (uint16_t)HAL_ADC_GetValue(&hadc1);
} else {
dst[i] = 0;
}
HAL_ADC_Stop(&hadc1);
}
}
#define ADC_CHANS 3
static uint16_t seq_values[ADC_CHANS] = {0};
void app_multi_read_polling(void) {
adc_read_sequence_blocking(seq_values, ADC_CHANS);
// seq_values[0..2] correspond to ranks 1..3
}
5. Multi-channel acquisition with interrupts
With continuous conversion and scan enabled, HAL_ADC_Start_IT will repeatedly trigger EOC interrupts. Buffer the data in the conversion-complete callback. Note that with scanning, the callback fires for each rank in sequence; if you only store raw samples, the order reflects the rank order but may appear interleaved with continuous operation.
- CubeMX:
- Scan mode: enabled
- Continuous conversion: enabled
- EOC interrupt: enabled
- Configure NVIC priority as needed
Example ring buffer:
#include "main.h"
extern ADC_HandleTypeDef hadc1;
#define ADC_RANKS 3
#define SAMPLES_PER_RANK 5
#define ADC_BUF_LEN (ADC_RANKS * SAMPLES_PER_RANK)
static volatile uint16_t adc_ring[ADC_BUF_LEN] = {0};
static volatile uint16_t adc_w = 0;
void adc_start_it(void) {
HAL_ADC_Start_IT(&hadc1);
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) {
if (hadc->Instance != ADC1) {
return;
}
adc_ring[adc_w++] = (uint16_t)HAL_ADC_GetValue(hadc);
if (adc_w >= ADC_BUF_LEN) {
adc_w = 0;
}
}
If channel-to-rank association is required, map indices by rank (e.g., index % ADC_RANKS indicates rank position within the scan).
6. Multi-channel acquisition with DMA
DMA provides the highest efficiency for periodic sampling of multiple channels. In circular mode, the ADC continuously fills the buffer and wraps around, optionally generating half/full transfer callbacks.
- CubeMX:
- Scan mode: enabled; set Number of Conversions to total channels
- Continuous conversion: enabled
- EOC interrupt: disabled (DMA will handle transfers)
- ADC DMA: enabled on the ADC regular conversion
- DMA mode:
- Circular for continuous streaming; Normal for one-shot
- Data width: Half Word (16-bit) for 12-bit ADC data
- Memory increment: enabled
Example startup code:
#include "main.h"
extern ADC_HandleTypeDef hadc1;
#define ADC_RANKS 3
#define SAMPLES_PER_RANK 5
#define ADC_DMA_LEN (ADC_RANKS * SAMPLES_PER_RANK)
static uint16_t adc_dma_buf[ADC_DMA_LEN] = {0};
void adc_start_dma(void) {
// For circular streaming, ensure DMA channel/stream is configured in circular mode in CubeMX
HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_dma_buf, ADC_DMA_LEN);
}
- In circular mode, the buffer layout is typically rank-major on most STM32 HAL configurations: [R1,R2,R3,R1,R2,R3,...]. Verify on your device and HAL version.
- Convert to voltage with Vin ≈ (sample / 4095.0f) * Vref+, or use Vrefint calibration for better accuracy if supported.
7. Notes on configuration parameters
- Data Alignment: Right alignment is common; left alignment can simplify scaling by shifting.
- External Trigger: Use software trigger for manual acquisision or a timer trigger for periodic sampling.
- Analog Watchdog: Configure thresholds to generate an interrupt when a reading goes outside [low, high]. Useful for fault detection without constant polling.
- Source impedance: For accurate results, insure the signal source impedance and sampling time satisfy datasheet requirements (settling within 1 LSB).