How to use DMA in circular mode on STM32?
Using DMA in circular mode on STM32 basically means:
“Keep filling the same buffer in RAM over and over, wrapping around when you reach the end.”
This is perfect for continuous ADC sampling, UART RX streams, audio, etc. Here’s how to do it, in practical steps.
1. Basic idea
-
Peripheral data register → DMA → RAM buffer.
-
DMA writes:
-
buf[0] … buf[N-1] -
then automatically wraps back to
buf[0]and continues.
-
-
Your code never restarts the DMA; it just reads from the buffer while DMA keeps filling it.
2. Create the buffer in RAM
Example for an ADC (16-bit samples):
Example for UART RX (8-bit):
Make sure the buffer is global or static so it stays allocated.
3. Configure DMA in circular mode (HAL example)
Let’s say you’re using STM32Cube HAL.
3.1. In CubeMX / code, set:
-
Direction: Peripheral → Memory
-
Mode:
DMA_CIRCULAR -
PeriphInc:
DMA_PINC_DISABLE(peripheral addr fixed) -
MemInc:
DMA_MINC_ENABLE(walk through buffer) -
Data width:
-
If ADC 12-bit → usually use 16-bit (
DMA_PDATAALIGN_HALFWORD,DMA_MDATAALIGN_HALFWORD) -
If UART → 8-bit (
BYTE)
-
Example manual init (F4 style):
For UART RX it’s the same idea, but Direction = DMA_PERIPH_TO_MEMORY with 8-bit alignment and linked to huartX.
4. Start DMA in circular mode
ADC example
UART RX example
Because the DMA is in circular mode, it will never “finish” – it just keeps overwriting the buffer.
5. How to process data from the circular buffer
Two common strategies:
5.1. Half-transfer + transfer-complete (ping-pong style)
Enable DMA interrupts for:
-
Half transfer
-
Transfer complete
HAL lets you implement callbacks:
-
HAL_ADC_ConvHalfCpltCallback()/HAL_DMA_XferHalfCpltCallback() -
HAL_ADC_ConvCpltCallback()/HAL_DMA_XferCpltCallback()
Concept:
-
Half-transfer interrupt
→ first half of the buffer[0 … N/2 - 1]is full → process that chunk. -
Transfer-complete interrupt
→ second half[N/2 … N - 1]is full → process that chunk.
Example:
Or, for a generic DMA stream:
This ping-pong approach is simple and robust for streaming data.
5.2. True “ring buffer” with head/tail pointers
If you don’t want fixed half-chunks, you can treat the DMA buffer as a ring and track where the DMA is currently writing.
For STM32, the DMA has a “number of data to transfer” (NDTR) register.
For a circular buffer:
Then you maintain your own tail index of “data already processed”:
Call process_adc_ring() from your main loop or from a timer/IRQ when you know new data is available.
Tip: briefly disable interrupts while reading NDTR + updating
tailif you need absolute safety against race conditions.
6. Circular DMA for UART RX with idle-line detection (very common)
Typical pattern to receive variable-length messages:
-
Circular DMA on UART RX:
-
Enable IDLE line interrupt on the UART:
-
In the UART IRQ handler or callback:
Now the DMA keeps filling the buffer; when the line goes idle, you know a frame ended and can process all bytes since the last time.
7. Things to watch out for
-
Buffer size:
Must fit in RAM; big audio/video buffers can eat memory quickly. -
Alignment:
Data width (byte/halfword/word) must match peripheral and buffer type. -
Caches (F7/H7 etc.):
If your MCU has D-Cache, DMA buffers must be in non-cached region or you must clean/invalidate cache around DMA. -
Overruns:
If your processing is too slow andheadovertakestail, you’ll lose data. Make sure your processing loop is fast enough.
TL;DR
-
Create a global buffer in RAM.
-
Configure DMA channel/stream:
-
Mode = DMA_CIRCULAR -
MemInc = ENABLE,PeriphInc = DISABLE
-
-
Start DMA from peripheral to that buffer (e.g.
HAL_ADC_Start_DMA,HAL_UART_Receive_DMA). -
Use half/full transfer callbacks or head/tail + NDTR to know which part of the circular buffer to process.

评论
发表评论