HomeAbout UsContact Us

UART Overrun Errors in STM32: Fixing with DMA

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

Table Of Contents

01
ASCII Art Diagram: 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

ASCII Art Diagram: UART → DMA → Memory Flow

UART PERIPHERAL DMA CONTROLLER MEMORY BUFFER
┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐
│ RDR (Receive │ │ Channel Config: │ │ Circular Buffer │
│ Data Register) │──▶│ Periph→Memory │──▶│ [byte][byte][byte] │
│ │ │ Circular Mode │ │ [byte][byte][byte] │
│ OVRE Flag │ │ Transfer Complete │ │ [byte][byte][byte] │
│ (Overrun Error) │ │ Half-Transfer IRQ │ │ [byte][byte][byte] │
└─────────────────────┘ └─────────────────────┘ └─────────────────────┘
│ │ │
│ Byte arrives │ Auto-transfer │ CPU reads at
│ every 86.8µs @ │ without CPU │ own pace via
│ 115200 baud │ intervention │ half/full IRQs
▼ ▼ ▼
┌─────────┐ ┌─────────────┐ ┌─────────────┐
│ OVRE │ │ Zero CPU │ │ Deterministic│
│ Risk! │ ───▶ │ Overhead │ ───▶ │ Processing │
└─────────┘ └─────────────┘ └─────────────┘

Overview

UART overrun errors are a common issue in embedded serial communication, particularly when handling high-speed data streams. These errors occur when the microcontroller’s UART peripheral receives new data before the CPU has read the previously received data, leading to data loss and potential system instability. In STM32 microcontrollers, this problem can be effectively resolved by leveraging the Direct Memory Access (DMA) controller to automate data transfer, thereby eliminating the need for CPU intervention during reception.

Why This Matters

At 115200 baud, a byte arrives every ~87 µs. The CPU must read the RDR within this window—impossible under load. DMA removes this timing constraint entirely.

Root Cause Analysis

The UART overrun error (OVRE flag) is set when the UART’s receive data register (RDR) is not read before the next byte arrives. In a polling-based or interrupt-driven reception scheme, the CPU must service the UART interrupt or poll the status register within the time it takes to receive a single byte. At high baud rates, this window becomes extremely narrow, increasing the likelihood of overruns especially when the CPU is busy with other tasks.

Timing Breakdown at 115200 Baud

  • Bit time: 8.68 µs
  • Frame (10 bits): 86.8 µs
  • CPU budget: < 87 µs per byte to read RDR
  • Context switch: 1–5 µs (Cortex-M4)
  • ISR entry/exit: 12–20 cycles
  • Result: Tight margin; any interrupt latency causes OVRE

Common Failure Scenarios

  1. High-priority interrupt blocks UART ISR
  2. Flash wait states delay ISR vector fetch
  3. RTOS task disables interrupts too long
  4. Multiple UARTs sharing single ISR priority

Solution: DMA-Based UART Reception

The DMA controller provides a hardware-assisted mechanism for transferring data between peripherals and memory without CPU involvement. By configuring the DMA to handle UART reception, the received bytes are automatically moved from the UART’s RDR to a memory buffer, freeing the CPU to handle other tasks or simply reducing its workload.

Why DMA Solves It

ApproachCPU InvolvementMax Sustainable RateOVRE Risk
Polling100%LowHigh
InterruptPer byteMediumMedium
DMA CircularPer bufferVery HighZero

Configuration Steps

Step 1: Enable Clocks

Activate the clock for the desired UART peripheral and the DMA controller.

Step 2: Configure UART for DMA Reception

Set the UART’s DMA request enable bit for reception (DMAR) in the control register 3 (CR3).

Step 3: DMA Channel Setup

  • Configure the DMA peripheral address as the UART’s RDR
  • Set the memory address to the start of the receive buffer
  • Define the data size (typically 8 bits for UART)
  • Select circular mode to continuously refill the buffer
  • Set the priority level and enable interrupts for transfer complete and half-transfer events

Step 4: Start DMA Transfer

Enable the DMA channel to begin transferring data.

Step 5: Interrupt Handling

In the DMA interrupt service routine (ISR), process the received data from the buffer based on whether the transfer complete or half-transfer flag is set.

Complete Code Example

Below is a complete example demonstrating DMA-based UART reception on an STM32F4xx using the HAL library. The code configures USART2 at 115200 baud with a circular DMA buffer of 64 bytes.

#include "stm32f4xx_hal.h"
UART_HandleTypeDef huart2;
DMA_HandleTypeDef hdma_usart2_rx;
uint8_t rx_buffer[64];
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_USART2_UART_Init(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 loop - CPU is free for other tasks
// Process rx_buffer in DMA ISR or via flags
}
}
void SystemClock_Config(void)
{
// Clock configuration specific to your STM32
}
static void MX_GPIO_Init(void)
{
// GPIO initialization for UART pins (PA2: TX, PA3: RX)
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
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();
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_LOW;
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);
// DMA interrupt init
HAL_NVIC_SetPriority(DMA1_Stream5_IRQn, 0, 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 DMA for UART reception
SET_BIT(huart2.Instance->CR3, USART_CR3_DMAR);
}
void DMA1_Stream5_IRQHandler(void)
{
HAL_DMA_IRQHandler(&huart2.hdmarx);
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART2) {
// Handle full buffer reception
// Process rx_buffer[0] to rx_buffer[63]
}
}
void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART2) {
// Handle half-buffer reception
// Process rx_buffer[0] to rx_buffer[31]
}
}

Key Configuration Details

Circular Buffer Sizing

// Buffer must accommodate worst-case ISR latency
// At 115200 baud: 1 byte/87µs
// If max ISR latency = 500µs → need 6+ bytes headroom
// 64-byte buffer = 5.5ms headroom (comfortable)
#define RX_BUFFER_SIZE 64

Interrupt Priority Setup

// DMA IRQ priority must be >= UART IRQ priority
// to prevent DMA ISR being preempted
HAL_NVIC_SetPriority(DMA1_Stream5_IRQn, 5, 0); // Example

Error Handling

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 still possible if DMA stops!
// Clear flag and restart DMA
__HAL_UART_CLEAR_OREFLAG(huart);
HAL_UART_Receive_DMA(huart, rx_buffer, RX_BUFFER_SIZE);
}
}
}

Verification and Testing

Test Procedure

  1. Continuous stream test: Transmit 1 MB from PC at 115200 baud; verify zero OVRE flags
  2. CPU load test: Run intensive math in main loop while receiving; verify no data loss
  3. Interrupt stress: Fire high-priority timer ISR every 100 µs; confirm DMA unaffected
  4. Buffer boundary: Send exact multiples of buffer size; verify half/full callbacks fire correctly

Debugging Tips

// Monitor OVRE flag in real-time
while (1) {
if (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_ORE)) {
// Overrun occurred - should never happen with DMA
GPIO_WritePin(LED_PORT, LED_PIN, GPIO_PIN_SET); // Visual alert
__HAL_UART_CLEAR_OREFLAG(&huart2);
}
}

Performance Measurement

  • Toggle GPIO in HAL_UART_RxHalfCpltCallback and HAL_UART_RxCpltCallback
  • Measure with oscilloscope: should see pulse every 2.8 ms (half) and 5.6 ms (full) at 115200 baud
  • CPU usage: < 1% vs 15–30% for interrupt-driven at same rate

Summary

UART overrun errors in STM32 can be effectively mitigated by utilizing the DMA controller for automatic data transfer. This approach eliminates the CPU’s strict timing constraints for reading the UART’s receive data register, thereby preventing data loss and improving system reliability. The provided code example demonstrates a complete implementation using the STM32 HAL library, enabling developers to integrate DMA-based UART reception into their applications with minimal effort.

Key Takeaways

  • 💡 Use circular DMA for continuous reception without CPU intervention
  • 💡 Size buffer for worst-case ISR latency + margin
  • 💡 Handle half/full callbacks for streaming processing
  • ⚠️ Still check OVRE in error callback—DMA can stall if bus fault occurs
  • 💡 DMA priority must allow timely RDR reads

References

  1. STMicroelectronics. “STM32F405xx/07xx and STM32F415xx/17xx Advanced ARM®-based 32-bit MCUs Reference Manual.” RM0090, https://www.st.com/content/ccc/resource/technical/document/reference_manual/3d/6d/5a/66/b4/99/40/d4/DM00031020.pdf/files/DM00031020.pdf/jcr:content/translations/en.DM00031020.pdf.
  2. ARM Limited. “ARM® Cortex®-M4 Devices Generic User Guide.” https://support.arm.com/documentation/dui0553/a.
  3. STMicroelectronics. “AN4031: Data transfer using the DMA controller.” https://www.st.com/content/ccc/resource/technical/document/application_note/27/46/7c/ea/2d/91/40/a9/DM00046011.pdf/files/DM00046011.pdf/jcr:content/translations/en.DM00046011.pdf.

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

Software UART for Embedded Debugging: GPIO-Based Serial
Software UART for Embedded Debugging: GPIO-Based Serial
September 11, 2026
4 min
© 2026, All Rights Reserved.
Powered By Netlyft

Quick Links

Advertise with usAbout UsContact Us

Social Media