All guides
TECHNICAL GUIDESTM32L4R5ADC2026

STM32L4R5 ADC
DMA, Oversampling & Calibration

Register-level and HAL configuration of the STM32L4R5 12-bit SAR ADCs — clock & calibration, channel sequencing, sampling time, hardware oversampling to 16-bit, and circular DMA for multi-channel acquisition.

01 ADC architecture & register map

The STM32L4R5 (Cortex-M4F, RM0432) integrates two independent 12-bit successive-approximation ADCsADC1 and ADC2 — on the AHB2 bus. Each converts up to 5 Msps at 12-bit and supports selectable 12/10/8/6-bit resolution, a hardware oversampling engine (up to 16-bit effective), a 16-entry regular sequencer, an injected group, analog watchdogs, and DMA output.

Registers split into two groups. Per-ADC registers (one set for ADC1, one for ADC2) hold everything about one converter: enable/calibration (CR), configuration (CFGR/CFGR2), sampling time (SMPRx), sequence (SQRx) and data (DR). The common block ADC12_COMMON holds settings shared by both: the clock source/prescaler (CCR.CKMODE/PRESC), internal-channel enables (VREFINT, temperature, VBAT), dual-mode and the combined status/data registers.

ADC1 base0x5004_0000 — per-ADC registers for converter 1
ADC2 base0x5004_0100 — per-ADC registers for converter 2
ADC12_COMMON0x5004_0300 — shared CSR / CCR / CDR
OffsetRegisterPurpose (key fields)
0x00ADC_ISRStatus: ADRDY, EOSMP, EOC, EOS, OVR (write-1-to-clear)
0x04ADC_IERInterrupt enables mirroring ISR flags
0x08ADC_CRADEN, ADDIS, ADSTART, ADSTP, ADCAL, ADCALDIF, ADVREGEN, DEEPPWD
0x0CADC_CFGRRES, CONT, DMAEN, DMACFG, OVRMOD, EXTEN, EXTSEL, ALIGN, DISCEN
0x10ADC_CFGR2ROVSE, JOVSE, OVSR[2:0], OVSS[3:0], TROVS, ROVSM (oversampling)
0x14 / 0x18ADC_SMPR1 / SMPR2Sampling time SMP0..SMP9 / SMP10..SMP18 (3 bits each)
0x30..0x3CADC_SQR1..SQR4Regular sequence length L and channels SQ1..SQ16 (5 bits each)
0x40ADC_DRRegular data register (reading clears EOC)
0xB0 / 0xB4ADC_DIFSEL / CALFACTDifferential select / calibration factors

Common block (offset from 0x5004_0300): CSR 0x00, CCR 0x08, CDR 0x0C.

c — CMSIS register handles (from stm32l4r5xx.h)
/* All symbols below ship in the CubeL4 CMSIS device header. */
#include <stdint.h>
#include "stm32l4r5xx.h"

ADC_TypeDef        *a1 = ADC1;          // 0x50040000
ADC_TypeDef        *a2 = ADC2;          // 0x50040100
ADC_Common_TypeDef *ac = ADC12_COMMON;  // 0x50040300 (shared CCR)

This section

  • Two 12-bit SAR ADCs (ADC1/ADC2) on AHB2, up to 5 Msps, 6/8/10/12-bit + oversampling.
  • Per-ADC registers configure one converter; ADC12_COMMON.CCR configures the shared clock and internal channels.
  • Enable the peripheral clock via RCC_AHB2ENR.ADCEN before touching any ADC register.

02 Clock, power-up & calibration

The ADC needs a working kernel clock, an enabled internal voltage regulator, and a calibration pass before the first conversion. Skipping any of these is the most common reason an ADC "reads garbage" or never sets ADRDY.

Choosing the ADC clock (CCR.CKMODE / PRESC)

Two clocking modes exist. In asynchronous mode (CKMODE=00) the ADC runs from a dedicated kernel clock selected by RCC_CCIPR.ADCSEL, then divided by PRESC. In synchronous mode (CKMODE=01/10/11) it runs from the AHB clock (HCLK) divided by 1/2/4. Keep the resulting fADC within the datasheet maximum (DS12023) — for 12-bit fast conversion this is on the order of tens of MHz, so at a 120 MHz SYSCLK use a prescaler.

CKMODE[1:0]ADC clock source
00Asynchronous: RCC_CCIPR.ADCSEL kernel clock ÷ PRESC
01HCLK / 1 (synchronous — requires AHB prescaler = 1)
10HCLK / 2 (synchronous)
11HCLK / 4 (synchronous)
ADCSEL[1:0] (RCC_CCIPR)Async kernel clock
00No clock selected
01PLLSAI1 "R" output (PLLADC1CLK)
11SYSCLK

PRESC[3:0] async divider: 0000=/1, 0001=/2, 0010=/4, 0011=/6, 0100=/8, 0101=/10 … 1011=/256.

Power-up + calibration order (CR)

01 RCC_AHB2ENR.ADCEN = 1   (enable ADC bus clock)
02 CCR: CKMODE + PRESC / or ADCSEL for async kernel clock
03 CR.DEEPPWD = 0          (leave deep-power-down)
04 CR.ADVREGEN = 1  then wait ~20 µs (regulator start-up)
05 CR.ADCALDIF = 0/1, CR.ADCAL = 1, poll until ADCAL == 0
06 ISR.ADRDY = 1 (clear), CR.ADEN = 1, poll until ADRDY == 1
ORDER MATTERS

ADEN can only be written to 1 when ADCAL=ADSTP=ADSTART=ADDIS=ADEN=0. Enabling the ADC before calibration finishes is silently ignored. Calibration is only valid after the regulator has started, and single-ended vs differential use separate calibration factors — recalibrate if you change ADCALDIF.

c — register-level clock, power-up and calibration
/* Crude blocking microsecond delay (replace with a timer/DWT in real code). */
static void delay_us(volatile uint32_t n){ while(n--) { for(volatile int i=0;i<20;i++){} } }

void adc1_init_clock_and_calibrate(void)
{
    /* 1. ADC peripheral (bus) clock on AHB2 */
    RCC->AHB2ENR |= RCC_AHB2ENR_ADCEN;
    (void)RCC->AHB2ENR;                 // dummy read: RCC clock-enable settling

    /* 2. Async kernel clock = SYSCLK, common prescaler /4 */
    RCC->CCIPR |= (3U << RCC_CCIPR_ADCSEL_Pos);       // 11 = SYSCLK
    ADC12_COMMON->CCR &= ~ADC_CCR_CKMODE;             // 00 = asynchronous
    ADC12_COMMON->CCR &= ~ADC_CCR_PRESC;
    ADC12_COMMON->CCR |= (0x2U << ADC_CCR_PRESC_Pos);  // 0010 = /4

    /* 3-4. Exit deep-power-down, enable regulator, wait start-up */
    ADC1->CR &= ~ADC_CR_DEEPPWD;
    ADC1->CR |= ADC_CR_ADVREGEN;
    delay_us(20);                                     // t_ADCVREG_STUP

    /* 5. Single-ended calibration (ADEN must be 0 here) */
    ADC1->CR &= ~ADC_CR_ADCALDIF;                     // single-ended factor
    ADC1->CR |= ADC_CR_ADCAL;
    while (ADC1->CR & ADC_CR_ADCAL) { }                 // cleared by HW when done
    // calibration factor is now in ADC1->CALFACT

    /* 6. Enable the ADC and wait until it is ready */
    ADC1->ISR = ADC_ISR_ADRDY;                        // clear (write-1-to-clear)
    ADC1->CR |= ADC_CR_ADEN;
    while (!(ADC1->ISR & ADC_ISR_ADRDY)) { }
    ADC1->ISR = ADC_ISR_ADRDY;
}

03 GPIO analog, channels (SQRx) & sampling (SMPRx)

A conversion is defined by three things: which physical pin is routed to the analog input, how long the sample-and-hold charges (SMPRx), and the sequence of channels to convert (SQRx) at the chosen resolution (CFGR.RES).

GPIO: analog mode + the L4 analog switch (ASCR)

Set the pin to analog mode (MODER = 11) and disable the pull-up/pull-down. On STM32L4/L4+, many ADC-capable pins sit behind a low-leakage analog switch that must be closed by setting the matching bit in GPIOx_ASCR (Analog Switch Control Register). Forgetting ASCR is a classic "why is my reading stuck?" bug.

ChannelTypical pin*ADC(s)
ADC12_IN5PA0ADC1, ADC2
ADC12_IN6PA1ADC1, ADC2
ADC12_IN7PA2ADC1, ADC2
ADC12_IN8PA3ADC1, ADC2
ADC12_IN9PA4ADC1, ADC2
ADC1_IN1PC0ADC1 (IN1)
ADC12_IN15PB0ADC1, ADC2
ADC12_IN16PB1ADC1, ADC2
VREFINTinternalADC1, ch 0
Temp sensorinternalADC1, ch 17
VBAT/3internalADC1, ch 18

*Pin↔channel routing varies by package — always confirm against the "Pin definitions / additional functions" table in DS12023 for your exact part. Internal-channel numbers (0/17/18) are fixed.

Sampling time (SMPRx) and resolution (CFGR.RES)

Each channel gets a 3-bit sampling time. Total conversion time = sampling cycles + successive-approximation cycles (12.5 cycles at 12-bit). High-impedance sources and internal channels need long sampling times.

SMP[2:0]SamplingRES[1:0]Resolution / SAR cycles
0002.5 cyc0012-bit / 12.5
0016.5 cyc0110-bit / 10.5
01012.5 cyc108-bit / 8.5
01124.5 cyc116-bit / 6.5
10047.5 cyc
10192.5 cyc
110247.5 cyc
111640.5 cyc

The STM32L4 sequencer has no separate "scan enable" bit (unlike STM32F1). Scanning is implicit: set the sequence length SQR1.L = N-1 and list the channels in SQ1..SQ16; the ADC walks the list automatically. Use CFGR.DISCEN only for discontinuous (chunked) sequencing.

c — single-channel conversion (PA0 = ADC1_IN5)
uint16_t adc1_read_pa0(void)
{
    /* PA0 analog + close the analog switch (ASCR) */
    RCC->AHB2ENR |= RCC_AHB2ENR_GPIOAEN;
    GPIOA->MODER |= (3U << (0 * 2));       // PA0 = analog (11)
    GPIOA->PUPDR &= ~(3U << (0 * 2));      // no pull
    GPIOA->ASCR  |= GPIO_ASCR_ASC0;        // route analog signal to ADC

    ADC1->CFGR &= ~ADC_CFGR_RES;           // 12-bit (RES = 00)

    /* channel 5 sampling = 47.5 cycles (SMP5 = 100) */
    ADC1->SMPR1 &= ~(0x7U << ADC_SMPR1_SMP5_Pos);
    ADC1->SMPR1 |=  (0x4U << ADC_SMPR1_SMP5_Pos);

    /* sequence: 1 conversion, SQ1 = channel 5 */
    ADC1->SQR1 &= ~ADC_SQR1_L;             // L = 0 -> one conversion
    ADC1->SQR1 &= ~ADC_SQR1_SQ1;
    ADC1->SQR1 |= (5U << ADC_SQR1_SQ1_Pos);

    ADC1->CFGR &= ~ADC_CFGR_CONT;          // single conversion
    ADC1->CFGR &= ~ADC_CFGR_EXTEN;         // software trigger

    ADC1->CR |= ADC_CR_ADSTART;
    while (!(ADC1->ISR & ADC_ISR_EOC)) { }
    return (uint16_t)ADC1->DR;              // reading DR clears EOC
}

04 Internal channels: VREFINT, temperature, VBAT

Three internal analog sources are wired to fixed ADC1 channels. They only produce valid data after their path is enabled in ADC12_COMMON.CCR and sampled long enough. Factory calibration constants in system memory turn raw counts into volts and degrees.

SignalADC1 channelCCR enable bitNotes
VREFINTIN0VREFEN (bit 22)~1.212 V bandgap; used to measure real VDDA
Temp sensorIN17CH17SEL (bit 23)needs long sampling + start-up time
VBAT/3IN18CH18SEL (bit 24)VBAT divided by 3 internally

On the STM32L4 CMSIS/SVD the temp/VBAT enables are named CH17SEL / CH18SEL (the RM refers to them as the temperature-sensor and VBAT enable bits).

CALIBRATION CONSTANTS (STM32L4, measured @ VDDA = 3.0 V)

VREFINT_CAL @ 0x1FFF75AA (30 °C). TS_CAL1 @ 0x1FFF75A8 (30 °C) and TS_CAL2 @ 0x1FFF75CA (130 °C). All are 16-bit right-aligned 12-bit values. Because they were taken at 3.0 V, scale live readings by the true VDDA you compute from VREFINT.

c — enable internal channels, compute VDDA and temperature
/* Factory calibration values in system memory (STM32L4 addresses). */
#define VREFINT_CAL   (*(uint16_t*)0x1FFF75AAUL)   // @3.0V, 30C
#define TS_CAL1       (*(uint16_t*)0x1FFF75A8UL)   // 30C
#define TS_CAL2       (*(uint16_t*)0x1FFF75CAUL)   // 130C

void adc1_enable_internal_paths(void)
{
    ADC12_COMMON->CCR |= ADC_CCR_VREFEN;    // VREFINT  (ch 0)
    ADC12_COMMON->CCR |= ADC_CCR_CH17SEL;   // temp     (ch 17)
    ADC12_COMMON->CCR |= ADC_CCR_CH18SEL;   // VBAT/3   (ch 18)
    // Use SMP = 111 (640.5 cyc) for these channels; the temp sensor
    // also needs its start-up time (~tens of µs) after CH17SEL = 1.
}

/* vref_data / ts_data = raw 12-bit conversions of ch0 / ch17 */
float vdda_millivolts(uint16_t vref_data)
{
    return 3000.0f * (float)VREFINT_CAL / (float)vref_data;
}

float temperature_celsius(uint16_t ts_data, float vdda_mv)
{
    /* rescale the live reading to the 3.0 V calibration reference */
    float ts = (float)ts_data * vdda_mv / 3000.0f;
    return (130.0f - 30.0f) / (float)(TS_CAL2 - TS_CAL1)
         * (ts - (float)TS_CAL1) + 30.0f;
}

05 Hardware oversampling (CFGR2)

The oversampling engine accumulates N consecutive conversions, right-shifts the sum by M bits, and keeps the low 16 bits — all in hardware, no CPU load. It raises effective resolution (each ×4 in ratio adds ~1 ENOB) and averages out noise. Configure it entirely in ADC_CFGR2.

ROVSEbit 0 — enable regular-group oversampling
JOVSEbit 1 — enable injected-group oversampling
OVSR[2:0]bits 4:2 — oversampling ratio, 2× … 256×
OVSS[3:0]bits 8:5 — right bit-shift of the sum, 0 … 8
TROVSbit 9 — triggered mode (one trigger per oversampled sample)
ROVSMbit 10 — regular oversampling mode (continued / resumed)
OVSRRatio NSum max (12-bit)OVSS shift for true resolution
000~13-bit1 → 12-bit avg
001~14-bit1 → 13-bit
01116×~16-bit0 → 16-bit / 4 → 12-bit avg
10164×~18-bit3 → 15-bit
111256×~20-bit4 → 16-bit

Recipe for a true 16-bit result from 12-bit conversions: ratio 256× (OVSR=111) + shift 4 (OVSS=0100). For plain noise-averaging that keeps 12-bit scale, pick a ratio and shift equal in "bits" (e.g. 16× + shift 4).

c — 256× oversampling → 16-bit result in ADC1->DR
void adc1_enable_16bit_oversampling(void)
{
    ADC1->CFGR2 &= ~(ADC_CFGR2_OVSR | ADC_CFGR2_OVSS);
    ADC1->CFGR2 |= (0x7U << ADC_CFGR2_OVSR_Pos);   // 111 = 256x ratio
    ADC1->CFGR2 |= (0x4U << ADC_CFGR2_OVSS_Pos);   // shift right by 4 bits
    ADC1->CFGR2 |= ADC_CFGR2_ROVSE;               // enable regular oversampling
    // One ADSTART now yields ONE 16-bit oversampled sample in DR.
    // Configure CFGR2 while the ADC is enabled but NOT converting.
}
SHIFT vs OVERFLOW

The engine keeps only the 16 least-significant bits after the shift. If OVSS is too small for the ratio you choose, the accumulated sum overflows 16 bits and the result clips. Match the shift to the ratio (see table). Oversampling multiplies conversion time by N — budget it against your sample rate.

06 Single, continuous, scan & circular DMA

For steady multi-channel acquisition you combine a scanned sequence (SQRx) with continuous conversion (CFGR.CONT) and a circular DMA that copies each result out of the single DR before the next conversion overwrites it. Because DR is one register shared by all ranks, DMA is effectively mandatory for multi-channel scanning.

CONTCFGR bit 13 — 0: single sequence per trigger; 1: restart automatically
DMAENCFGR bit 0 — issue a DMA request after every conversion
DMACFGCFGR bit 1 — 0: one-shot DMA; 1: circular DMA (for continuous)
OVRMODCFGR bit 12 — 1: overrun overwrites DR (recommended with circular DMA)

DMAMUX request routing

On the STM32L4+ family DMA1/DMA2 channels are wired to peripherals through DMAMUX1. Program the peripheral's request ID into the DMAMUX channel that maps to your DMA channel. DMAMUX1_Channel0 drives DMA1_Channel1, DMAMUX1_Channel1DMA1_Channel2, and so on (DMA2 continues after DMA1's 7 channels).

PeripheralDMAMUX request ID (DMAREQ_ID)
MEM2MEM0
ADC15
ADC26
DAC1_CH16 (+shift on L4P5/L4Q5 only)

Values from the CubeL4 LL DMAMUX header (LL_DMAMUX_REQ_ADC1 = 5, LL_DMAMUX_REQ_ADC2 = 6). Confirm the full list in RM0432's DMAMUX request-line table for your exact device.

c — 3-channel scan, continuous, circular DMA (register level)
#define NCH 3
static volatile uint16_t adc_buf[NCH];   // DMA target (half-words)

/* Assumes adc1_init_clock_and_calibrate() ran and ADC1 is DISABLED. */
void adc1_start_scan_dma(void)
{
    /* sequence: ch1, ch2, ch17(temp) -> L = 2 (three conversions) */
    ADC1->SQR1 = (2U  << ADC_SQR1_L_Pos)
               | (1U  << ADC_SQR1_SQ1_Pos)
               | (2U  << ADC_SQR1_SQ2_Pos)
               | (17U << ADC_SQR1_SQ3_Pos);

    /* sampling: 247.5 cyc (110) for all three */
    ADC1->SMPR1 = (6U << ADC_SMPR1_SMP1_Pos) | (6U << ADC_SMPR1_SMP2_Pos);
    ADC1->SMPR2 = (6U << ADC_SMPR2_SMP17_Pos);
    ADC12_COMMON->CCR |= ADC_CCR_CH17SEL;   // enable temp path (ch17)

    /* continuous + DMA + circular + overwrite on overrun */
    ADC1->CFGR |= ADC_CFGR_CONT | ADC_CFGR_DMAEN
                | ADC_CFGR_DMACFG | ADC_CFGR_OVRMOD;

    /* ---- DMA1 Channel1 via DMAMUX (request 5 = ADC1) ---- */
    RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN;
    DMAMUX1_Channel0->CCR = 5U;                 // DMAREQ_ID = ADC1

    DMA1_Channel1->CCR   = 0;                   // disable while configuring
    DMA1_Channel1->CPAR  = (uint32_t)&ADC1->DR; // source: ADC data reg
    DMA1_Channel1->CMAR  = (uint32_t)adc_buf;   // dest: RAM buffer
    DMA1_Channel1->CNDTR = NCH;
    DMA1_Channel1->CCR   = DMA_CCR_MINC                  // increment memory
                        | (1U << DMA_CCR_PSIZE_Pos)      // 16-bit peripheral
                        | (1U << DMA_CCR_MSIZE_Pos)      // 16-bit memory
                        | DMA_CCR_CIRC                   // circular
                        | DMA_CCR_TCIE;                  // transfer-complete IRQ
    DMA1_Channel1->CCR  |= DMA_CCR_EN;

    /* enable ADC, then start — it now free-runs into adc_buf[] */
    ADC1->ISR = ADC_ISR_ADRDY;
    ADC1->CR |= ADC_CR_ADEN;
    while (!(ADC1->ISR & ADC_ISR_ADRDY)) { }
    ADC1->CR |= ADC_CR_ADSTART;
}
CIRCULAR DMA CHECKLIST

Set DMACFG=1 (circular) and OVRMOD=1 so an overrun overwrites rather than stalls. Use 16-bit (half-word) transfer sizes on both sides — the meaningful ADC data is 16-bit even in 12-bit mode. Write DMAEN while ADSTART=0; to reconfigure a running stream, clear DMAEN then set it again.

07 HAL variant (STM32Cube)

The STM32Cube HAL wraps the same registers. The handle's Init fields map one-to-one to CFGR/CFGR2 bits, HAL_ADCEx_Calibration_Start performs the ADCAL sequence, and HAL_ADC_Start_DMA wires DR to your buffer. Clock enables, GPIO analog config and the DMA handle link live in the CubeMX-generated HAL_ADC_MspInit.

c — multi-channel + oversampling + circular DMA (HAL)
ADC_HandleTypeDef hadc1;
static uint16_t adc_buf[3];

void MX_ADC1_Init(void)
{
    hadc1.Instance                   = ADC1;
    hadc1.Init.ClockPrescaler        = ADC_CLOCK_ASYNC_DIV4;
    hadc1.Init.Resolution            = ADC_RESOLUTION_12B;
    hadc1.Init.ScanConvMode          = ADC_SCAN_ENABLE;
    hadc1.Init.ContinuousConvMode    = ENABLE;
    hadc1.Init.NbrOfConversion       = 3;
    hadc1.Init.DiscontinuousConvMode = DISABLE;
    hadc1.Init.ExternalTrigConv      = ADC_SOFTWARE_START;
    hadc1.Init.DMAContinuousRequests = ENABLE;              // DMACFG = circular
    hadc1.Init.Overrun               = ADC_OVR_DATA_OVERWRITTEN;

    /* hardware oversampling: 256x, shift 4 -> true 16-bit */
    hadc1.Init.OversamplingMode                = ENABLE;
    hadc1.Init.Oversampling.Ratio              = ADC_OVERSAMPLING_RATIO_256;
    hadc1.Init.Oversampling.RightBitShift      = ADC_RIGHTBITSHIFT_4;
    hadc1.Init.Oversampling.TriggeredMode      = ADC_TRIGGEREDMODE_SINGLE_TRIGGER;
    hadc1.Init.Oversampling.OversamplingStopReset = ADC_REGOVERSAMPLING_CONTINUED_MODE;
    HAL_ADC_Init(&hadc1);

    ADC_ChannelConfTypeDef s = {0};
    s.SingleDiff   = ADC_SINGLE_ENDED;
    s.OffsetNumber = ADC_OFFSET_NONE;
    s.Offset       = 0;

    s.Channel = ADC_CHANNEL_1; s.Rank = ADC_REGULAR_RANK_1;
    s.SamplingTime = ADC_SAMPLETIME_247CYCLES_5;
    HAL_ADC_ConfigChannel(&hadc1, &s);

    s.Channel = ADC_CHANNEL_2; s.Rank = ADC_REGULAR_RANK_2;
    HAL_ADC_ConfigChannel(&hadc1, &s);

    s.Channel = ADC_CHANNEL_TEMPSENSOR; s.Rank = ADC_REGULAR_RANK_3;
    s.SamplingTime = ADC_SAMPLETIME_640CYCLES_5;           // internal ch needs long SMP
    HAL_ADC_ConfigChannel(&hadc1, &s);

    /* calibrate (single-ended) then start the circular DMA stream */
    HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED);
    HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buf, 3);
}

The HAL ScanConvMode = ADC_SCAN_ENABLE plus NbrOfConversion = 3 simply program SQR1.L = 2 and the three SQ ranks — there is no hardware scan bit behind it. HAL_ADC_MspInit (generated by CubeMX) must enable ADCEN, set the analog pins + ASCR, enable the temperature path, and initialise/link the DMA handle for DMA_REQUEST_ADC1.

08 Gotchas & common mistakes

Almost every "the ADC doesn't work" report on STM32L4 traces back to one of these. Check them in order.

SymptomCause / fix
ADRDY never sets, ADEN "won't stick"You set ADEN while another CR command bit was non-zero. ADEN is only accepted when ADCAL=ADSTP=ADSTART=ADDIS=ADEN=0. Also exit DEEPPWD and enable ADVREGEN + wait ~20 µs first.
Readings are noisy / offsetNo calibration, or wrong calibration domain. Run ADCAL after the regulator is up; single-ended and differential need separate calibrations (ADCALDIF).
One specific pin reads garbage / floatsMissing the L4 analog switch: set the pin to analog mode and the matching GPIOx_ASCR bit (ASCn).
Internal channels read wrongEnable the path (VREFEN / CH17SEL / CH18SEL), use a long SMP (247.5–640.5 cyc), and wait the temp-sensor start-up time after enabling CH17SEL.
Multi-channel buffer holds only one value / all identicalOnly one DR exists; you must use DMA. Set DMAEN=1, DMACFG=1 (circular), CONT=1, and a scanned SQR sequence.
DMA stops after first pass / overrun (OVR)Set OVRMOD=1 (overwrite) with circular DMA; verify DMAMUX request ID (ADC1=5, ADC2=6) and that DMAMUX1_ChannelN drives DMA1_Channel(N+1).
Oversampled result clips / saturatesOVSS shift too small for the ratio — the sum overflows 16 bits. Match shift to ratio (256× → shift 4 for true 16-bit).
Reconfig while running has no effectChange SQR/SMPR/CFGR only with ADSTART=0. Use ADSTP to stop a regular conversion; ADDIS to disable the ADC.
EOC/DR behaves oddly with DMAReading DR clears EOC. In DMA mode the DMA reads DR — do not also read it in software.
ADC clock out of range / conversions wrong at high SYSCLKKeep fADC within the DS12023 limit: use CCR.PRESC in async mode or CKMODE=HCLK/2,/4 in sync mode.

Golden path

  • Clock (AHB2 ADCEN + CCR clock) → DEEPPWD=0 → ADVREGEN=1 + wait → ADCAL → ADEN + wait ADRDY.
  • Pins: analog mode + ASCR; sequence via SQR1.L and SQ ranks; long SMP for high-Z and internal channels.
  • Multi-channel = scan + CONT + circular DMA (DMAEN/DMACFG/OVRMOD, DMAMUX ADC1=5).
  • Oversampling in CFGR2: ROVSE + OVSR ratio + matched OVSS shift for the resolution you want.