Bare Metal STM32 · Part 5 of 7
ADC DMA Circular Mode: Continuous Sampling
29:45
Part 5 / 7
⚙️ Bare Metal STM32 Series · 7 Parts
Part 5 of 7
Part 01
HAL vs Bare Metal: Which Should You Use?
38:22
Part 02
Clock Configuration with RCC: From HSI to PLL
22:10
Part 03
Interrupts & NVIC: Complete Configuration Guide
47:22
Part 04
SPI DMA Transfers: No CPU Overhead
33:05
Part 05
ADC DMA Circular Mode: Continuous Sampling
29:45
Part 06
Debugging with SWO Trace & ITM Printf
18:30
Part 07
Low-Power Stop Mode with Wake-Up on EXTI
34:12
01
Overview
Polling the ADC end-of-conversion flag wastes CPU time and introduces jitter between samples. Combine continuous conversion mode with DMA circular transfers and the ADC fills a buffer automatically — the CPU only wakes up at half-buffer and full-buffer boundaries to process data. This is the foundation for audio processing, data logging, and any application that needs a clean, continuous sample stream.
- Configure ADC1 in scan + continuous mode across 4 channels
- Set up DMA2 Stream 0 in circular mode to auto-refill a double buffer
- Handle HT and TC interrupts to process each half of the buffer while the other fills
- Choose the right ADC sample time to avoid cross-channel contamination
02
Core Concepts
Circular DMA
DMA auto-reloads NDTR after each complete transfer, wrapping around to the start of the buffer. Fire-and-forget — never needs restarting. HT fires at N/2, TC fires at N samples.
DMA_SxCR: CIRC | HTIE | TCIE
Scan Mode
ADC steps through a sequence of channels automatically. With continuous mode, it loops the sequence indefinitely. Each conversion result is written sequentially to the DR register, which DMA captures.
ADC_CR1: SCAN · ADC_CR2: CONT
Sample Time
Longer sample time = more accurate reads but slower throughput. For high-impedance sources (>1kΩ), use at least 56 cycles. For low-impedance sources 15 cycles is sufficient.
ADC_SMPRx: SMP[2:0]
03
Code
01
ADC1 + DMA2 circular — 4 channels, double-bufferedadc_dma.c
C
#define NUM_CH 4 #define BUF_LEN (NUM_CH * 32) /* 32 samples per channel */ volatile uint16_t adc_buf[BUF_LEN]; void ADC1_DMA_Init(void) { /* Clocks */ RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN | RCC_AHB1ENR_DMA2EN; RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; /* PA0–PA3 as analog inputs (MODER = 11) */ GPIOA->MODER |= 0xFF; /* ADC prescaler: PCLK2/4 = 21 MHz (max ADC clock is 36 MHz) */ ADC->CCR = ADC_CCR_ADCPRE_0; /* ADC1: scan, continuous, DMA circular, 12-bit, 4 conversions */ ADC1->CR1 = ADC_CR1_SCAN; ADC1->CR2 = ADC_CR2_CONT | ADC_CR2_DMA | ADC_CR2_DDS; ADC1->SQR1 = ((NUM_CH - 1) << ADC_SQR1_L_Pos); // 4 conversions /* Sequence: CH0→CH1→CH2→CH3, 56-cycle sample time each */ ADC1->SQR3 = (0 << 0) | (1 << 5) | (2 << 10) | (3 << 15); ADC1->SMPR2 = 0x00B6DB6D; // 56 cycles for ch 0–3 /* DMA2 Stream0 Channel0 — ADC1 */ DMA2_Stream0->CR = DMA_SxCR_CIRC // Circular | DMA_SxCR_MINC // Memory increment | DMA_SxCR_PSIZE_0 // Periph 16-bit | DMA_SxCR_MSIZE_0 // Memory 16-bit | DMA_SxCR_HTIE // Half-transfer IRQ | DMA_SxCR_TCIE; // Transfer-complete IRQ DMA2_Stream0->PAR = (uint32_t)&ADC1->DR; DMA2_Stream0->M0AR = (uint32_t)adc_buf; DMA2_Stream0->NDTR = BUF_LEN; DMA2_Stream0->CR |= DMA_SxCR_EN; NVIC_EnableIRQ(DMA2_Stream0_IRQn); ADC1->CR2 |= ADC_CR2_ADON; // Power on ADC ADC1->CR2 |= ADC_CR2_SWSTART; // Start conversions } void DMA2_Stream0_IRQHandler(void) { if (DMA2->LISR & DMA_LISR_HTIF0) { DMA2->LIFCR = DMA_LIFCR_CHTIF0; Process_Samples(adc_buf, BUF_LEN / 2); // First half ready } if (DMA2->LISR & DMA_LISR_TCIF0) { DMA2->LIFCR = DMA_LIFCR_CTCIF0; Process_Samples(adc_buf + BUF_LEN/2, BUF_LEN/2); // Second half } }
DDS bit prevents DMA from stopping after one cycle
Set
ADC_CR2_DDS (DMA Disable Selection) alongside the DMA bit. Without it, the ADC stops issuing DMA requests after the first buffer fill — making circular mode effectively useless.Continue the Series
Work through all 7 parts of Bare Metal STM32 to master low-level embedded development from the ground up.