Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.
STM32F407 ADC Deep Dive
Master the 12-bit triple ADC — all registers bit-by-bit, single/continuous/scan modes, sample time tuning, and bare-metal C examples from potentiometer reading to multi-channel scanning.
01 ADC Overview
The STM32F407 contains three independent 12-bit successive-approximation ADCs (ADC1, ADC2, ADC3). Each ADC can independently measure up to 16 external channels and several internal channels. They share a common clock and can be operated in independent, dual, or triple interleaved modes for higher throughput.
| ADC | Base Address | External Ch. | APB Bus | DMA | Notes |
|---|---|---|---|---|---|
| ADC1 | 0x40012000 | 16 (IN0–IN15) | APB2 | DMA2 Ch0 S0/S4 | Has temperature sensor, VREFINT, VBAT |
| ADC2 | 0x40012100 | 16 (IN0–IN15) | APB2 | DMA2 Ch1 S2/S3 | Multi-mode slave |
| ADC3 | 0x40012200 | 16 (IN0–IN15) | APB2 | DMA2 Ch2 S0/S1 | Multi-mode slave |
| ADC_COM | 0x40012300 | — | APB2 | — | ADC_CCR common register |
ADC Channel Map (STM32F407VGT6)
Each ADC channel corresponds to a GPIO pin or an internal signal. The channel number is independent of the GPIO port — you configure the GPIO pin as analog mode (MODER = 11) and route it to the ADC by selecting the channel in SQR or JSQR.
ADC Clock
The ADC clock is derived from the APB2 clock (84 MHz at 168 MHz system clock) via a prescaler set in ADC_CCR.ADCPRE. The ADC clock must not exceed 36 MHz (datasheet limit). At 168 MHz system clock with APB2 at 84 MHz:
| ADCPRE[1:0] | Prescaler | ADC Clock (APB2=84MHz) | Valid? |
|---|---|---|---|
| 00 | ÷2 | 42 MHz | EXCEEDS 36MHz ✗ |
| 01 | ÷4 | 21 MHz | OK ✓ (recommended) |
| 10 | ÷6 | 14 MHz | OK ✓ |
| 11 | ÷8 | 10.5 MHz | OK ✓ |
ADCPRE = 01 (÷4 = 21 MHz) or higher prescaler at 168 MHz.Conversion Pipeline
Every ADC conversion goes through three phases:
Total conversion time = Sampling time + 12 ADC clock cycles. Sampling time is configurable per channel in SMPR1/SMPR2 (3 to 480 cycles). At 21 MHz ADC clock:
- Minimum: 3 + 12 = 15 cycles = 714 ns (2.4 MSPS theoretical max — only for low-impedance sources)
- Typical (28 cycles): 28 + 12 = 40 cycles = 1.9 µs
- Temp sensor / VREFINT need ≥10 µs — use 480 cycles sampling
02 ADC Register Map
All ADC registers are 32-bit. Offsets are from the ADC base address (0x40012000 for ADC1).
| Offset | Register | Description |
|---|---|---|
| 0x00 | ADC_SR | Status register — flags EOC, STRT, OVR, AWD, JEOC |
| 0x04 | ADC_CR1 | Control 1 — resolution, scan, watchdog, interrupt enables |
| 0x08 | ADC_CR2 | Control 2 — ADON, continuous, trigger, alignment, DMA |
| 0x0C | ADC_SMPR1 | Sample time for channels 10–18 |
| 0x10 | ADC_SMPR2 | Sample time for channels 0–9 |
| 0x14 | ADC_JOFR1–4 | Injected channel data offset (0x14–0x20) |
| 0x24 | ADC_HTR | Analog watchdog high threshold |
| 0x28 | ADC_LTR | Analog watchdog low threshold |
| 0x2C | ADC_SQR1 | Regular sequence register 1 (L, SQ13–SQ16) |
| 0x30 | ADC_SQR2 | Regular sequence register 2 (SQ7–SQ12) |
| 0x34 | ADC_SQR3 | Regular sequence register 3 (SQ1–SQ6) |
| 0x38 | ADC_JSQR | Injected sequence register |
| 0x3C–0x48 | ADC_JDR1–4 | Injected channel data registers |
| 0x4C | ADC_DR | Regular data register — holds conversion result |
ADC_SR — Status Register (Offset 0x00)
Bit and field descriptions
| Bit / Field | Description |
|---|---|
| 31 — | Reserved |
| 5 OVR | OVR — Overrun: |
| 4 STRT | STRT — Regular channel start: |
| 3 JSTRT | JSTRT — Injected channel start: |
| 2 JEOC | JEOC — Injected EOC: |
| 1 EOC | EOC — End of Conversion: |
| 0 AWD | AWD — Analog Watchdog: |
| Bit | Name | Access | Description |
|---|---|---|---|
| 5 | OVR | rc_w0 | Overrun — DR was not read before next conversion completed. Data was lost. Write 0 to clear. |
| 4 | STRT | rc_w0 | Regular channel start — Set when regular conversion starts. Write 0 to clear. |
| 3 | JSTRT | rc_w0 | Injected channel start — Set when injected conversion starts. |
| 2 | JEOC | rc_w0 | Injected end of conversion — Set when injected group completes. |
| 1 | EOC | rc_w0 | End of conversion — Set when regular conversion complete. Reading ADC_DR clears this if EOCS=0. |
| 0 | AWD | rc_w0 | Analog watchdog — Set when converted value is outside HTR/LTR thresholds. Write 0 to clear. |
rc_w0). Use ADC1->SR &= ~ADC_SR_OVR to clear the OVR flag. Never use ADC1->SR = 0 as it clears ALL flags simultaneously.ADC_CR1 — Control Register 1 (Offset 0x04)
Bit and field descriptions
| Bit / Field | Description |
|---|---|
| 31 res | Reserved |
| 27 RES1 | RES[1] bit 27 — Resolution MSB: |
| 26 OVRIE | OVRIE (bit 26) — Overrun Interrupt Enable |
| 25 res | Reserved |
| 23 AWDEN | AWDEN — Analog Watchdog enable on regular channels |
| 22 JAWDEN | JAWDEN — Analog Watchdog enable on injected channels |
| 21 res | Reserved |
| 19 DC2 | DISCNUM[2:0] bits 19:16 — Discontinuous mode channel count |
| 18 DC1 | DISCNUM[1] |
| 17 DC0 | DISCNUM[0] |
| 16 res | Reserved |
| 13 JDISCEN | JDISCEN — Discontinuous mode on injected channels |
| 12 DISCEN | DISCEN — Discontinuous mode on regular channels |
| 11 JAUTO | JAUTO — Automatic injected group conversion |
| 10 AWDSGL | AWDSGL — AWD on single channel (use AWDCH to select) |
| 9 res | Reserved |
| 8 SCAN | SCAN — Scan mode: 1=convert all channels in sequence |
| 7 JEOCIE | JEOCIE — Injected end-of-conversion interrupt enable |
| 6 AWDIE | AWDIE — Analog Watchdog interrupt enable |
| 5 EOCIE | EOCIE — Regular EOC interrupt enable |
| 4 AWD4 | AWDCH[4:0] bits 4:0 — Analog Watchdog channel select |
| 3 AWD3 | AWDCH[3] |
| 2 AWD2 | AWDCH[2] |
| 1 AWD1 | AWDCH[1] |
| 0 AWD0 | AWDCH[0] — Selects monitored channel (0–18) |
| Bits | Name | Description |
|---|---|---|
| 27:26 | RES[1:0] | Resolution: 00=12-bit (15 cycles), 01=10-bit (13), 10=8-bit (11), 11=6-bit (9). Fewer bits = faster conversion. |
| 26 | OVRIE | Overrun interrupt enable: 1 = interrupt on OVR flag |
| 23 | AWDEN | Analog watchdog enable on regular channels |
| 22 | JAWDEN | Analog watchdog enable on injected channels |
| 19:16 | DISCNUM[2:0] | Discontinuous mode channel count: Number of channels to convert per trigger (1–8) |
| 13 | JDISCEN | Discontinuous mode for injected channels |
| 12 | DISCEN | Discontinuous mode for regular channels: Each trigger converts DISCNUM+1 channels |
| 11 | JAUTO | Automatic injected group conversion: Injected group converts after regular group |
| 10 | AWDSGL | AWD on single channel: 0=all channels, 1=only AWDCH channel |
| 8 | SCAN | Scan mode: 1 = convert all channels in sequence (SQR1–SQR3) |
| 7 | JEOCIE | Interrupt enable for injected EOC |
| 6 | AWDIE | Analog watchdog interrupt enable |
| 5 | EOCIE | Interrupt enable for regular EOC: Fires ADC IRQ when EOC=1 |
| 4:0 | AWDCH[4:0] | Analog watchdog channel: Which channel (0–18) the AWD monitors (when AWDSGL=1) |
ADC_CR2 — Control Register 2 (Offset 0x08)
Bit and field descriptions
| Bit / Field | Description |
|---|---|
| 31 res | Reserved |
| 30 SWSTART | SWSTART — Software start: |
| 29 EXTEN1 | EXTEN[1:0] bits 29:28 — External trigger enable for regular channels: |
| 28 EXTEN0 | EXTEN[0] |
| 27 EXT3 | EXTSEL[3:0] bits 27:24 — External trigger source for regular channels |
| 26 EXT2 | EXTSEL[2] |
| 25 EXT1 | EXTSEL[1] |
| 24 EXT0 | EXTSEL[0] |
| 23 res | Reserved |
| 22 JSWSTART | JSWSTART — Injected software start |
| 21 JEXTEN1 | JEXTEN[1:0] bits 21:20 — External trigger enable for injected |
| 20 JEXTEN0 | JEXTEN[0] |
| 19 JEXT3 | JEXTSEL[3:0] bits 19:16 — External trigger source for injected |
| 18 JEXT2 | JEXTSEL[2] |
| 17 JEXT1 | JEXTSEL[1] |
| 16 JEXT0 | JEXTSEL[0] |
| 15 res | Reserved |
| 11 ALIGN | ALIGN — Data alignment: |
| 10 EOCS | EOCS — End of conversion selection: |
| 9 DDS | DDS — DMA disable selection: |
| 8 DMA | DMA — DMA enable: |
| 7 res | Reserved |
| 1 CONT | CONT — Continuous mode: |
| 0 ADON | ADON — ADC ON: |
| Bits | Name | Description |
|---|---|---|
| 30 | SWSTART | Software start: Write 1 to start a regular conversion. Self-clears (no need to clear manually). |
| 29:28 | EXTEN[1:0] | External trigger enable: 00=disabled (SW only), 01=rising edge, 10=falling edge, 11=both edges |
| 27:24 | EXTSEL[3:0] | External trigger source: selects TIM/EXTI event to trigger conversion (see reference manual Table 79) |
| 22 | JSWSTART | Injected software start |
| 21:20 | JEXTEN[1:0] | External trigger enable for injected |
| 19:16 | JEXTSEL[3:0] | External trigger for injected channels |
| 11 | ALIGN | Data alignment: 0=right-aligned (typical), 1=left-aligned. Right: result in bits [11:0]. Left: result in bits [15:4]. |
| 10 | EOCS | End of conversion selection: 0=EOC after each sequence, 1=EOC after each channel. Set to 1 for per-channel interrupt. |
| 9 | DDS | DMA disable selection: 0=DMA disabled after last transfer, 1=continuous DMA requests |
| 8 | DMA | DMA enable: 1=DMA requests generated after each conversion |
| 1 | CONT | Continuous conversion mode: 1=automatically restart conversions. 0=single shot (must re-trigger). |
| 0 | ADON | ADC ON/OFF: 1=enable ADC. Must wait ~3 µs stabilization time after first enable before starting conversion. |
SWSTART and ADON in the same write is NOT recommended. First set ADON=1, wait for stabilization, then set SWSTART=1 in a separate write.SMPR1 / SMPR2 — Sample Time Registers
Each channel has an independent 3-bit sample time field. Sample time directly affects accuracy vs. speed: longer sampling allows the hold capacitor to fully charge, critical for high-impedance sources.
| SMP[2:0] | Cycles | Time @ 21MHz | Use Case |
|---|---|---|---|
| 000 | 3 | 143 ns | Very low impedance (<1kΩ), test only |
| 001 | 15 | 714 ns | Low impedance (<10kΩ) |
| 010 | 28 | 1.33 µs | Medium impedance sensors |
| 011 | 56 | 2.67 µs | Potentiometer (10kΩ typical) |
| 100 | 84 | 4.0 µs | High impedance (<100kΩ) |
| 101 | 112 | 5.33 µs | Very high impedance |
| 110 | 144 | 6.86 µs | Noisy signals, averaging needed |
| 111 | 480 | 22.9 µs | Internal channels (Temp/VREFINT) |
Register layout: SMPR2 covers channels 0–9 (3 bits each, bits 29:0). SMPR1 covers channels 10–18 (bits 26:0). Channel N sample time bits = SMPRx[3N+2 : 3N].
/* Set channel 1 sample time to 56 cycles (SMP=011) in SMPR2 */
/* Channel 1 occupies bits 5:3 of SMPR2 */
ADC1->SMPR2 &= ~(7 << (3 * 1)); /* clear bits 5:3 */
ADC1->SMPR2 |= (0x3 << (3 * 1)); /* 011 = 56 cycles */
/* Set channel 16 (Temp sensor) to 480 cycles (SMP=111) in SMPR1 */
/* Channel 16 occupies bits 20:18 of SMPR1 */
ADC1->SMPR1 &= ~(7 << (3 * (16-10))); /* clear bits 20:18 */
ADC1->SMPR1 |= (0x7 << (3 * (16-10))); /* 111 = 480 cycles */
SQR1 / SQR2 / SQR3 — Regular Sequence Registers
The regular sequence defines which channels are converted and in what order. You can have up to 16 conversions in one sequence. The total number of conversions is set in SQR1.L[3:0] (0 = 1 conversion, 15 = 16 conversions).
| Register | Bit Field | Sequence Position | Bits Per Entry |
|---|---|---|---|
| SQR3 | [4:0] | SQ1 (1st conversion) | 5-bit channel number |
| SQR3 | [9:5] | SQ2 | 5-bit channel number |
| SQR3 | [14:10] | SQ3 | 5-bit channel number |
| SQR3 | [19:15] | SQ4 | 5-bit channel number |
| SQR3 | [24:20] | SQ5 | 5-bit channel number |
| SQR3 | [29:25] | SQ6 | 5-bit channel number |
| SQR2 | [4:0] | SQ7 | 5-bit channel number |
| SQR2 | … | SQ8–SQ12 | 5-bit channel number |
| SQR1 | [4:0] | SQ13 | 5-bit channel number |
| SQR1 | … | SQ14–SQ16 | 5-bit channel number |
| SQR1 | [23:20] | L[3:0] | Sequence length: 0=1conv, 1=2conv, …, 15=16conv |
/* Configure sequence: only channel 1 (PA1), length=1 */
ADC1->SQR3 = (1 << 0); /* SQ1 = channel 1 */
ADC1->SQR1 = (0 << 20); /* L = 0 → 1 conversion */
/* Configure 3-channel scan: CH1, CH4, CH8 */
ADC1->SQR3 = (1<<0) | (4<<5) | (8<<10); /* SQ1=CH1, SQ2=CH4, SQ3=CH8 */
ADC1->SQR2 = 0;
ADC1->SQR1 = (2 << 20); /* L = 2 → 3 conversions */
ADC_DR — Data Register (Offset 0x4C)
The 16-bit data register holds the latest conversion result. Bits 31:16 are used only in ADC1 dual mode (holding ADC2 result). In independent mode, only bits 15:0 are used.
- Right-aligned (ALIGN=0, default): Result in bits [11:0]. Range 0–4095.
voltage = (DR / 4095.0) * VREF - Left-aligned (ALIGN=1): Result in bits [15:4]. Range 0–65520. Useful when you only need 8-bit precision — just read the high byte.
Live conversion result visualizer:
03 ADC_CCR — Common Control Register
ADC_CCR is located at 0x40012304 (ADC common base + 0x04) and controls settings shared across all three ADCs.
| Bits | Name | Description |
|---|---|---|
| 23 | TSVREFE | Temperature sensor and VREFINT enable: Must be set before reading CH16 or CH17 |
| 22 | VBATE | VBAT enable: Must be set before reading CH18 (VBAT/4). Disable when not in use. |
| 17:16 | ADCPRE[1:0] | ADC prescaler: 00=÷2, 01=÷4 (21MHz), 10=÷6 (14MHz), 11=÷8 (10.5MHz) |
| 15:14 | DMA[1:0] | DMA mode for multi-ADC: Used in dual/triple ADC mode |
| 13 | DDS | DMA disable selection (multi-mode) |
| 11:8 | DELAY[3:0] | Delay between two sampling phases (multi-mode) |
| 4:0 | MULTI[4:0] | Multi ADC mode selection: 00000=independent mode |
#define ADC_CCR (*(volatile uint32_t *)0x40012304)
/* Set ADC prescaler to /4 (21MHz) for independent mode */
ADC_CCR &= ~(0x3 << 16); /* clear ADCPRE */
ADC_CCR |= (0x1 << 16); /* 01 = /4 */
/* Enable temperature sensor + VREFINT */
ADC_CCR |= (1 << 23); /* TSVREFE */
04 Single Conversion Mode
In single mode, each conversion must be triggered manually. The ADC converts one channel and sets EOC, then stops. This is the simplest mode — ideal for on-demand measurements.
#include <stdint.h>
/* ── Register definitions ── */
#define RCC_BASE 0x40023800
#define GPIOA_BASE 0x40020000
#define ADC1_BASE 0x40012000
#define ADC_CCR (*(volatile uint32_t *)0x40012304)
#define RCC_AHB1ENR (*(volatile uint32_t *)(RCC_BASE+0x30))
#define RCC_APB2ENR (*(volatile uint32_t *)(RCC_BASE+0x44))
#define GPIOA_MODER (*(volatile uint32_t *)(GPIOA_BASE+0x00))
typedef struct {
volatile uint32_t SR, CR1, CR2;
volatile uint32_t SMPR1, SMPR2;
volatile uint32_t JOFR1, JOFR2, JOFR3, JOFR4;
volatile uint32_t HTR, LTR;
volatile uint32_t SQR1, SQR2, SQR3;
volatile uint32_t JSQR;
volatile uint32_t JDR1, JDR2, JDR3, JDR4;
volatile uint32_t DR;
} ADC_TypeDef;
#define ADC1 ((ADC_TypeDef *)ADC1_BASE)
/* ── Initialize ADC1 for single conversion on PA1 (IN1) ── */
void ADC1_Init_Single(void) {
/* 1. Enable clocks */
RCC_AHB1ENR |= (1 << 0); /* GPIOAEN */
RCC_APB2ENR |= (1 << 8); /* ADC1EN */
/* 2. PA1 → Analog mode (MODER[3:2] = 11) */
GPIOA_MODER |= (0x3 << (1*2)); /* bits 3:2 = 11 */
/* 3. ADC clock = APB2/4 = 21 MHz */
ADC_CCR &= ~(3 << 16);
ADC_CCR |= (1 << 16); /* ADCPRE = 01 = /4 */
/* 4. ADC_CR1: 12-bit resolution, no scan */
ADC1->CR1 = 0; /* RES=00 (12-bit), SCAN=0 */
/* 5. ADC_CR2: single, SW trigger, right align */
ADC1->CR2 = 0; /* CONT=0, ALIGN=0, no DMA */
/* 6. Sample time for channel 1 = 56 cycles (SMP=011) */
ADC1->SMPR2 &= ~(7 << 3);
ADC1->SMPR2 |= (3 << 3); /* bits 5:3 = 011 */
/* 7. Sequence: length=1, channel 1 first */
ADC1->SQR3 = 1; /* SQ1 = channel 1 */
ADC1->SQR1 = 0; /* L = 0 → 1 conversion */
/* 8. Enable ADC */
ADC1->CR2 |= (1 << 0); /* ADON = 1 */
/* 9. Wait >3µs stabilization (at 168MHz, ~504 cycles) */
for (volatile int i = 0; i < 600; i++);
}
/* ── Perform one ADC conversion ── */
uint16_t ADC1_Read(void) {
/* Start conversion by setting SWSTART */
ADC1->CR2 |= (1 << 30); /* SWSTART = 1 */
/* Wait for EOC (End of Conversion) */
while (!(ADC1->SR & (1 << 1))); /* poll EOC bit */
return (uint16_t)(ADC1->DR & 0x0FFF); /* 12-bit result */
}
int main(void) {
ADC1_Init_Single();
while (1) {
uint16_t raw = ADC1_Read();
/* raw is 0–4095; convert to voltage: */
/* float v = (raw / 4095.0f) * 3.3f; */
/* In integer: millivolts = (raw * 3300) / 4095 */
uint32_t mV = (uint32_t)raw * 3300 / 4095;
(void)mV; /* use mV as needed */
}
}
05 Continuous Conversion Mode
Set CONT=1 in CR2. After the first SWSTART, the ADC automatically restarts and continuously overwrites ADC_DR. You simply read the latest value at any time. No need to re-trigger.
ADC_DR before the next conversion completes, the overrun flag (SR.OVR) is set and new data is lost. In software polling this is fine (you always read after checking), but with DMA you must ensure the buffer is larger than the conversion rate.void ADC1_Init_Continuous(void) {
RCC_AHB1ENR |= (1 << 0);
RCC_APB2ENR |= (1 << 8);
GPIOA_MODER |= (0x3 << (1*2)); /* PA1 analog */
ADC_CCR &= ~(3 << 16);
ADC_CCR |= (1 << 16); /* /4 = 21MHz */
ADC1->CR1 = 0;
ADC1->CR2 = (1 << 1); /* CONT = 1 */
ADC1->SMPR2 &= ~(7 << 3);
ADC1->SMPR2 |= (3 << 3); /* 56 cycles */
ADC1->SQR3 = 1;
ADC1->SQR1 = 0;
/* Enable ADC */
ADC1->CR2 |= (1 << 0); /* ADON */
for (volatile int i=0; i<600; i++);
/* Start first conversion */
ADC1->CR2 |= (1 << 30); /* SWSTART */
}
/* Non-blocking read — just read the latest result */
uint16_t ADC1_Read_Continuous(void) {
return (uint16_t)(ADC1->DR & 0x0FFF);
/* EOC is cleared automatically when DR is read */
}
06 Scan Mode — Multiple Channels
Scan mode converts all channels defined in the regular sequence (SQR1–SQR3) one after another. In scan mode without DMA, you must use EOCS=1 (per-channel EOC) so you can read each channel result from ADC_DR before the next one overwrites it. With DMA (preferred), results are automatically transferred to a buffer.
/* ── Scan mode: CH1 (PA1), CH4 (PA4), CH8 (PB0) ── */
/* Results stored in adc_buf[] via DMA2 Stream 0 */
#define DMA2_BASE 0x40026400
#define DMA2_S0CR (*(volatile uint32_t *)(DMA2_BASE+0x10))
#define DMA2_S0NDTR (*(volatile uint32_t *)(DMA2_BASE+0x14))
#define DMA2_S0PAR (*(volatile uint32_t *)(DMA2_BASE+0x18))
#define DMA2_S0M0AR (*(volatile uint32_t *)(DMA2_BASE+0x1C))
#define RCC_AHB1ENR (*(volatile uint32_t *)0x40023830)
volatile uint16_t adc_buf[3]; /* one entry per channel */
void ADC1_Scan_DMA_Init(void) {
/* 1. Clocks: GPIOA, GPIOB, DMA2, ADC1 */
volatile uint32_t *rcc_ahb1 = (volatile uint32_t *)(0x40023800+0x30);
volatile uint32_t *rcc_apb2 = (volatile uint32_t *)(0x40023800+0x44);
*rcc_ahb1 |= (1<<0)|(1<<1)|(1<<22); /* GPIOAEN|GPIOBEN|DMA2EN */
*rcc_apb2 |= (1<<8); /* ADC1EN */
/* 2. GPIO analog mode */
volatile uint32_t *GPIOA_MODER = (volatile uint32_t *)0x40020000;
volatile uint32_t *GPIOB_MODER = (volatile uint32_t *)0x40020400;
*GPIOA_MODER |= (3<<2)|(3<<8); /* PA1, PA4 analog */
*GPIOB_MODER |= (3<<0); /* PB0 analog */
/* 3. ADC clock /4 */
ADC_CCR &= ~(3<<16); ADC_CCR |= (1<<16);
/* 4. ADC1 CR1: SCAN=1 */
ADC1->CR1 = (1<<8); /* SCAN bit */
/* 5. ADC1 CR2: CONT=1, DMA=1, DDS=1 */
ADC1->CR2 = (1<<1)|(1<<8)|(1<<9);
/* CONT DMA DDS */
/* 6. Sample times: CH1 (56cy), CH4 (56cy), CH8 (56cy) */
ADC1->SMPR2 = (3<<3)|(3<<12)|(3<<24);
/* CH1 CH4 CH8 */
/* 7. Sequence: SQ1=CH1, SQ2=CH4, SQ3=CH8, L=3 */
ADC1->SQR3 = (1<<0)|(4<<5)|(8<<10);
ADC1->SQR2 = 0;
ADC1->SQR1 = (2<<20); /* L = 2 → 3 conversions */
/* 8. Configure DMA2 Stream 0 Channel 0 */
DMA2_S0CR = 0; /* disable first */
while (DMA2_S0CR & 1); /* wait disabled */
DMA2_S0PAR = (uint32_t)&(ADC1->DR); /* peripheral: ADC_DR */
DMA2_S0M0AR = (uint32_t)adc_buf; /* memory: our buffer */
DMA2_S0NDTR = 3; /* 3 transfers */
DMA2_S0CR =
(0<<25) | /* CHSEL = 0 (ADC1) */
(1<<16) | /* PL = 01 (medium priority) */
(1<<13) | /* MSIZE = 01 (16-bit memory) */
(1<<11) | /* PSIZE = 01 (16-bit peripheral) */
(1<<10) | /* MINC = 1 (memory increment) */
(1<<8) | /* CIRC = 1 (circular mode) */
(0<<6) | /* DIR = 0 (periph→memory) */
(1<<0); /* EN = 1 */
/* 9. Enable ADC and start */
ADC1->CR2 |= (1<<0); /* ADON */
for (volatile int i=0; i<600; i++);
ADC1->CR2 |= (1<<30); /* SWSTART */
}
int main(void) {
ADC1_Scan_DMA_Init();
while (1) {
/* adc_buf[0] = CH1 (PA1), [1] = CH4 (PA4), [2] = CH8 (PB0) */
uint16_t ch1 = adc_buf[0];
uint16_t ch4 = adc_buf[1];
uint16_t ch8 = adc_buf[2];
(void)ch1; (void)ch4; (void)ch8;
/* process results... */
}
}
07 ADC Interrupt (EOCIE)
Set EOCIE=1 in CR1 to fire an interrupt whenever EOC is set. All three ADCs share IRQ 18 (ADC_IRQn). In the interrupt handler, check which ADC triggered it, read the data, and optionally re-trigger.
#define NVIC_ISER0 (*(volatile uint32_t *)0xE000E100)
volatile uint16_t latest_adc = 0;
void ADC1_Init_IRQ(void) {
RCC_AHB1ENR |= (1<<0);
RCC_APB2ENR |= (1<<8);
GPIOA_MODER |= (3<<2); /* PA1 analog */
ADC_CCR &= ~(3<<16); ADC_CCR |= (1<<16);
ADC1->CR1 = (1<<5); /* EOCIE = 1 */
ADC1->CR2 = (1<<10); /* EOCS = 1 (EOC after each single ch) */
ADC1->SMPR2 = (3<<3); /* CH1, 56 cycles */
ADC1->SQR3 = 1;
ADC1->SQR1 = 0;
/* Enable ADC IRQ 18 in NVIC */
NVIC_ISER0 |= (1<<18);
ADC1->CR2 |= (1<<0); /* ADON */
for (volatile int i=0; i<600; i++);
}
/* Trigger a new conversion from main code */
void ADC1_StartConversion(void) {
ADC1->CR2 |= (1<<30); /* SWSTART */
}
/* ADC interrupt handler — all 3 ADCs share this */
void ADC_IRQHandler(void) {
if (ADC1->SR & (1<<1)) { /* EOC set? */
latest_adc = (uint16_t)(ADC1->DR & 0xFFF);
/* Reading DR clears EOC automatically (EOCS=1 mode) */
}
if (ADC1->SR & (1<<5)) { /* OVR = overrun? */
ADC1->SR &= ~(1<<5); /* clear OVR (write 0) */
}
}
08 ADC Timing Calculator
ADC Conversion Time Calculator
09 Complete Potentiometer Example
Connect a 10kΩ potentiometer to PA1 (ADC1_IN1). The wiper output goes to PA1, one end to 3.3V, other end to GND. The Discovery board has no built-in potentiometer, so this requires an external connection.
#include <stdint.h>
/* ======================================================
Bare-metal STM32F407 Potentiometer Reader
PA1 → ADC1_IN1 → 12-bit value → mapped to 0–100%
USART2 (PA2/PA3, 115200 baud) for output
====================================================== */
/* ── Peripheral base addresses ── */
#define RCC_BASE 0x40023800U
#define GPIOA_BASE 0x40020000U
#define ADC1_BASE 0x40012000U
#define ADC_CCR_ADDR 0x40012304U
#define USART2_BASE 0x40004400U
/* ── RCC registers ── */
#define RCC_AHB1ENR (*(volatile uint32_t *)(RCC_BASE+0x30))
#define RCC_APB1ENR (*(volatile uint32_t *)(RCC_BASE+0x40))
#define RCC_APB2ENR (*(volatile uint32_t *)(RCC_BASE+0x44))
/* ── GPIOA registers ── */
#define GPIOA_MODER (*(volatile uint32_t *)(GPIOA_BASE+0x00))
#define GPIOA_AFRL (*(volatile uint32_t *)(GPIOA_BASE+0x20))
/* ── ADC1 registers ── */
#define ADC1_SR (*(volatile uint32_t *)(ADC1_BASE+0x00))
#define ADC1_CR1 (*(volatile uint32_t *)(ADC1_BASE+0x04))
#define ADC1_CR2 (*(volatile uint32_t *)(ADC1_BASE+0x08))
#define ADC1_SMPR2 (*(volatile uint32_t *)(ADC1_BASE+0x10))
#define ADC1_SQR1 (*(volatile uint32_t *)(ADC1_BASE+0x2C))
#define ADC1_SQR3 (*(volatile uint32_t *)(ADC1_BASE+0x34))
#define ADC1_DR (*(volatile uint32_t *)(ADC1_BASE+0x4C))
#define ADC_CCR (*(volatile uint32_t *)ADC_CCR_ADDR)
/* ── USART2 registers ── */
#define USART2_SR (*(volatile uint32_t *)(USART2_BASE+0x00))
#define USART2_DR (*(volatile uint32_t *)(USART2_BASE+0x04))
#define USART2_BRR (*(volatile uint32_t *)(USART2_BASE+0x08))
#define USART2_CR1 (*(volatile uint32_t *)(USART2_BASE+0x0C))
/* ─────────────────────────────────────────────────────
Simple delay using NOP loop (not cycle-accurate)
───────────────────────────────────────────────────── */
static void delay_ms(uint32_t ms) {
while (ms--) for (volatile int i=0; i<16800; i++);
}
/* ─────────────────────────────────────────────────────
USART2 init — PA2 (TX), PA3 (RX), 115200, 8N1
APB1 clock = 42 MHz → BRR = 42000000/115200 ≈ 364
───────────────────────────────────────────────────── */
void USART2_Init(void) {
RCC_AHB1ENR |= (1<<0); /* GPIOAEN */
RCC_APB1ENR |= (1<<17); /* USART2EN */
/* PA2, PA3 → AF7 (USART2) */
GPIOA_MODER &= ~((3<<4)|(3<<6));
GPIOA_MODER |= ((2<<4)|(2<<6)); /* Alternate function */
GPIOA_AFRL &= ~((0xF<<8)|(0xF<<12));
GPIOA_AFRL |= ((7<<8)|(7<<12)); /* AF7 */
USART2_BRR = 364; /* 42MHz / 115200 ≈ 364 */
USART2_CR1 = (1<<13)|(1<<3)|(1<<2); /* UE|TE|RE */
}
void USART2_SendChar(char c) {
while (!(USART2_SR & (1<<7))); /* wait TXE */
USART2_DR = (uint8_t)c;
}
void USART2_Print(const char *s) {
while (*s) USART2_SendChar(*s++);
}
/* Print unsigned integer */
void print_uint(uint32_t val) {
char buf[12]; int i = 11;
buf[i] = '\0';
if (val == 0) { USART2_SendChar('0'); return; }
while (val > 0 && i > 0) { buf[--i] = '0' + (val % 10); val /= 10; }
USART2_Print(&buf[i]);
}
/* ─────────────────────────────────────────────────────
ADC1 init — IN1 (PA1), single mode, SW trigger
───────────────────────────────────────────────────── */
void ADC1_Init(void) {
RCC_AHB1ENR |= (1<<0); /* GPIOAEN */
RCC_APB2ENR |= (1<<8); /* ADC1EN */
/* PA1 → analog (MODER[3:2] = 11) */
GPIOA_MODER |= (3<<2);
/* ADC clock = APB2/4 = 21MHz */
ADC_CCR &= ~(3<<16);
ADC_CCR |= (1<<16);
ADC1_CR1 = 0; /* 12-bit, no scan, no interrupts */
ADC1_CR2 = 0; /* single, SW trigger, right align */
/* Channel 1 sample time: 56 cycles */
ADC1_SMPR2 = (3<<3); /* SMP1[2:0] = 011 */
/* Sequence: 1 conversion of channel 1 */
ADC1_SQR3 = 1; /* SQ1 = channel 1 */
ADC1_SQR1 = 0; /* L = 0 (1 conversion) */
/* Enable ADC and wait for stabilization */
ADC1_CR2 |= (1<<0); /* ADON = 1 */
for (volatile int i=0; i<600; i++); /* ~3µs @ 168MHz */
}
uint16_t ADC1_ReadChannel1(void) {
ADC1_CR2 |= (1<<30); /* SWSTART */
while (!(ADC1_SR & (1<<1))); /* wait EOC */
return (uint16_t)(ADC1_DR & 0xFFF);
}
/* Simple software averaging for noise reduction */
uint16_t ADC1_ReadAveraged(uint8_t samples) {
uint32_t sum = 0;
for (uint8_t i=0; i<samples; i++) sum += ADC1_ReadChannel1();
return (uint16_t)(sum / samples);
}
/* ── Main ── */
int main(void) {
USART2_Init();
ADC1_Init();
USART2_Print("\r\n--- STM32F407 Potentiometer ADC ---\r\n");
while (1) {
uint16_t raw = ADC1_ReadAveraged(8); /* average 8 samples */
/* Convert to millivolts (VREF = 3300mV) */
uint32_t mV = (uint32_t)raw * 3300 / 4095;
/* Convert to percentage (0–100%) */
uint32_t pct = (uint32_t)raw * 100 / 4095;
USART2_Print("Raw: "); print_uint(raw);
USART2_Print(" Voltage: "); print_uint(mV); USART2_Print(" mV");
USART2_Print(" Position: "); print_uint(pct); USART2_Print("%\r\n");
delay_ms(200); /* print at 5 Hz */
}
}
10 Multi-Channel Scan Example
Read 4 channels (CH0/PA0, CH1/PA1, CH4/PA4, CH8/PB0) with DMA in circular mode. Each scan cycle automatically fills the 4-entry buffer, ready to read at any time.
/* ── 4-channel ADC scan with DMA circular buffer ──
Channels: 0→PA0, 1→PA1, 4→PA4, 8→PB0
Buffer: adc_results[0..3] always up-to-date
─────────────────────────────────────────────── */
volatile uint16_t adc_results[4]; /* DMA destination */
static void gpio_analog(volatile uint32_t *moder, uint8_t pin) {
*moder |= (3U << (pin*2));
}
void ADC1_4CH_Init(void) {
volatile uint32_t *rcc_ahb1 = (volatile uint32_t *)(0x40023830);
volatile uint32_t *rcc_apb2 = (volatile uint32_t *)(0x40023844);
volatile uint32_t *GPIOA_M = (volatile uint32_t *)(0x40020000);
volatile uint32_t *GPIOB_M = (volatile uint32_t *)(0x40020400);
*rcc_ahb1 |= (1<<0)|(1<<1)|(1<<22); /* GPIOA+B+DMA2 */
*rcc_apb2 |= (1<<8); /* ADC1 */
gpio_analog(GPIOA_M, 0); /* PA0 = CH0 */
gpio_analog(GPIOA_M, 1); /* PA1 = CH1 */
gpio_analog(GPIOA_M, 4); /* PA4 = CH4 */
gpio_analog(GPIOB_M, 0); /* PB0 = CH8 */
/* ADC common clock /4 = 21 MHz */
ADC_CCR = (1<<16);
/* CR1: SCAN mode */
ADC1_CR1 = (1<<8);
/* CR2: CONT + DMA + DDS */
ADC1_CR2 = (1<<1)|(1<<8)|(1<<9);
/* Sample times (56 cycles for all) */
ADC1_SMPR2 = (3<<0)|(3<<3)|(3<<12)|(3<<24);
/* CH0 CH1 CH4 CH8 */
/* Sequence: SQ1=CH0, SQ2=CH1, SQ3=CH4, SQ4=CH8, L=4 */
ADC1_SQR3 = (0<<0)|(1<<5)|(4<<10)|(8<<15);
ADC1_SQR1 = (3<<20); /* L=3 → 4 conversions */
/* DMA2 Stream0 Ch0: ADC1_DR → adc_results[], 4 transfers, circular */
volatile uint32_t *DMA2_S0CR = (volatile uint32_t *)(0x40026410);
volatile uint32_t *DMA2_S0NDTR = (volatile uint32_t *)(0x40026414);
volatile uint32_t *DMA2_S0PAR = (volatile uint32_t *)(0x40026418);
volatile uint32_t *DMA2_S0M0AR = (volatile uint32_t *)(0x4002641C);
*DMA2_S0CR = 0; while(*DMA2_S0CR & 1);
*DMA2_S0PAR = 0x4001204C; /* ADC1_DR */
*DMA2_S0M0AR = (uint32_t)adc_results;
*DMA2_S0NDTR = 4;
*DMA2_S0CR = (0<<25)|(1<<13)|(1<<11)|(1<<10)|(1<<8)|(1<<0);
/* CHSEL=0 | MSIZE=16bit | PSIZE=16bit | MINC | CIRC | EN */
ADC1_CR2 |= (1<<0); /* ADON */
for (volatile int i=0;i<600;i++);
ADC1_CR2 |= (1<<30); /* SWSTART */
}
int main(void) {
ADC1_4CH_Init();
while (1) {
/* adc_results[] is always current — DMA keeps it updated */
uint16_t val0 = adc_results[0]; /* PA0 */
uint16_t val1 = adc_results[1]; /* PA1 */
uint16_t val4 = adc_results[2]; /* PA4 */
uint16_t val8 = adc_results[3]; /* PB0 */
(void)val0; (void)val1; (void)val4; (void)val8;
}
}
11 Injected Channels
Injected channels are a high-priority conversion group that can interrupt an ongoing regular sequence. They are useful for safety-critical measurements (e.g., motor current sensing that must occur at a precise PWM phase).
- Up to 4 injected channels in the JSQR register
- Results go into separate JDR1–JDR4 registers (no overwrite risk)
- Can be triggered by an external event (
JEXTSEL/JEXTEN) for precise synchronization - Can auto-trigger after regular sequence completes (
JAUTO=1) - JEOC flag / JEOCIE interrupt separate from regular EOC
/* Configure injected channel — CH2 (PA2), triggered by TIM1_CC4 */
/* JEXTSEL = 0100 (TIM1_CC4), JEXTEN = 01 (rising edge) */
volatile uint32_t *ADC1_CR2 = (volatile uint32_t *)(0x40012008);
volatile uint32_t *ADC1_JSQR = (volatile uint32_t *)(0x40012038);
volatile uint32_t *ADC1_JDR1 = (volatile uint32_t *)(0x4001203C);
/* Set injected sequence: 1 channel = CH2 */
*ADC1_JSQR = (0<<20) | (2<<15);
/* JL=0 JSQ4=CH2 (only JSQ4 is used when JL=0) */
/* Enable external trigger: TIM1_CC4 rising edge */
*ADC1_CR2 &= ~((0xF<<16) | (0x3<<20));
*ADC1_CR2 |= ((0x4<<16) | (0x1<<20));
/* JEXTSEL=TIM1_CC4 JEXTEN=rising */
/* In the JEOC interrupt: */
void ADC_IRQHandler(void) {
volatile uint32_t *sr = (volatile uint32_t *)(0x40012000);
if (*sr & (1<<2)) { /* JEOC */
uint16_t current = (uint16_t)(*ADC1_JDR1 & 0xFFF);
*sr &= ~(1<<2); /* clear JEOC */
(void)current;
}
}
12 Analog Watchdog
The analog watchdog monitors a converted result against HTR (high threshold) and LTR (low threshold) registers. If the result falls outside [LTR, HTR], the AWD flag in ADC_SR is set and an interrupt fires (if AWDIE=1). This is useful for battery monitoring, temperature alerting, etc.
/* ── Analog Watchdog: alert when PA1 voltage < 1.0V or > 2.5V ──
LTR = (1.0V / 3.3V) * 4095 ≈ 1241
HTR = (2.5V / 3.3V) * 4095 ≈ 3102
─────────────────────────────────────────────────────────────── */
volatile uint32_t *ADC1_HTR = (volatile uint32_t *)(0x40012024);
volatile uint32_t *ADC1_LTR = (volatile uint32_t *)(0x40012028);
volatile uint32_t *ADC1_CR1 = (volatile uint32_t *)(0x40012004);
*ADC1_HTR = 3102; /* upper limit: 2.5V */
*ADC1_LTR = 1241; /* lower limit: 1.0V */
/* CR1: AWDEN=1 (watch regular ch), AWDSGL=1 (watch single ch),
AWDIE=1 (interrupt), AWDCH=1 (watch channel 1) */
*ADC1_CR1 |= (1<<23) | /* AWDEN */
(1<<10) | /* AWDSGL */
(1<<6) | /* AWDIE */
(1<<0); /* AWDCH = channel 1 */
void ADC_IRQHandler(void) {
volatile uint32_t *sr = (volatile uint32_t *)(0x40012000);
if (*sr & (1<<0)) { /* AWD flag */
/* Voltage out of range! Take action */
*sr &= ~(1<<0); /* clear AWD (write 0) */
}
}
13 Tips, Pitfalls & Best Practices
- Forgetting ADON stabilization: After setting
ADON=1, the ADC needs ~3 µs to stabilize. Running SWSTART immediately gives wrong results. Always add a short delay. - ADC clock exceeding 36 MHz: With APB2=84MHz, using ADCPRE=÷2 gives 42MHz — this violates the datasheet limit. Use ÷4 or higher.
- OVR flag in continuous+scan mode without DMA: If CPU is slow to read, data is lost. Always pair scan mode with DMA.
- SMPR too short for high-impedance sources: A 10kΩ potentiometer requires at least 56 cycles. For sources >100kΩ use an op-amp buffer or increase sample time to 144–480.
- Forgetting to set GPIO to analog mode: If the GPIO MODER bits are not
11(analog), the ADC reads noise. Analog mode also disables the digital input buffer, reducing power. - SWSTART doesn’t require re-clearing: It’s a self-clearing bit. You don’t need to manually clear it after writing 1.
- Internal channels need TSVREFE: Temperature sensor (CH16) and VREFINT (CH17) are disabled by default. Set
ADC_CCR.TSVREFE=1before reading them. - ADC_SR clearing direction:
rc_w0means write-0-to-clear. UseSR &= ~flag, NOTSR = 0orSR |= flag.
- Use DMA for multi-channel: Interrupt-based or polled scan mode risks overrun. DMA with circular buffer is always the right choice for >1 channel.
- Software averaging: For noisy signals, average 8–16 samples in firmware. Reduces noise by √N (√8 ≈ 2.8 bits of effective resolution improvement).
- Oversampling + decimation: For 16-bit effective resolution: sample 256×, sum, shift right 4 bits. Works well for slow signals.
- Left-align for 8-bit precision: If only 8 bits needed, set
ALIGN=1and read the upper byte of ADC_DR — saves a shift operation. - Calibrate against VREFINT: VREFINT is 1.21V (typical). Read CH17, compute actual VDDA:
VDDA = 1.21 * 4095 / vrefint_raw. Use VDDA as your reference for accurate voltage conversion.
C — VDDA calibration using VREFINT
/* Read VREFINT (1.21V nominal) on CH17 to calibrate VDDA */
volatile uint32_t *ADC_CCR_R = (volatile uint32_t *)(0x40012304);
*ADC_CCR_R |= (1<<23); /* TSVREFE — enable VREFINT */
/* Configure CH17, sample time 480 cycles (required) */
ADC1_SMPR1 &= ~(7<<21); ADC1_SMPR1 |= (7<<21); /* SMP17=480 */
ADC1_SQR3 = 17; /* measure channel 17 */
ADC1_CR2 |= (1<<30); /* trigger */
while(!(ADC1_SR & (1<<1)));
uint16_t vrefint_raw = (uint16_t)(ADC1_DR & 0xFFF);
/* VDDA = 1210mV * 4095 / vrefint_raw (result in mV) */
uint32_t vdda_mV = (uint32_t)1210 * 4095 / vrefint_raw;
/* Now accurate voltage from any channel: */
/* voltage_mV = (uint32_t)raw * vdda_mV / 4095; */
T°C = ((V25 – Vsense) / Avg_Slope) + 25 where V25 ≈ 0.76V, Avg_Slope ≈ 2.5 mV/°C per the datasheet. Read CH16 with at least 480-cycle sample time and TSVREFE=1.STM32F407 Tutorial Series — Tutorial 16: ADC | ← CAN Bus | DAC →
Bare-metal register-level programming · No HAL library · STM32F407VGT6
Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments