Configuring DMA for Zero‑CPU Overhead UART Reception on STM32 Using HAL
Learn how to set up a DMA‑based UART receive on an STM32 so the CPU stays idle while data fills a buffer. The guide covers prerequisites, step‑by‑step HAL configuration, verification checks, and recovery strategies.
03 Mar 2026, 14:44 UTC

Desired Outcome
Transfer data from a UART peripheral to a memory buffer without the CPU consuming cycles for each byte. The DMA engine should load a predefined buffer size, signal completion via an interrupt, and leave the CPU free for other tasks.
Prerequisites
- STM32CubeIDE or equivalent toolchain with HAL libraries for the target device.
- Basic knowledge of UART, DMA, and interrupt handling in C.
- Hardware: an STM32 board with a UART port (e.g., STM32F103C8) and a serial terminal for testing.
- Ensure the board’s clock tree is configured so that the UART and DMA clocks are enabled.
Focused Procedure
- Enable Clocks
__HAL_RCC_USART1_CLK_ENABLE(); __HAL_RCC_DMA2_CLK_ENABLE();These macros enable the peripheral clocks for USART1 and DMA2 (the DMA controller that services USART1 on many STM32F1 devices).
- Configure UART
UART_HandleTypeDef huart1; huart1.Instance = USART1; huart1.Init.BaudRate = 115200; huart1.Init.WordLength = UART_WORDLENGTH_8B; huart1.Init.StopBits = UART_STOPBITS_1; huart1.Init.Parity = UART_PARITY_NONE; huart1.Init.Mode = UART_MODE_TX_RX; huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart1.Init.OverSampling = UART_OVERSAMPLING_16; HAL_UART_Init(&huart1);Initialize the UART with the desired settings. The HAL function takes care of setting the USART registers.
- Allocate DMA Channel
Choose a DMA stream/channel that the UART can request. For example, on STM32F103C8, USART1_RX uses DMA2 Stream 2 Channel 4.
DMA_HandleTypeDef hdma_usart1_rx; hdma_usart1_rx.Instance = DMA2_Stream2; hdma_usart1_rx.Init.Channel = DMA_CHANNEL_4; hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE; hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma_usart1_rx.Init.Mode = DMA_NORMAL; hdma_usart1_rx.Init.Priority = DMA_PRIORITY_LOW; HAL_DMA_Init(&hdma_usart1_rx);Link the DMA handle to the UART handle so the HAL knows which DMA stream to use for reception.
__HAL_LINKDMA(&huart1, hdmarx, hdma_usart1_rx); - Prepare Buffer and Start DMA
Allocate a buffer that is aligned to 4‑byte boundaries to avoid bus faults. For a 256‑byte UART receive:
#define UART_RX_BUF_LEN 256 uint8_t uart_rx_buf[UART_RX_BUF_LEN] __attribute__((aligned(4)));Start the DMA transfer in interrupt mode:
HAL_UART_Receive_DMA(&huart1, uart_rx_buf, UART_RX_BUF_LEN);The HAL function configures the DMA registers, enables the transfer‑complete interrupt, and starts the stream.
- Enable DMA Transfer‑Complete Interrupt
The HAL library automatically sets the DMA interrupt priority in the NVIC when you call
HAL_UART_Receive_DMA. Verify that the IRQ is enabled:HAL_NVIC_SetPriority(DMA2_Stream2_IRQn, 5, 0); HAL_NVIC_EnableIRQ(DMA2_Stream2_IRQn);Adjust the priority to suit your application’s interrupt hierarchy.
- Implement the ISR
The HAL provides a weakly‑linked default handler that calls
HAL_DMA_IRQHandler. Override it to set a flag when the transfer completes.volatile bool dma_rx_done = false; void DMA2_Stream2_IRQHandler(void) { HAL_DMA_IRQHandler(&hdma_usart1_rx); if (HAL_DMA_GetState(&hdma_usart1_rx) == HAL_DMA_STATE_READY) { dma_rx_done = true; } }In the main loop, poll
dma_rx_doneor use a semaphore if you’re in an RTOS environment. - Verify the Transfer
- After the ISR sets
dma_rx_done, compareuart_rx_bufagainst a known pattern you sent from the terminal. - Use a debugger to inspect the DMA registers:
CCR,CMAR,CPAR,CNDTR. VerifyCMARpoints touart_rx_buf,CPARpoints toUSART1->DR, andCNDTRequalsUART_RX_BUF_LENduring transfer. - Measure CPU load with a profiling tool or by toggling an LED in a tight loop; the LED should remain steady while the DMA runs.
- After the ISR sets
- Recovery Options
- If
dma_rx_donenever becomes true, check for DMA errors:HAL_DMA_GetError(&hdma_usart1_rx). If an error is returned, callHAL_DMA_Abort(&hdma_usart1_rx)and re‑start the transfer. - For long‑running data streams, switch the DMA mode to
DMA_CIRCULARand use a ring buffer. Then, in the ISR, compute the number of bytes received by readingCNDTRand updating a head pointer. - When the UART is re‑initialized (e.g., after a baud‑rate change), always re‑link the DMA handle with
__HAL_LINKDMAbefore starting a new transfer.
- If
Limitations and Practical Checks
- Not all STM32 families expose a DMA stream for every UART. Verify the datasheet or reference manual for the correct stream/ channel.
- DMA buffers must be aligned to at least 4 bytes for Cortex‑M3/M4 devices; misalignment can trigger bus faults.
- On devices with a limited number of DMA streams, allocating all streams to high‑priority tasks can starve other peripherals. Plan channel usage early.
- When using circular mode, ensure that the CPU does not read the buffer while DMA writes to it. Use double buffering or critical sections.
Conclusion
By following the steps above, you can off‑load UART reception to the DMA engine, freeing the CPU for other real‑time tasks. The HAL library abstracts low‑level register manipulation, but careful attention to buffer alignment, interrupt handling, and error recovery is essential for robust operation.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.