HomeAbout UsContact Us

UART Overrun Errors in STM32: Fixing with DMA

By Jithin Tom
Published in Embedded C/C++
September 18, 2026
4 min read
UART Overrun Errors in STM32: Fixing with DMA

Table Of Contents

01
UART -> DMA -> Memory Flow
02
Overview
03
Root Cause Analysis
04
Solution: DMA-Based UART Reception
05
Verification and Testing
06
Summary
07
Related Reading
08
References
09
Frequently Asked Questions

UART -> DMA -> Memory Flow

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 IRQs
v v v
+---------+ +-------------+ +---------------+
| ORE | | Zero CPU | | Deterministic |
| Risk! | ---> | Overhead | ---> | Processing |
+---------+ +-------------+ +---------------+

Overview

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.

Why This Matters

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.

Root Cause Analysis

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):

  • On classic STM32 devices (STM32F1, STM32F2, STM32F4), data is received via the combined USART_DR (Data Register), and the ORE status bit is located in USART_SR.
  • On newer STM32 families (STM32F0, STM32F7, STM32G0, STM32G4, STM32L0, STM32L4, STM32H7, STM32U5), reception uses the dedicated 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.

Timing Breakdown at 115200 Baud (8N1)

Bit Duration Formula & Calculation:
T_bit = 1 / Baud_Rate
T_bit = 1 / 115200 bps ~= 8.68 us
Frame Duration (8N1: 1 Start bit + 8 Data bits + 1 Stop bit = 10 bits):
T_frame = 10 * T_bit
T_frame = 10 * 8.68 us = 86.8 us
Maximum CPU Servicing Deadline:
T_deadline < T_frame
T_deadline < 86.8 us per incoming byte
  • Baud rate: 115200 bps (8 data bits, no parity, 1 stop bit)
  • Bit duration (T_bit): 8.68 us
  • Frame duration (T_frame): 86.8 us (10 bits per character frame)
  • CPU servicing deadline: Less than 86.8 us per incoming byte
  • Cortex-M4 ISR stacking & entry latency: 12 CPU cycles minimum (longer with flash wait states or FPU lazy stacking)
  • Conclusion: System latency exceeding 86.8 us invariably triggers an ORE condition.

Common Failure Scenarios

  1. Higher-Priority ISR Preemption: Long-running timer, motor-control, or USB interrupts preempting the UART ISR.
  2. Flash Wait States & Memory Contention: Running at high core frequencies (e.g., 168 MHz on STM32F4) introduces flash latency (5 wait states) during ISR vector table fetching.
  3. Critical Sections & Disabled Interrupts: RTOS kernel critical sections (taskENTER_CRITICAL() or __disable_irq()) exceeding 80 us.
  4. Multiple Concurrent UARTs: Servicing multiple high-speed serial streams using software ISRs creates severe bus and NVIC contention.

Solution: DMA-Based UART Reception

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.

Reception Method Comparison

ApproachServicing RateMax Sustainable RateORE Risk Under Heavy Load
Polling100% CPU lockLow (< 38400 baud)Very High
Interrupt-DrivenPer byte (~86.8 us)Medium (~115200 baud)High (Vulnerable to jitter)
Circular DMAPer buffer half (Ping-Pong)High-Speed (Up to 10+ Mbps)Zero (Hardware-driven)

Configuration Architecture

  1. Enable Peripheral Clocks: Power the target USART and the corresponding DMA stream/channel in RCC.
  2. Configure GPIO Alternate Functions: Map TX/RX pins to the designated AF matrix (e.g., GPIO_AF7_USART2 on STM32F4).
  3. Configure DMA Stream:
    • Direction: Peripheral-to-Memory (DMA_PERIPH_TO_MEMORY)
    • Peripheral Address: Address of USART_DR (or USART_RDR)
    • Memory Address: Base address of destination ring buffer
    • Mode: Circular (DMA_CIRCULAR)
    • Increments: Peripheral increment disabled (DMA_PINC_DISABLE), memory increment enabled (DMA_MINC_ENABLE)
    • Transfer Size: Byte alignment (DMA_PDATAALIGN_BYTE, DMA_MDATAALIGN_BYTE)
  4. Configure NVIC Priorities:
    • Assign the DMA Stream IRQ a high priority to promptly handle buffer flips.
    • Enable the UART global IRQ (USART2_IRQn) so that hardware line errors (parity, framing, noise, and overrun) are routed to HAL_UART_ErrorCallback.
  5. Arm Reception: Invoke HAL_UART_Receive_DMA(), which arms the DMA stream and sets the DMAR bit in USART_CR3.

Complete Production Code Example (STM32F4 HAL)

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 64
UART_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 mode
if (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_RX
GPIO_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 STM32F4
hdma_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 NVIC
HAL_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) {
}
}

Critical Implementation Details

1. Circular Ping-Pong Buffer Sizing

When utilizing Half-Transfer (HT) and Transfer-Complete (TC) callbacks, the circular buffer functions as a double buffer:

  • For a circular buffer of size N = 64 bytes, each half consists of 32 bytes (N / 2).
  • At 115200 baud (8N1), one byte arrives every 86.8 us.
Processing Budget Calculation (Double-Buffer Half-Block):
T_budget = (Buffer_Size / 2) * T_frame
T_budget = (64 / 2) * 86.8 us
= 32 * 86.8 us
~= 2.78 ms (2777.6 us)
  • As long as the CPU processes the active half within 2.78 ms, no data overwrite or loss can occur.

2. Interrupt Priority Structuring

In ARM Cortex-M architecture, lower numerical values designate higher logical execution priority:

  • The DMA Stream IRQ should be assigned a logically higher or equal priority compared to the UART IRQ (e.g., DMA priority 5, UART priority 6).
  • This ensures the DMA transfer callbacks are not preempted or delayed by secondary UART status events.

3. Hardware Error Recovery Sequence

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:

  1. STM32F4 (Classic): Cleared by reading USART_SR followed by a dummy read of USART_DR (encapsulated in __HAL_UART_CLEAR_OREFLAG(huart)).
  2. STM32G4/L4/H7 (Modern): Cleared by writing to the ORECF bit in USART_ICR.
  3. Following flag clearance, call HAL_UART_Receive_DMA() to rearm the peripheral DMA stream.

Verification and Testing

Diagnostic Validation Procedures

  1. Continuous Stream Stress Test: Stream 10 MB of continuous binary data at maximum baud rate from an external host; monitor HAL_UART_GetError() to ensure zero ORE events.
  2. CPU Saturation Test: Execute continuous mathematical operations (e.g., matrix inversion or FFT) in the main loop while receiving data. Verify zero dropped frames.
  3. High-Frequency Timer Jitter Test: Trigger a high-priority timer interrupt every 50 us to confirm that DMA transfers proceed unimpeded despite constant CPU preemption.

Real-Time Overrun Monitoring

// Real-time hardware status inspection
if (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_ORE)) {
// Overrun detected - indicate failure on diagnostic pin
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
__HAL_UART_CLEAR_OREFLAG(&huart2);
}

Summary

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.

Key Takeaways

  • Circular DMA: Moves bytes into SRAM automatically, reducing CPU reception overhead to zero.
  • Double Buffering (HT/TC): Provides deterministic processing windows (2.78 ms at 115200 baud for a 64-byte buffer).
  • Always Enable UART IRQ: Without the UART IRQ enabled in NVIC, hardware error callbacks (HAL_UART_ErrorCallback) will never execute on line faults.
  • Correct Flag Clearance: Always clear ORE via the appropriate family-specific register sequence before restarting DMA.


References

  1. STMicroelectronics. “STM32F405xx/07xx and STM32F415xx/17xx Advanced ARM-based 32-bit MCUs Reference Manual.” RM0090, https://www.st.com/en/microcontrollers-microprocessors/stm32f4-series/documentation.html.
  2. ARM Limited. “ARM Cortex-M4 Devices Generic User Guide.” DUI0553, https://developer.arm.com/documentation/dui0553/latest/.
  3. STMicroelectronics. “AN4031 Application Note: Using the STM32F2, STM32F4 and STM32F7 Series DMA controller.” https://www.st.com/resource/en/application_note/an4031-using-the-stm32f2-stm32f4-and-stm32f7-series-dma-controller-stmicroelectronics.pdf.

Frequently Asked Questions

What causes a UART overrun error (ORE) in STM32 microcontrollers?

An overrun error occurs when the UART receive shift register receives a complete frame while the data register (DR on STM32F1/F2/F4 or RDR on STM32F7/G4/L4/H7) still contains an unread byte (RXNE flag is set). The incoming byte is discarded, the ORE flag is set, and data loss occurs.

How does circular DMA eliminate UART overrun errors?

DMA offloads byte transfers from the CPU by transferring incoming bytes directly from the UART data register into an SRAM buffer over the internal bus matrix. This drains the peripheral register within nanoseconds, completely immune to CPU interrupt latency or RTOS scheduler jitter.

Why are Half-Transfer (HT) and Transfer-Complete (TC) interrupts used in circular mode?

They implement a zero-copy double-buffering (ping-pong) scheme. When the first half of the circular buffer fills, the HT interrupt fires, allowing the CPU to safely process the first half while DMA streams into the second half. When the buffer reaches capacity, the TC interrupt fires, repeating the cycle without stopping DMA.

How is the ORE flag cleared in STM32F4 compared to STM32G4/L4/H7?

On STM32F4 (legacy USART peripheral), clearing ORE requires a software read sequence: reading USART_SR followed by reading USART_DR. On modern STM32 series (STM32G4, L4, H7, U5), ORE is cleared by setting the ORECF bit in the USART_ICR (Interrupt Clear Register).

What is the correct recovery procedure if an overrun error occurs during DMA reception?

In HAL_UART_ErrorCallback, check if HAL_UART_ERROR_ORE is asserted, clear the error flag using __HAL_UART_CLEAR_OREFLAG(huart), and call HAL_UART_Receive_DMA to rearm the DMA stream.

Tags

uartdmastm32embedded-c

Share


Previous Article
Linker Garbage Collection for Faster STM32 Builds
Jithin Tom

Jithin Tom

A Closer Look at C/C++, RTOS, and Embedded Systems

Related Posts

Fixing UART Overrun Errors on NXP i.MX RT with FlexIO
Fixing UART Overrun Errors on NXP i.MX RT with FlexIO
October 13, 2026
5 min
© 2026, All Rights Reserved.
Powered By Netlyft

Quick Links

Advertise with usAbout UsContact Us

Social Media