
UART PERIPHERAL DMA CONTROLLER MEMORY BUFFER+---------------------+ +---------------------+ +---------------------+| DR / RDR (Data Reg) | | Channel Config: | | Circular Buffer || |-->| Periph->Memory |-->| [byte][byte][byte] || | | Circular Mode | | [byte][byte][byte] || ORE Flag | | Transfer Complete | | [byte][byte][byte] || (Overrun Error) | | Half-Transfer IRQ | | [byte][byte][byte] |+---------------------+ +---------------------+ +---------------------+| | || Byte arrives | Auto-transfer | CPU reads at| every 86.8 us @ | without CPU | own pace via| 115200 baud | intervention | half/full IRQsv v v+---------+ +-------------+ +---------------+| ORE | | Zero CPU | | Deterministic || Risk! | ---> | Overhead | ---> | Processing |+---------+ +-------------+ +---------------+
UART overrun errors are a pervasive challenge in embedded serial communication, especially when processing continuous high-throughput data streams. These errors occur when the microcontroller’s UART hardware receives a new character before the CPU has extracted the previously received character from the receive data register, causing data corruption and lost frames.
In STM32 microcontrollers, this hardware bottleneck can be eliminated by offloading data reception to the Direct Memory Access (DMA) controller. By automating byte movement directly into SRAM, the hardware eliminates CPU intervention during reception and provides deterministic buffering.
At 115200 baud (8N1), a complete 10-bit serial frame arrives every 86.8 us. In CPU-driven or interrupt-driven architectures, the CPU must service the receive register within this strict time budget. Under high interrupt loads, nested ISRs, or RTOS task preemption, missing this window triggers an Overrun Error (ORE). DMA completely removes this per-byte CPU deadline.
The UART Overrun Error (ORE flag) is asserted by the UART hardware when the receive shift register finishes assembling a new character while the internal receive data buffer is still full (i.e., the RXNE flag is still set):
USART_DR (Data Register), and the ORE status bit is located in USART_SR.USART_RDR (Receive Data Register), and ORE is located in USART_ISR.In polling or standard interrupt-driven reception (HAL_UART_Receive_IT), the CPU must execute the interrupt vector, switch context, and read USART_DR / USART_RDR before the subsequent stop bit arrives. If any latency delays this read, the incoming byte is permanently discarded and ORE is raised.
Bit Duration Formula & Calculation:T_bit = 1 / Baud_RateT_bit = 1 / 115200 bps ~= 8.68 usFrame Duration (8N1: 1 Start bit + 8 Data bits + 1 Stop bit = 10 bits):T_frame = 10 * T_bitT_frame = 10 * 8.68 us = 86.8 usMaximum CPU Servicing Deadline:T_deadline < T_frameT_deadline < 86.8 us per incoming byte
T_bit): 8.68 usT_frame): 86.8 us (10 bits per character frame)ORE condition.taskENTER_CRITICAL() or __disable_irq()) exceeding 80 us.The STM32 DMA controller acts as an autonomous bus master capable of moving data between peripheral registers and SRAM across the internal multi-layer AHB bus matrix without CPU cycle execution.
When configured in Circular Mode, the DMA controller continuously cycles through a preallocated SRAM ring buffer. By responding to UART DMA receive requests (USART_CR3_DMAR), the DMA controller extracts bytes from USART_DR within single-digit bus clock cycles, keeping RXNE cleared and preventing overruns.
| Approach | Servicing Rate | Max Sustainable Rate | ORE Risk Under Heavy Load |
|---|---|---|---|
| Polling | 100% CPU lock | Low (< 38400 baud) | Very High |
| Interrupt-Driven | Per byte (~86.8 us) | Medium (~115200 baud) | High (Vulnerable to jitter) |
| Circular DMA | Per buffer half (Ping-Pong) | High-Speed (Up to 10+ Mbps) | Zero (Hardware-driven) |
GPIO_AF7_USART2 on STM32F4).DMA_PERIPH_TO_MEMORY)USART_DR (or USART_RDR)DMA_CIRCULAR)DMA_PINC_DISABLE), memory increment enabled (DMA_MINC_ENABLE)DMA_PDATAALIGN_BYTE, DMA_MDATAALIGN_BYTE)USART2_IRQn) so that hardware line errors (parity, framing, noise, and overrun) are routed to HAL_UART_ErrorCallback.HAL_UART_Receive_DMA(), which arms the DMA stream and sets the DMAR bit in USART_CR3.Below is an end-to-end, robust implementation for an STM32F4xx microcontroller configuring USART2 with DMA1 Stream 5 Channel 4 in circular double-buffering mode.
#include "stm32f4xx_hal.h"#define RX_BUFFER_SIZE 64UART_HandleTypeDef huart2;DMA_HandleTypeDef hdma_usart2_rx;uint8_t rx_buffer[RX_BUFFER_SIZE];void SystemClock_Config(void);static void MX_GPIO_Init(void);static void MX_DMA_Init(void);static void MX_USART2_UART_Init(void);static void Error_Handler(void);int main(void){HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_DMA_Init();MX_USART2_UART_Init();// Start DMA reception in circular modeif (HAL_UART_Receive_DMA(&huart2, rx_buffer, sizeof(rx_buffer)) != HAL_OK) {Error_Handler();}while (1) {// Main application execution: CPU is 100% free from byte-level servicing.// Data processing occurs deterministically in half-buffer chunks.}}static void MX_GPIO_Init(void){__HAL_RCC_GPIOA_CLK_ENABLE();GPIO_InitTypeDef GPIO_InitStruct = {0};GPIO_InitStruct.Pin = GPIO_PIN_2 | GPIO_PIN_3; // PA2: USART2_TX, PA3: USART2_RXGPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Pull = GPIO_PULLUP;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;GPIO_InitStruct.Alternate = GPIO_AF7_USART2;HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);}static void MX_DMA_Init(void){__HAL_RCC_DMA1_CLK_ENABLE();// USART2_RX is mapped to DMA1 Stream 5 Channel 4 on STM32F4hdma_usart2_rx.Instance = DMA1_Stream5;hdma_usart2_rx.Init.Channel = DMA_CHANNEL_4;hdma_usart2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;hdma_usart2_rx.Init.PeriphInc = DMA_PINC_DISABLE;hdma_usart2_rx.Init.MemInc = DMA_MINC_ENABLE;hdma_usart2_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;hdma_usart2_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;hdma_usart2_rx.Init.Mode = DMA_CIRCULAR;hdma_usart2_rx.Init.Priority = DMA_PRIORITY_HIGH;hdma_usart2_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;if (HAL_DMA_Init(&hdma_usart2_rx) != HAL_OK) {Error_Handler();}__HAL_LINKDMA(&huart2, hdmarx, hdma_usart2_rx);// Configure DMA Stream interrupt in NVICHAL_NVIC_SetPriority(DMA1_Stream5_IRQn, 5, 0);HAL_NVIC_EnableIRQ(DMA1_Stream5_IRQn);}static void MX_USART2_UART_Init(void){__HAL_RCC_USART2_CLK_ENABLE();huart2.Instance = USART2;huart2.Init.BaudRate = 115200;huart2.Init.WordLength = UART_WORDLENGTH_8B;huart2.Init.StopBits = UART_STOPBITS_1;huart2.Init.Parity = UART_PARITY_NONE;huart2.Init.Mode = UART_MODE_TX_RX;huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;huart2.Init.OverSampling = UART_OVERSAMPLING_16;if (HAL_UART_Init(&huart2) != HAL_OK) {Error_Handler();}// Enable UART global interrupt to handle line error callbacks (ORE, NE, FE, PE)HAL_NVIC_SetPriority(USART2_IRQn, 6, 0);HAL_NVIC_EnableIRQ(USART2_IRQn);}/*** @brief DMA1 Stream 5 interrupt request handler*/void DMA1_Stream5_IRQHandler(void){HAL_DMA_IRQHandler(&hdma_usart2_rx);}/*** @brief USART2 global interrupt request handler*/void USART2_IRQHandler(void){HAL_UART_IRQHandler(&huart2);}/*** @brief Half-transfer callback: Invoked when buffer indices [0 .. RX_BUFFER_SIZE/2 - 1] are filled.*/void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart){if (huart->Instance == USART2) {// Safely process first half: rx_buffer[0] to rx_buffer[31]// DMA is currently filling rx_buffer[32] to rx_buffer[63]}}/*** @brief Transfer complete callback: Invoked when buffer indices [RX_BUFFER_SIZE/2 .. RX_BUFFER_SIZE - 1] are filled.*/void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){if (huart->Instance == USART2) {// Safely process second half: rx_buffer[32] to rx_buffer[63]// DMA has automatically wrapped around and is now filling rx_buffer[0] to rx_buffer[31]}}/*** @brief UART error callback: Invoked when hardware errors occur.*/void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart){if (huart->Instance == USART2) {uint32_t error = HAL_UART_GetError(huart);if (error & HAL_UART_ERROR_ORE) {// Overrun occurred (e.g., bus master stall or delayed DMA stream init)// Clear the ORE flag and rearm circular DMA reception__HAL_UART_CLEAR_OREFLAG(huart);HAL_UART_Receive_DMA(huart, rx_buffer, sizeof(rx_buffer));}}}static void Error_Handler(void){__disable_irq();while (1) {}}
When utilizing Half-Transfer (HT) and Transfer-Complete (TC) callbacks, the circular buffer functions as a double buffer:
N = 64 bytes, each half consists of 32 bytes (N / 2).Processing Budget Calculation (Double-Buffer Half-Block):T_budget = (Buffer_Size / 2) * T_frameT_budget = (64 / 2) * 86.8 us= 32 * 86.8 us~= 2.78 ms (2777.6 us)
In ARM Cortex-M architecture, lower numerical values designate higher logical execution priority:
If higher-priority AHB bus masters (such as Ethernet DMA or high-throughput memory-to-memory transfers) stall the DMA bus arbiter, an overrun can still occur. When ORE is asserted, hardware reception halts until cleared:
USART_SR followed by a dummy read of USART_DR (encapsulated in __HAL_UART_CLEAR_OREFLAG(huart)).ORECF bit in USART_ICR.HAL_UART_Receive_DMA() to rearm the peripheral DMA stream.HAL_UART_GetError() to ensure zero ORE events.// Real-time hardware status inspectionif (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_ORE)) {// Overrun detected - indicate failure on diagnostic pinHAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);__HAL_UART_CLEAR_OREFLAG(&huart2);}
UART overrun errors in STM32 microcontrollers are hardware timing faults caused by CPU latencies exceeding serial frame durations. Offloading reception to the DMA controller in circular mode completely eliminates CPU servicing constraints, providing zero-overhead data transfer and deterministic double-buffering.
HAL_UART_ErrorCallback) will never execute on line faults.ORE via the appropriate family-specific register sequence before restarting DMA.Quick Links
Legal Stuff





