What is STM32F407 ADC Deep Dive-Embedded Systems Training Institute in Hyderabad

Need Help or More Information?

Questions about this free C programming course, embedded C training or career guidance? Get in touch.

Tutorial 16 — Analog to Digital Conversion

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.

12-bit Resolution3 ADC Instances24 External Channels2.4 MSPS Max Sample Rate0–3.3V Input Range

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 Instance Summary
ADCBase AddressExternal Ch.APB BusDMANotes
ADC10x4001200016 (IN0–IN15)APB2DMA2 Ch0 S0/S4Has temperature sensor, VREFINT, VBAT
ADC20x4001210016 (IN0–IN15)APB2DMA2 Ch1 S2/S3Multi-mode slave
ADC30x4001220016 (IN0–IN15)APB2DMA2 Ch2 S0/S1Multi-mode slave
ADC_COM0x40012300—APB2—ADC_CCR common register
Note: ADC1 is the only instance that can access the internal temperature sensor (channel 16), VREFINT (channel 17), and VBAT/4 (channel 18). ADC2 and ADC3 external channels share the same GPIO pins as ADC1 on most packages.

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.

IN0
PA0
IN1
PA1
IN2
PA2
IN3
PA3
IN4
PA4
IN5
PA5
IN6
PA6
IN7
PA7
IN8
PB0
IN9
PB1
IN10
PC0
IN11
PC1
IN12
PC2
IN13
PC3
IN14
PC4
IN15
PC5
IN16
Temp (ADC1)
IN17
VREFINT (ADC1)
IN18
VBAT/4 (ADC1)

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]PrescalerADC Clock (APB2=84MHz)Valid?
00÷242 MHzEXCEEDS 36MHz ✗
01÷421 MHzOK ✓ (recommended)
10÷614 MHzOK ✓
11÷810.5 MHzOK ✓
Warning: Running ADC at 42 MHz (÷2) exceeds the 36 MHz limit and results in unreliable conversions. Always use ADCPRE = 01 (÷4 = 21 MHz) or higher prescaler at 168 MHz.

Conversion Pipeline

Every ADC conversion goes through three phases:

Diagram: Conversion Pipeline
Sampling
Hold capacitor charges
→
Conversion
SAR logic (12 cycles)
→
Transfer
Result → DR register

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
STM32F407 ADC1 Internal Architecture
ADC1 (0x40012000)
External Ch
(IN0–IN15)
Internal Ch
(Temp/VREF/VBAT)
Multiplexer
SQR/JSQR (sequence)
Sample & Hold
SAR ADC (12-bit)
ADC_DR
DMA
EOC / OVR / AWD
Interrupt/DMA Req
APB2 bus
(RCC_APB2ENR ADC1EN = bit 8)
ADC_CCR clock
(ADCPRE ÷4 = 21MHz)
==================== SECTION 2: REGISTERS ====================

02 ADC Register Map

All ADC registers are 32-bit. Offsets are from the ADC base address (0x40012000 for ADC1).

OffsetRegisterDescription
0x00ADC_SRStatus register — flags EOC, STRT, OVR, AWD, JEOC
0x04ADC_CR1Control 1 — resolution, scan, watchdog, interrupt enables
0x08ADC_CR2Control 2 — ADON, continuous, trigger, alignment, DMA
0x0CADC_SMPR1Sample time for channels 10–18
0x10ADC_SMPR2Sample time for channels 0–9
0x14ADC_JOFR1–4Injected channel data offset (0x14–0x20)
0x24ADC_HTRAnalog watchdog high threshold
0x28ADC_LTRAnalog watchdog low threshold
0x2CADC_SQR1Regular sequence register 1 (L, SQ13–SQ16)
0x30ADC_SQR2Regular sequence register 2 (SQ7–SQ12)
0x34ADC_SQR3Regular sequence register 3 (SQ1–SQ6)
0x38ADC_JSQRInjected sequence register
0x3C–0x48ADC_JDR1–4Injected channel data registers
0x4CADC_DRRegular data register — holds conversion result

ADC_SR — Status Register (Offset 0x00)

ADC_SR @ ADC_BASE + 0x00 (bits 31:6 reserved)
31—
30—
29—
28—
27—
26—
25—
24—
23—
22—
21—
20—
19—
18—
17—
16—
15—
14—
13—
12—
11—
10—
9—
8—
7—
6—
5OVR
4STRT
3JSTRT
2JEOC
1EOC
0AWD
Bit and field descriptions
Bit / FieldDescription
31 —Reserved
5 OVROVR — Overrun:
4 STRTSTRT — Regular channel start:
3 JSTRTJSTRT — Injected channel start:
2 JEOCJEOC — Injected EOC:
1 EOCEOC — End of Conversion:
0 AWDAWD — Analog Watchdog:
BitNameAccessDescription
5OVRrc_w0Overrun — DR was not read before next conversion completed. Data was lost. Write 0 to clear.
4STRTrc_w0Regular channel start — Set when regular conversion starts. Write 0 to clear.
3JSTRTrc_w0Injected channel start — Set when injected conversion starts.
2JEOCrc_w0Injected end of conversion — Set when injected group completes.
1EOCrc_w0End of conversion — Set when regular conversion complete. Reading ADC_DR clears this if EOCS=0.
0AWDrc_w0Analog watchdog — Set when converted value is outside HTR/LTR thresholds. Write 0 to clear.
Important — ADC_SR clearing: Unlike EXTI_PR (write 1 to clear), ADC_SR flags are cleared by writing 0 (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)

ADC_CR1 @ ADC_BASE + 0x04
31res
30res
29res
28res
27RES1
26OVRIE
25res
24res
23AWDEN
22JAWDEN
21res
20res
19DC2
18DC1
17DC0
16res
15res
14res
13JDISCEN
12DISCEN
11JAUTO
10AWDSGL
9res
8SCAN
7JEOCIE
6AWDIE
5EOCIE
4AWD4
3AWD3
2AWD2
1AWD1
0AWD0
Bit and field descriptions
Bit / FieldDescription
31 resReserved
27 RES1RES[1] bit 27 — Resolution MSB:
26 OVRIEOVRIE (bit 26) — Overrun Interrupt Enable
25 resReserved
23 AWDENAWDEN — Analog Watchdog enable on regular channels
22 JAWDENJAWDEN — Analog Watchdog enable on injected channels
21 resReserved
19 DC2DISCNUM[2:0] bits 19:16 — Discontinuous mode channel count
18 DC1DISCNUM[1]
17 DC0DISCNUM[0]
16 resReserved
13 JDISCENJDISCEN — Discontinuous mode on injected channels
12 DISCENDISCEN — Discontinuous mode on regular channels
11 JAUTOJAUTO — Automatic injected group conversion
10 AWDSGLAWDSGL — AWD on single channel (use AWDCH to select)
9 resReserved
8 SCANSCAN — Scan mode: 1=convert all channels in sequence
7 JEOCIEJEOCIE — Injected end-of-conversion interrupt enable
6 AWDIEAWDIE — Analog Watchdog interrupt enable
5 EOCIEEOCIE — Regular EOC interrupt enable
4 AWD4AWDCH[4:0] bits 4:0 — Analog Watchdog channel select
3 AWD3AWDCH[3]
2 AWD2AWDCH[2]
1 AWD1AWDCH[1]
0 AWD0AWDCH[0] — Selects monitored channel (0–18)
BitsNameDescription
27:26RES[1:0]Resolution: 00=12-bit (15 cycles), 01=10-bit (13), 10=8-bit (11), 11=6-bit (9). Fewer bits = faster conversion.
26OVRIEOverrun interrupt enable: 1 = interrupt on OVR flag
23AWDENAnalog watchdog enable on regular channels
22JAWDENAnalog watchdog enable on injected channels
19:16DISCNUM[2:0]Discontinuous mode channel count: Number of channels to convert per trigger (1–8)
13JDISCENDiscontinuous mode for injected channels
12DISCENDiscontinuous mode for regular channels: Each trigger converts DISCNUM+1 channels
11JAUTOAutomatic injected group conversion: Injected group converts after regular group
10AWDSGLAWD on single channel: 0=all channels, 1=only AWDCH channel
8SCANScan mode: 1 = convert all channels in sequence (SQR1–SQR3)
7JEOCIEInterrupt enable for injected EOC
6AWDIEAnalog watchdog interrupt enable
5EOCIEInterrupt enable for regular EOC: Fires ADC IRQ when EOC=1
4:0AWDCH[4:0]Analog watchdog channel: Which channel (0–18) the AWD monitors (when AWDSGL=1)

ADC_CR2 — Control Register 2 (Offset 0x08)

ADC_CR2 @ ADC_BASE + 0x08
31res
30SWSTART
29EXTEN1
28EXTEN0
27EXT3
26EXT2
25EXT1
24EXT0
23res
22JSWSTART
21JEXTEN1
20JEXTEN0
19JEXT3
18JEXT2
17JEXT1
16JEXT0
15res
14res
13res
12res
11ALIGN
10EOCS
9DDS
8DMA
7res
6res
5res
4res
3res
2res
1CONT
0ADON
Bit and field descriptions
Bit / FieldDescription
31 resReserved
30 SWSTARTSWSTART — Software start:
29 EXTEN1EXTEN[1:0] bits 29:28 — External trigger enable for regular channels:
28 EXTEN0EXTEN[0]
27 EXT3EXTSEL[3:0] bits 27:24 — External trigger source for regular channels
26 EXT2EXTSEL[2]
25 EXT1EXTSEL[1]
24 EXT0EXTSEL[0]
23 resReserved
22 JSWSTARTJSWSTART — Injected software start
21 JEXTEN1JEXTEN[1:0] bits 21:20 — External trigger enable for injected
20 JEXTEN0JEXTEN[0]
19 JEXT3JEXTSEL[3:0] bits 19:16 — External trigger source for injected
18 JEXT2JEXTSEL[2]
17 JEXT1JEXTSEL[1]
16 JEXT0JEXTSEL[0]
15 resReserved
11 ALIGNALIGN — Data alignment:
10 EOCSEOCS — End of conversion selection:
9 DDSDDS — DMA disable selection:
8 DMADMA — DMA enable:
7 resReserved
1 CONTCONT — Continuous mode:
0 ADONADON — ADC ON:
BitsNameDescription
30SWSTARTSoftware start: Write 1 to start a regular conversion. Self-clears (no need to clear manually).
29:28EXTEN[1:0]External trigger enable: 00=disabled (SW only), 01=rising edge, 10=falling edge, 11=both edges
27:24EXTSEL[3:0]External trigger source: selects TIM/EXTI event to trigger conversion (see reference manual Table 79)
22JSWSTARTInjected software start
21:20JEXTEN[1:0]External trigger enable for injected
19:16JEXTSEL[3:0]External trigger for injected channels
11ALIGNData alignment: 0=right-aligned (typical), 1=left-aligned. Right: result in bits [11:0]. Left: result in bits [15:4].
10EOCSEnd of conversion selection: 0=EOC after each sequence, 1=EOC after each channel. Set to 1 for per-channel interrupt.
9DDSDMA disable selection: 0=DMA disabled after last transfer, 1=continuous DMA requests
8DMADMA enable: 1=DMA requests generated after each conversion
1CONTContinuous conversion mode: 1=automatically restart conversions. 0=single shot (must re-trigger).
0ADONADC ON/OFF: 1=enable ADC. Must wait ~3 µs stabilization time after first enable before starting conversion.
Tip — SWSTART and ADON: Setting 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]CyclesTime @ 21MHzUse Case
0003143 nsVery low impedance (<1kΩ), test only
00115714 nsLow impedance (<10kΩ)
010281.33 µsMedium impedance sensors
011562.67 µsPotentiometer (10kΩ typical)
100844.0 µsHigh impedance (<100kΩ)
1011125.33 µsVery high impedance
1101446.86 µsNoisy signals, averaging needed
11148022.9 µsInternal 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).

RegisterBit FieldSequence PositionBits Per Entry
SQR3[4:0]SQ1 (1st conversion)5-bit channel number
SQR3[9:5]SQ25-bit channel number
SQR3[14:10]SQ35-bit channel number
SQR3[19:15]SQ45-bit channel number
SQR3[24:20]SQ55-bit channel number
SQR3[29:25]SQ65-bit channel number
SQR2[4:0]SQ75-bit channel number
SQR2…SQ8–SQ125-bit channel number
SQR1[4:0]SQ135-bit channel number
SQR1…SQ14–SQ165-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.
Diagram: ADC_DR — Data Register (Offset 0x4C)

Live conversion result visualizer:

2048
Conversion bar (0 → 3.3V):
1.650V
Hex: 0x0800  |  Percent: 50.0%
==================== SECTION 3: ADC_CCR ====================

03 ADC_CCR — Common Control Register

ADC_CCR is located at 0x40012304 (ADC common base + 0x04) and controls settings shared across all three ADCs.

BitsNameDescription
23TSVREFETemperature sensor and VREFINT enable: Must be set before reading CH16 or CH17
22VBATEVBAT enable: Must be set before reading CH18 (VBAT/4). Disable when not in use.
17:16ADCPRE[1:0]ADC prescaler: 00=÷2, 01=÷4 (21MHz), 10=÷6 (14MHz), 11=÷8 (10.5MHz)
15:14DMA[1:0]DMA mode for multi-ADC: Used in dual/triple ADC mode
13DDSDMA disable selection (multi-mode)
11:8DELAY[3:0]Delay between two sampling phases (multi-mode)
4:0MULTI[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 */
==================== SECTION 4: SINGLE MODE ====================

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 */
    }
}
==================== SECTION 5: CONTINUOUS ====================

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.

Beware of overrun (OVR): If you don’t read 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 */
}
==================== SECTION 6: SCAN MODE ====================

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 + DMA is the standard pattern for multi-channel ADC. Without DMA, you only have one ADC_DR register — each new conversion overwrites the previous. The only safe polling approach for N channels is N separate triggering with EOCS=1.
/* ── 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... */
    }
}
==================== SECTION 7: INTERRUPT ====================

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) */
    }
}
==================== SECTION 8: CALCULATOR ====================

08 ADC Timing Calculator

ADC Conversion Time Calculator

168 MHz
÷1 (same as sys)
÷4 (recommended)
28 cycles
12-bit (+12 cycles)
APB2 clock: 168 MHz  |  ADC clock: 42.00 MHz ✗ EXCEEDS 36MHz!
Total cycles: 28 (sample) + 12 (conversion) = 40 cycles
Conversion time: 952 ns  =  0.952 µs  →  1050 kSPS
⚠ Increase ADC prescaler to /4, /6, or /8 to stay under 36 MHz.
==================== SECTION 9: POTENTIOMETER ====================

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.

Potentiometer wired to ADC1 channel 1
3.3V
10kΩ pot
wiper
PA1 (ADC1_IN1)
ADC1 IN1
GND
#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 */
    }
}
==================== SECTION 10: MULTI-CHANNEL ====================

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;
    }
}
==================== SECTION 11: INJECTED ====================

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;
    }
}
==================== SECTION 12: WATCHDOG ====================

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) */
    }
}
==================== SECTION 13: TIPS ====================

13 Tips, Pitfalls & Best Practices

Common Pitfalls
  • 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=1 before reading them.
  • ADC_SR clearing direction: rc_w0 means write-0-to-clear. Use SR &= ~flag, NOT SR = 0 or SR |= flag.
Performance Tips
  • 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=1 and 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; */
Temperature sensor formula: 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

Leave a Reply

Your email address will not be published. Required fields are marked *