STM32 ADC Scan Sequence with DMA: Continuous Multi-Channel Acquisition on STM32F4

2026-06-06 · Davide Carrese
STM32 · ADC · DMA · STM32F4 · Embedded

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:

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:

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:

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:

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

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:

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

Comments

Have comments? Send me an email.