01 ADC architecture & register map
The STM32L4R5 (Cortex-M4F, RM0432) integrates two independent 12-bit successive-approximation ADCs — ADC1 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.
| Offset | Register | Purpose (key fields) |
|---|---|---|
| 0x00 | ADC_ISR | Status: ADRDY, EOSMP, EOC, EOS, OVR (write-1-to-clear) |
| 0x04 | ADC_IER | Interrupt enables mirroring ISR flags |
| 0x08 | ADC_CR | ADEN, ADDIS, ADSTART, ADSTP, ADCAL, ADCALDIF, ADVREGEN, DEEPPWD |
| 0x0C | ADC_CFGR | RES, CONT, DMAEN, DMACFG, OVRMOD, EXTEN, EXTSEL, ALIGN, DISCEN |
| 0x10 | ADC_CFGR2 | ROVSE, JOVSE, OVSR[2:0], OVSS[3:0], TROVS, ROVSM (oversampling) |
| 0x14 / 0x18 | ADC_SMPR1 / SMPR2 | Sampling time SMP0..SMP9 / SMP10..SMP18 (3 bits each) |
| 0x30..0x3C | ADC_SQR1..SQR4 | Regular sequence length L and channels SQ1..SQ16 (5 bits each) |
| 0x40 | ADC_DR | Regular data register (reading clears EOC) |
| 0xB0 / 0xB4 | ADC_DIFSEL / CALFACT | Differential select / calibration factors |
Common block (offset from 0x5004_0300): CSR 0x00, CCR 0x08, CDR 0x0C.
/* 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 |
|---|---|
| 00 | Asynchronous: RCC_CCIPR.ADCSEL kernel clock ÷ PRESC |
| 01 | HCLK / 1 (synchronous — requires AHB prescaler = 1) |
| 10 | HCLK / 2 (synchronous) |
| 11 | HCLK / 4 (synchronous) |
| ADCSEL[1:0] (RCC_CCIPR) | Async kernel clock |
|---|---|
| 00 | No clock selected |
| 01 | PLLSAI1 "R" output (PLLADC1CLK) |
| 11 | SYSCLK |
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
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.
/* 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.
| Channel | Typical pin* | ADC(s) |
|---|---|---|
| ADC12_IN5 | PA0 | ADC1, ADC2 |
| ADC12_IN6 | PA1 | ADC1, ADC2 |
| ADC12_IN7 | PA2 | ADC1, ADC2 |
| ADC12_IN8 | PA3 | ADC1, ADC2 |
| ADC12_IN9 | PA4 | ADC1, ADC2 |
| ADC1_IN1 | PC0 | ADC1 (IN1) |
| ADC12_IN15 | PB0 | ADC1, ADC2 |
| ADC12_IN16 | PB1 | ADC1, ADC2 |
| VREFINT | internal | ADC1, ch 0 |
| Temp sensor | internal | ADC1, ch 17 |
| VBAT/3 | internal | ADC1, 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] | Sampling | RES[1:0] | Resolution / SAR cycles | |
|---|---|---|---|---|
| 000 | 2.5 cyc | 00 | 12-bit / 12.5 | |
| 001 | 6.5 cyc | 01 | 10-bit / 10.5 | |
| 010 | 12.5 cyc | 10 | 8-bit / 8.5 | |
| 011 | 24.5 cyc | 11 | 6-bit / 6.5 | |
| 100 | 47.5 cyc | |||
| 101 | 92.5 cyc | |||
| 110 | 247.5 cyc | |||
| 111 | 640.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.
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.
| Signal | ADC1 channel | CCR enable bit | Notes |
|---|---|---|---|
| VREFINT | IN0 | VREFEN (bit 22) | ~1.212 V bandgap; used to measure real VDDA |
| Temp sensor | IN17 | CH17SEL (bit 23) | needs long sampling + start-up time |
| VBAT/3 | IN18 | CH18SEL (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).
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.
/* 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.
| OVSR | Ratio N | Sum max (12-bit) | OVSS shift for true resolution |
|---|---|---|---|
| 000 | 2× | ~13-bit | 1 → 12-bit avg |
| 001 | 4× | ~14-bit | 1 → 13-bit |
| 011 | 16× | ~16-bit | 0 → 16-bit / 4 → 12-bit avg |
| 101 | 64× | ~18-bit | 3 → 15-bit |
| 111 | 256× | ~20-bit | 4 → 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).
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.
}
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.
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_Channel1 → DMA1_Channel2, and so on (DMA2 continues after DMA1's 7 channels).
| Peripheral | DMAMUX request ID (DMAREQ_ID) |
|---|---|
| MEM2MEM | 0 |
| ADC1 | 5 |
| ADC2 | 6 |
| DAC1_CH1 | 6 (+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.
#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;
}
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.
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.
| Symptom | Cause / 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 / offset | No 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 / floats | Missing the L4 analog switch: set the pin to analog mode and the matching GPIOx_ASCR bit (ASCn). |
| Internal channels read wrong | Enable 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 identical | Only 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 / saturates | OVSS 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 effect | Change SQR/SMPR/CFGR only with ADSTART=0. Use ADSTP to stop a regular conversion; ADDIS to disable the ADC. |
| EOC/DR behaves oddly with DMA | Reading 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 SYSCLK | Keep 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.