STM32 ADC Scan Sequence with DMA: Continuous Multi-Channel Acquisition on STM32F4
Every embedded system that reads analogue signals — battery voltage, current sense, thermistor, potentiometer, pressure transducer — needs the ADC. On the STM32F4, the ADC is a 12-bit SAR converter capable of up to 2.4 MSPS, with a flexible scan engine that sequences through multiple channels automatically. When combined with DMA, you get continuous ring-buffered acquisition without a single cycle spent polling in software. This article walks through the register-level setup of scan mode, continuous conversion, DMA transfer, and calibration on the STM32F401.
ADC architecture on STM32F4
The STM32F401 has a single 12-bit successive-approximation ADC with up to 16 external channels (plus internal channels for temperature sensor, VREFINT, and VBAT). The converter can be configured in two independent groups:
- Regular group — up to 16 channels sequenced in programmable order. Conversions triggered by software, timer, or external pin. Results land in a single
ADC_DRregister. - Injected group — up to 4 channels with priority over the regular group. When an injected trigger occurs, the regular conversion is interrupted, the injected channels are converted, and the regular conversion resumes. Each injected channel has its own data register (
ADC_JDR1..4).
For most data-acquisition tasks, you will use the regular group in scan mode. The scan sequencer walks through the ranked channels (SQ1…SQ16) in order, converting one after another. Each conversion triggers the EOC (end of conversion) flag, and when the entire sequence finishes, the EOS (end of sequence) flag is set.
Scan mode + continuous conversion
Scan mode (bit SCAN in ADC_CR1) enables the sequencer. Without it, only SQ1 is converted. With scan enabled, the ADC cycles through all configured ranks.
Continuous conversion (bit CONT in ADC_CR2) makes the ADC restart the sequence immediately after finishing one scan, without waiting for a new trigger. This is what you want for free-running data acquisition.
The DMA transfer (bit DMA in ADC_CR2) streams each converted data word out of ADC_DR as soon as EOC fires. With DDS (DMA disable selection) cleared, the DMA request is generated after every data word in the sequence, not just at end-of-sequence.
Sequencer length and channel ranking
The number of channels in the scan is set in ADC_SQR1 (bits L[3:0]). The mapping is: 0 = 1 channel, 1 = 2 channels, …, 15 = 16 channels. Each rank registers the actual channel number in the SQR registers:
ADC_SQR3— SQ1 (bits31:28) … SQ6 (bits11:8)ADC_SQR2— SQ7 … SQ12ADC_SQR1— SQ13 … SQ16
Each field is a 5-bit channel number (0–18 on STM32F401). The order in the SQR registers defines the conversion order, which is independent of the channel number.
Sampling time per channel
Each channel can have an independent sampling time programmed in ADC_SMPR1 (channels 10–18) and ADC_SMPR2 (channels 0–9). The sampling time directly affects the total conversion time:
TCONV = Sampling time + 12 cycles (for 12-bit resolution)
On the STM32F401, the ADC runs from the APB2 clock divided by a prescaler (bits ADCPRE[1:0] in ADC_CCR). At 84 MHz APB2, setting ADCPRE = 2 (÷4) gives a 21 MHz ADC clock — safely under the 36 MHz maximum. Each ADC cycle is then ~47.6 ns.
Sampling time choices: 3, 15, 28, 56, 84, 112, 144, or 480 cycles. For a 10 kΩ source impedance driving a 4 pF sampling capacitor, 15 cycles (≈ 0.71 µs) is usually sufficient; for high-impedance sensors (like a voltage divider with 100 kΩ series resistance), use 112 or 480 cycles.
ADC calibration
The STM32F4 ADC has an embedded calibration that compensates for manufacturing variations in the comparator offset. Calibration must be run when the ADC is powered on but idle:
void adc_calibrate(void)
{
// Enable ADC voltage regulator (ADON bit)
ADC1->CR2 |= ADC_CR2_ADON;
// Wait for regulator startup (~10 µs)
for (volatile int i = 0; i < 200; i++);
// Start calibration
ADC1->CR2 |= ADC_CR2_CAL;
while (ADC1->CR2 & ADC_CR2_CAL);
}
The calibration factor is stored in ADC_DR after completion and automatically applied. The calibration is valid for the current VDDA and temperature — run it again if the supply or temperature changes significantly. Some applications run calibration on every boot; others run it once and store the factor for suspend/resume cycles.
Register-level ADC + DMA setup
Here is a complete initialisation sequence for a 4-channel scan (PA0–PA3 → ADC1_IN0…ADC1_IN3) at 21 MHz ADC clock with 15-cycle sampling time, continuous mode, and DMA circular transfer into a 256-sample buffer:
#define ADC_BUF_LEN 256
static volatile uint16_t adc_buf[ADC_BUF_LEN]; // DMA writes here
static volatile uint32_t sample_count = 0; // incremented by ADC ISR
static void adc_gpio_init(void)
{
// PA0..PA3 = analog input
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
GPIOA->MODER |= (3 << (0*2)) | (3 << (1*2)) | (3 << (2*2)) | (3 << (3*2));
GPIOA->PUPDR &= ~(3 << (0*2)) | (3 << (1*2)) | (3 << (2*2)) | (3 << (3*2));
}
static void adc_init(void)
{
// 1. Enable ADC1 and DMA2 clocks
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN;
__DSB();
// 2. ADC common register: prescaler = /4 → 21 MHz ADC clock
ADC->CCR = (2 << ADC_CCR_ADCPRE_Pos);
// 3. Calibration
adc_calibrate();
// 4. SCAN + CONT + DMA + DDS
// SCAN=1 (scan mode), EOCIE=0 (we use DMA), RES=0 (12-bit)
ADC1->CR1 = ADC_CR1_SCAN;
// CONT=1 (continuous), DMA=1 (DMA requests), DDS=0 (request per data)
ADC1->CR2 = ADC_CR2_CONT | ADC_CR2_DMA;
// 5. Sampling time: 15 cycles on channels 0..3
ADC1->SMPR2 = (ADC_SMPR2_SMP0_0 | ADC_SMPR2_SMP0_1 | ADC_SMPR2_SMP0_2); // 480 cycles
// Actually, let's use 15 cycles: SMP0..3 = 001 = 15 cycles
ADC1->SMPR2 = (1 << (0*3)) | (1 << (1*3)) | (1 << (2*3)) | (1 << (3*3));
// 6. Sequence: 4 channels (L=3 → 4 channels)
ADC1->SQR1 = (3 << ADC_SQR1_L_Pos); // 4 channels total
ADC1->SQR3 = (0 << ADC_SQR3_SQ1_Pos) // SQ1 = ch0
| (1 << ADC_SQR3_SQ2_Pos) // SQ2 = ch1
| (2 << ADC_SQR3_SQ3_Pos) // SQ3 = ch2
| (3 << ADC_SQR3_SQ4_Pos); // SQ4 = ch3
// 7. DMA2 stream 0, channel 0 → ADC1 (see DMA request mapping table)
DMA2_Stream0->PAR = (uint32_t)&ADC1->DR; // source = ADC DR
DMA2_Stream0->M0AR = (uint32_t)adc_buf; // destination
DMA2_Stream0->NDTR = ADC_BUF_LEN; // number of transfers
DMA2_Stream0->CR = DMA_SxCR_CHSEL_0 // channel select = 0
| DMA_SxCR_MSIZE_0 // memory 16-bit
| DMA_SxCR_PSIZE_0 // peripheral 16-bit
| DMA_SxCR_MINC // increment memory
| DMA_SxCR_CIRC // circular mode
| DMA_SxCR_TCIE; // interrupt on completion
DMA2_Stream0->FCR = DMA_SxCR_DMDIS; // direct mode (no FIFO)
// 8. Enable DMA stream and ADC
DMA2_Stream0->CR |= DMA_SxCR_EN;
ADC1->CR2 |= ADC_CR2_ADON; // power on ADC
ADC1->CR2 |= ADC_CR2_SWSTART; // first software trigger
}
void DMA2_Stream0_IRQHandler(void)
{
if (DMA2_Stream0->SR & DMA_SR_TCIF0) {
DMA2_Stream0->SR &= ~DMA_SR_TCIF0;
sample_count += ADC_BUF_LEN; // one full buffer collected
}
}
A few notes on this setup:
- Circular DMA —
CIRCmakes the stream wrap around in the buffer. The buffer behaves as a ring; you read the latestADC_BUF_LENsamples at any point. The transfer-complete interrupt fires every time the buffer fills, letting you throttle processing to frame boundaries. - 16-bit transfers — The ADC result is 12-bit left- or right-aligned in a 16-bit register. With
PSIZEandMSIZEset to 16-bit, each DMA beat transfers one conversion result. - Channel selection — DMA2 stream 0 with channel 0 maps to ADC1 on STM32F401. Always verify the DMA request mapping in the reference manual (Table 40 in RM0368 for F401).
- SWSTART vs timer trigger — The code uses
SWSTARTto kick off the first conversion. In continuous mode, after SWSTART the ADC keeps cycling. For precise periodic acquisition, connect a timer output trigger (e.g. TIM2_TRGO) to the ADC viaADC_CR2_EXTSEL.
Understanding the ADC data alignment
The 12-bit result can be aligned left or right in the 16-bit data register via the ALIGN bit in ADC_CR2:
- Right alignment (
ALIGN = 0) — bits[11:0]hold the result,[15:12]are zero. Extract withadc_buf[i] & 0x0FFF. - Left alignment (
ALIGN = 1) — bits[15:4]hold the result left-shifted by 4. Useful when directly computing percentages:value >> 4gives a 0–4095 range. Extract withadc_buf[i] >> 4.
I use right alignment in every project. It preserves the raw 12-bit value without a shift, which is important when you need to apply calibration coefficients or lookup-tables indexed by the full 12-bit code.
Practical example: 4-channel sensor data logger
Imagine a client project that monitors four analogue signals: battery voltage (÷3 voltage divider on PA0), a 10 kΩ NTC thermistor (PA1), a current-sense amplifier output (PA2), and an external setpoint potentiometer (PA3). The ADC continuously converts all four channels at ~1.8 MSPS total (~450 kSPS per channel). Every 256 complete scans (1024 samples), the DMA ISR sets a flag and the main loop processes the latest frame.
// Called from main loop when sample_count advances
void process_adc_frame(void)
{
// adc_buf contains the latest 256 samples in scan order:
// [ch0_0, ch1_0, ch2_0, ch3_0, ch0_1, ch1_1, ...]
// 64 samples per channel in each frame (256 / 4 = 64)
uint32_t sum_ch0 = 0, sum_ch1 = 0, sum_ch2 = 0, sum_ch3 = 0;
uint16_t min_ch0 = 0xFFFF, max_ch0 = 0;
for (int i = 0; i < ADC_BUF_LEN; i += 4) {
uint16_t v0 = adc_buf[i + 0] & 0x0FFF; // right-aligned
uint16_t v1 = adc_buf[i + 1] & 0x0FFF;
uint16_t v2 = adc_buf[i + 2] & 0x0FFF;
uint16_t v3 = adc_buf[i + 3] & 0x0FFF;
sum_ch0 += v0;
sum_ch1 += v1;
sum_ch2 += v2;
sum_ch3 += v3;
if (v0 < min_ch0) min_ch0 = v0;
if (v0 > max_ch0) max_ch0 = v0;
}
uint16_t avg_ch0 = sum_ch0 / 64;
// Convert to physical units
float vbat = avg_ch0 * 3.3f / 4096.0f * 3.0f; // ×3 for divider
float temp_raw = (float)sum_ch1 / 64.0f;
// ... NTC lookup table or Steinhart-Hart equation
}
The scan order guarantees that channel indices are interleaved in a deterministic pattern. If your sensor channels have different sampling time requirements (e.g. a high-impedance channel needs 480 cycles), you must assign per-channel sampling times in SMPR1/SMPR2. The ADC applies the channel's sampling time when it reaches that rank in the scan.
Practical checklist
- Run calibration every boot — Bypassing calibration can introduce up to ±10 LSB offset error, which on a 12-bit ADC with 3.3 V reference is about ±8 mV. For a battery monitor reading 7.4 V through a 3:1 divider, that is ±24 mV tolerance added to already noisy readings.
- Verify ADC clock frequency — Read
ADC_CCRto confirmADCPRE. At 84 MHz APB2, ÷4 gives 21 MHz; ÷6 gives 14 MHz. The ADC maximum is 36 MHz on F401. Too high a clock reduces accuracy; too low a clock increases conversion time unnecessarily. - Check DMA channel mapping — Each ADC instance connects to specific DMA streams/channels. On F401: ADC1 → DMA2 stream 0/4, channel 0. ADC2 → DMA2 stream 2/3, channel 1. ADC3 → DMA2 stream 1, channel 2. Wrong mapping means no data movement.
- Circular buffer read pointer — In continuous circular mode, the DMA writes over old data. The application must read samples faster than one buffer fill cycle. At 4 channels × 450 kSPS = 1.8 MSPS, a 256-sample buffer (1024 bytes for 16-bit × 512 halfwords) fills in ~142 µs. That is a tight window for a main loop — consider using a double-buffer scheme with the DMA's
CTflag (current target) or the transfer-complete interrupt to switch between two ping-pong buffers. - Injected group for urgent measurements — If a critical analogue input (e.g. overcurrent sense) must be measured immediately without waiting for the regular scan to finish, assign it to the injected group. Injected conversions preempt the regular scan, store into
JDRxregisters, and the regular scan resumes from where it stopped. - Watchdog on analogue channels — The analogue watchdog (AWD) in
ADC_CR1can monitor one or all channels and fire an interrupt if the converted value is outside a programmed window. This is useful for fault detection without polling.
How I would approach this on a client project
The code above is the skeleton I use to validate ADC hardware on a new board — four random channels, free-running, DMA into a buffer, verify on oscilloscope that the analogue front-end is clean and the pins are correctly routed. Once validated, I replace the raw buffer with a proper signal-conditioning pipeline:
- Hardware trigger — Replace SWSTART with a timer output trigger (e.g. TIM8_TRGO at 1 kHz) so that the conversion rate is deterministic and independent of interrupt latency.
- Ping-pong DMA — Use two buffers (DMA double-buffer mode on STM32F4:
DBM = 1). While DMA fills buffer B, the application processes buffer A and vice versa. This eliminates the race condition on the circular pointer. - Oversampling — For noisy environment (motor drives, switch-mode supplies), accumulate 16 or 64 readings and shift right to gain extra bits of resolution. A 64× oversample on a 12-bit ADC yields 3 extra bits (15-bit effective resolution) at the cost of 64× lower throughput.
- DC offset calibration — On production, sample an internal VREFINT channel to measure the actual VDDA, then correct all external readings for supply variation.
- Per-channel averaging filter — A simple exponential moving average per channel instead of frame-based block averaging, so the output updates smoothly on every scan rather than in bursts.
In one recent industrial project, I used exactly this pattern for a 6-channel ADC on STM32F410 reading PT1000 RTDs through a multiplexer and instrumentation amplifier. The DMA double-buffer with timer-triggered conversion at 200 Hz per channel produced clean 14-bit effective resolution after 16× oversampling — all without a single wasted CPU cycle on data movement.
Sources and further reading
- STMicroelectronics, RM0368 — STM32F401 Reference Manual: Chapter 13 (ADC), register descriptions and DMA request mapping.
- STMicroelectronics, AN2834 — How to get the best ADC accuracy in STM32 microcontrollers: source impedance, sampling time calculations, and noise reduction techniques.
- STMicroelectronics, AN3116 — STM32 ADC modes and applications: scan mode, discontinuous mode, injected group examples.
- STMicroelectronics, STM32CubeF4 Firmware Package —
Projects/STM32F401RE-Nucleo/Examples/ADC/ADC_DMA_Transfer: HAL-based reference for ADC + DMA integration. - STMicroelectronics, STM32F401xE datasheet: ADC characteristics, maximum clock frequency, sampling capacitor values.
- Memfault Blog — "How to Read and Calibrate an STM32 ADC" and the "Interrupt" series on embedded data acquisition patterns.
- ARM, CMSIS-Core (Cortex-M4): intrinsic functions for memory barriers and NVIC configuration.

Comments
Have comments? Send me an email.