HomeAbout UsContact Us

Fixing UART DMA Overrun Errors on STM32

By Jithin Tom
Published in Embedded Concepts
August 21, 2026
5 min read
Fixing UART DMA Overrun Errors on STM32

Table Of Contents

01
Problem Statement: The Overrun Condition
02
Root Cause Analysis
03
Solution Approaches
04
Complete Recovery Sequence
05
Detection: HAL vs LL
06
Prevention Strategies
07
Verification Steps
08
UART DMA Data Flow
09
Recovery Sequence State Machine
10
Summary
11
Related Reading
12
References
13
Frequently Asked Questions

UART DMA overrun errors are a frequent source of data corruption and silent communication failures in STM32-based systems. The symptom is often subtle: the UART appears to work initially, then starts dropping bytes or producing garbage after sustained traffic. The root cause is almost always a timing mismatch between the UART peripheral filling its receive data register and the DMA engine draining it.

This article covers the complete problem-solution cycle: why overrun happens, how to detect it in both HAL and LL drivers, the exact recovery sequence, and prevention strategies that work in production.

Problem Statement: The Overrun Condition

An overrun error (ORE) occurs when the UART shift register completes receiving a new byte, but the previous byte in the receive data register (RDR or DR) has not yet been read. In DMA mode, the DMA controller is responsible for reading the data register and writing to memory. If the DMA transfer does not complete before the next byte arrives, the hardware sets the ORE flag and the incoming byte is lost.

The STM32 reference manual (e.g., RM0090) states: “An overrun error occurs when a character is received when RXNE has not been reset.” The new byte is discarded, and the data currently in the data register is retained.

This is not a software bug — it is a hardware timing violation. The fix requires understanding the timing budget and ensuring the DMA path meets it.

Root Cause Analysis

Three primary factors cause UART DMA overrun:

1. Baud Rate Exceeds DMA Service Latency

At 921600 baud with 8N1 framing, each byte takes approximately 10.85 us on the wire. The DMA must read RDR and write to SRAM within this window. On an STM32F4 at 168 MHz, a single DMA transfer takes ~3-5 clock cycles plus bus arbitration. That is fast enough in isolation, but add:

  • Other DMA channels competing for the bus
  • CPU accessing SRAM simultaneously (flash wait states, stack operations)
  • Interrupt latency delaying the DMA TC/HT interrupt handler

The budget shrinks rapidly.

2. DMA Channel Contention

STM32 DMA controllers have multiple channels/streams sharing a single bus matrix. UART RX DMA typically uses a low-priority channel. If a high-priority channel (e.g., ADC DMA, SPI DMA, or memory-to-memory transfer) runs concurrently, the UART RX DMA may be stalled long enough for the next byte to arrive.

3. Interrupt Latency Blocking DMA Reconfiguration

In non-circular mode, the DMA transfer complete (TC) interrupt must reconfigure the DMA for the next buffer. If the TC interrupt is delayed (higher-priority ISR, critical section, WFI sleep), the DMA sits idle while UART bytes pile up in RDR.

Solution Approaches

Circular DMA eliminates the TC interrupt reconfiguration latency. Double buffering (using HT and TC interrupts to ping-pong between two halves) gives the application a full half-buffer time to process data.

// STM32 HAL - Circular DMA with IDLE line detection (STM32L4/F7/H7/G4/F4)
#define UART_RX_BUF_SIZE 256
#define UART_RX_HALF_SIZE (UART_RX_BUF_SIZE / 2)
static uint8_t uart_rx_buf[UART_RX_BUF_SIZE];
static volatile uint16_t rx_write_idx = 0;
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) {
if (huart->Instance == USART2) {
// Size is the absolute number of bytes received since reception started.
// In circular mode, it corresponds to our current write index.
rx_write_idx = (Size == UART_RX_BUF_SIZE) ? 0 : Size;
}
}
void uart_dma_init_circular(UART_HandleTypeDef *huart) {
// Start circular DMA reception with IDLE line detection.
// HAL_UARTEx_ReceiveToIdle_DMA internally enables the IDLE interrupt.
HAL_UARTEx_ReceiveToIdle_DMA(huart, uart_rx_buf, UART_RX_BUF_SIZE);
// Disable DMA half-transfer interrupt if not needed
// __HAL_DMA_DISABLE_IT(huart->hdmarx, DMA_IT_HT);
}

Trade-off: Requires IDLE line detection (STM32F4/F7/H7/L4+) or manual HT/TC handling. Slightly more complex buffer management.

Approach 2: Increase DMA Priority and Reduce Contention

If circular DMA is not an option (older F1/F0), raise the UART RX DMA channel priority to Very High and isolate it from other DMA traffic.

// STM32F1xx LL - Set DMA channel priority to Very High
LL_DMA_SetChannelPriorityLevel(DMA1, LL_DMA_CHANNEL_5, LL_DMA_PRIORITY_VERYHIGH);
// Ensure no other Very High priority channels exist
// Move ADC/SPI DMA to High or Medium

Trade-off: Only helps if contention is the bottleneck. Does not fix baud-rate-exceeds-latency cases.

Approach 3: Hardware Flow Control (RTS/CTS)

The most robust fix: let the UART signal the sender to pause when DMA cannot keep up. Requires RTS/CTS pins and a cooperating sender.

// Enable hardware flow control in HAL
huart->Init.HwFlowCtl = UART_HWCONTROL_RTS_CTS;
HAL_UART_Init(huart);
// In LL
LL_USART_SetHWFlowCtrl(USART2, LL_USART_HWCONTROL_RTS_CTS);

Trade-off: Requires 2 extra pins and a sender that respects CTS. Not viable for fixed-pin designs or legacy protocols.

Approach 4: Reduce Interrupt Latency

If the TC interrupt handler is the bottleneck, optimize it:

  • Move DMA reconfiguration out of the ISR (use circular mode)
  • Keep critical sections short
  • Use NVIC_SetPriority to ensure UART DMA TC interrupt has appropriate priority
  • Avoid HAL_Delay() or blocking calls in ISRs

Complete Recovery Sequence

When an overrun is detected, the UART peripheral enters a locked error state. To resume reception, a specific recovery sequence must be executed. The exact steps depend on whether you are using the HAL or LL drivers.

Bare Metal / LL Driver Recovery

For bare metal or LL driver implementations, you must manually execute the full sequence in order. Skipping any step (such as re-enabling DMA before clearing ORE) can leave the peripheral in a stuck state.

Note on V1 vs V2 USART: STM32F1/F4 use V1 USART (SR/DR registers), where ORE is cleared by reading SR then DR. STM32F7/H7/L4/G4 use V2 USART (ISR/ICR/RDR registers), where ORE is cleared by writing to ICR. The example below targets V2. Similarly, STM32F1 uses DMA channels (LL_DMA_DisableChannel), while F4/F7/H7 use DMA streams (LL_DMA_DisableStream).

// LL-based recovery (V2 USART: STM32F7/H7/L4/G4 with DMA streams)
// For V1 USART families (F1/F4), replace LL_DMA_*Stream* calls with
// LL_DMA_*Channel* equivalents, and clear ORE by reading SR then DR.
void uart_dma_overrun_recovery_ll(USART_TypeDef *USARTx, DMA_TypeDef *DMAx, uint32_t Stream) {
// 1. Disable UART DMA RX Request
LL_USART_DisableDMAReq_RX(USARTx);
// 2. Clear Overrun Error flag and flush data register
LL_USART_ClearFlag_ORE(USARTx); // Writes 1 to ICR.ORECF
while (LL_USART_IsActiveFlag_RXNE(USARTx)) {
volatile uint32_t dummy = LL_USART_ReceiveData8(USARTx);
(void)dummy;
}
// 3. Clear other error flags (optional but recommended)
LL_USART_ClearFlag_PE(USARTx);
LL_USART_ClearFlag_FE(USARTx);
LL_USART_ClearFlag_NE(USARTx);
// 4. Abort DMA stream
LL_DMA_DisableStream(DMAx, Stream);
while (LL_DMA_IsEnabledStream(DMAx, Stream)) {} // Wait for disable
// 5. Reconfigure DMA
// (Assuming addresses and directions are already configured)
LL_DMA_SetDataLength(DMAx, Stream, UART_RX_BUF_SIZE);
LL_DMA_EnableStream(DMAx, Stream);
// 6. Re-enable UART DMA RX Request
LL_USART_EnableDMAReq_RX(USARTx);
}

HAL Driver Recovery

The STM32 HAL simplifies this significantly. When an overrun occurs, the HAL_UART_IRQHandler automatically clears the ORE flag, stops the DMA transfer, and calls HAL_UART_ErrorCallback with the HAL_UART_ERROR_ORE error code.

You do not need to manually flush registers or clear flags. The only requirement is to restart the DMA reception.

// HAL-based recovery
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) {
if (huart->ErrorCode & HAL_UART_ERROR_ORE) {
// The HAL IRQ handler has already cleared the ORE flag and aborted the DMA.
// We only need to restart the DMA reception.
// Example: Restarting circular DMA with IDLE line detection
HAL_UARTEx_ReceiveToIdle_DMA(huart, uart_rx_buf, UART_RX_BUF_SIZE);
// Optional: track statistics
uart_stats.overrun_count++;
}
// Clear error code after handling
huart->ErrorCode = HAL_UART_ERROR_NONE;
}

Detection: HAL vs LL

HAL Detection

As shown above, the HAL handles detection in its UART_IRQHandler and defers user logic to the HAL_UART_ErrorCallback.

LL Detection

// In your USART IRQ handler
void USART2_IRQHandler(void) {
if (LL_USART_IsActiveFlag_ORE(USART2)) {
// Overrun detected - handle immediately
uart_dma_overrun_recovery_ll(USART2, DMA1, LL_DMA_STREAM_5);
}
// Handle other flags (RXNE, IDLE, TC, etc.)
}

LL gives you direct register access. The ORE flag is in USART_ISR (or USART_SR for V1) and cleared via USART_ICR (or by reading SR and DR).

Prevention Strategies

1. Size the Buffer for Worst-Case Latency

Calculate the maximum bytes that can arrive during the longest DMA service interruption:

// Worst-case latency = max ISR latency + max critical section + DMA bus contention
// Example: 500 us at 921600 baud = 46 bytes
// Buffer should be at least 2x worst-case
#define UART_RX_BUF_SIZE 256 // 5.5x margin

2. Use IDLE Line Detection for Variable-Length Packets

IDLE line detection triggers an interrupt when the line stays high for >1 frame time. This works with circular DMA to deliver packets of any length without timeout-based polling.

// HAL: Use ReceiveToIdle_DMA
HAL_UARTEx_ReceiveToIdle_DMA(&huart2, uart_rx_buf, UART_RX_BUF_SIZE);
// LL: Enable IDLE interrupt
LL_USART_EnableIT_IDLE(USART2);

3. Monitor Overrun Count in Production

Add a diagnostic counter and log/alert when it increments:

typedef struct {
uint32_t overrun_count;
uint32_t framing_error_count;
uint32_t noise_error_count;
uint32_t parity_error_count;
} uart_stats_t;
static uart_stats_t uart_stats = {0};
// In error callback/IRQ
if (error == ORE) uart_stats.overrun_count++;
// Periodic health check task
void uart_health_check(void) {
if (uart_stats.overrun_count > last_reported_overrun) {
LOG_WARN("UART overrun detected: %lu total", uart_stats.overrun_count);
last_reported_overrun = uart_stats.overrun_count;
}
}

4. Verify DMA Configuration

Common misconfigurations that cause overrun:

  • Wrong DMA direction: Must be PeripheralToMemory
  • Incorrect data size: Byte for 8-bit UART, HalfWord for 9-bit
  • Missing circular mode: For continuous reception
  • DMA stream/channel mismatch: Verify against datasheet (e.g., USART2_RX on DMA1 Stream 5 / Channel 4 for F4)

Verification Steps

1. Stress Test at Maximum Baud Rate

// Test harness: blast data at max baud for 60 seconds
void uart_stress_test(void) {
const uint32_t test_duration_ms = 60000;
uint32_t start = HAL_GetTick();
uint32_t bytes_sent = 0;
uint32_t bytes_received = 0;
while (HAL_GetTick() - start < test_duration_ms) {
// Fill TX buffer with known pattern
for (int i = 0; i < 256; i++) {
tx_buf[i] = (bytes_sent + i) & 0xFF;
}
HAL_UART_Transmit_DMA(&huart2, tx_buf, 256);
while (HAL_UART_GetState(&huart2) == HAL_UART_STATE_BUSY_TX) {}
bytes_sent += 256;
// Check RX buffer
bytes_received += uart_get_received_count();
}
printf("Sent: %lu, Received: %lu, Lost: %lu, Overruns: %lu\n",
bytes_sent, bytes_received, bytes_sent - bytes_received,
uart_stats.overrun_count);
}

Expected result: zero overruns at target baud rate under load.

2. Inject Artificial Latency

Add a controlled delay in the DMA TC interrupt to verify recovery works:

void DMA1_Stream5_IRQHandler(void) {
if (LL_DMA_IsActiveFlag_TC5(DMA1)) {
LL_DMA_ClearFlag_TC5(DMA1);
// Artificial 2 ms delay to force overrun at high baud.
// WARNING: HAL_Delay depends on SysTick. This will deadlock if this
// ISR priority is >= SysTick priority. Only use for testing.
HAL_Delay(2); // ONLY FOR TESTING
// Re-arm circular DMA...
}
}

Verify the recovery sequence restores communication without data loss beyond the expected overrun window.

3. Check Register State After Recovery

After recovery, verify:

  • USART_CR3.DMAR = 1 (DMA reception enabled)
  • USART_ISR.ORE = 0 or USART_SR.ORE = 0 (Overrun flag cleared)
  • DMA_SxCR.EN = 1 (DMA stream enabled)
  • DMA_SxNDTR = buffer_size (Counter reloaded)

UART DMA Data Flow

+----------------------------------------------------------------------+
| UART DMA RECEPTION FLOW |
+----------------------------------------------------------------------+
| |
| EXTERNAL SENDER STM32 UART PERIPHERAL |
| +----------------+ +---------------------+ |
| | TX Shift Reg | | RX Shift Register | |
| +-------+--------+ +----------+----------+ |
| | | |
| | Serial Bit Stream | |
| +-------------------------->| |
| v |
| +------+------+ |
| | RDR/DR | |
| +------+------+ |
| | |
| | DMA Request (RXNE=1) |
| v |
| +------+------+ |
| | DMA | |
| | Channel | |
| +------+------+ |
| | |
| | AHB/APB Bus Transfer |
| v |
| +------+------+ |
| | SRAM Buffer | |
| +-------------+ |
| |
| OVERRUN CONDITION: |
| +--------------------------------------------------------------+ |
| | 1. Byte N completes reception in Shift Register | |
| | 2. Byte N-1 is still in RDR/DR (DMA hasn't read it yet) | |
| | 3. Hardware sets ORE=1 (Overrun Error) | |
| | 4. Byte N is LOST, Byte N-1 is retained in RDR/DR | |
| +--------------------------------------------------------------+ |
| |
+----------------------------------------------------------------------+

Recovery Sequence State Machine

+----------------------------------------------------------------------+
| UART DMA OVERRUN RECOVERY |
| STATE MACHINE (BARE METAL / LL) |
+----------------------------------------------------------------------+
| |
| NORMAL OPERATION |
| | |
| | Overrun Detected (ISR.ORE = 1 or SR.ORE = 1) |
| v |
| +-----------------+ |
| | DISABLE DMA RX | LL_USART_DisableDMAReq_RX() |
| | (CR3.DMAR=0) | |
| +--------+--------+ |
| | |
| | Flush stale data |
| v |
| +-----------------+ |
| | CLEAR ORE FLAG | Write 1 to ICR.ORECF (V2) or |
| | | Read SR then DR (V1) |
| +--------+--------+ |
| | |
| | Abort & Reinit DMA |
| v |
| +-----------------+ |
| | REINIT DMA | LL_DMA_DisableStream() |
| | (Stream/Channel)| [Wait for EN=0] -> Reconfig -> Enable |
| +--------+--------+ |
| | |
| | Re-enable DMA RX |
| v |
| +-----------------+ |
| | ENABLE DMA RX | LL_USART_EnableDMAReq_RX() |
| | (CR3.DMAR=1) | |
| +--------+--------+ |
| | |
| | SUCCESS |
| v |
| NORMAL OPERATION |
| |
+----------------------------------------------------------------------+

Summary

UART DMA overrun on STM32 is a timing violation, not a software defect. The fix requires:

  1. Detection: Check HAL_UART_ERROR_ORE in HAL or USART_ISR.ORE in LL
  2. Recovery: Disable DMA —> Flush RDR —> Clear ORE —> Reinit DMA —> Re-enable DMA (exact order)
  3. Prevention: Circular DMA with double buffering, hardware flow control, proper buffer sizing, and interrupt latency reduction
  4. Verification: Stress test at max baud, inject latency, verify register state post-recovery

The circular DMA + IDLE line detection approach is the most robust for variable-length protocols. For fixed-packet protocols with RTS/CTS available, hardware flow control eliminates overrun entirely. Always monitor overrun counters in production — a non-zero count indicates a latent timing margin issue that will surface under load.

References

  1. STMicroelectronics, “STM32F4xx Reference Manual RM0090”, Section 30.5.10 “Overrun error”, Rev 21, 2023.
  2. STMicroelectronics, “STM32F4xx HAL Driver UART API”, UM1725, Section “UART DMA Reception”, 2024.
  3. ARM Limited, “Cortex-M4 Devices Generic User Guide”, Section 4.2 “Nested Vectored Interrupt Controller”, 2010.
  4. STMicroelectronics, “AN3155: USART protocol used in the STM32 bootloader”, Section “DMA Configuration”, 2021.
  5. Joseph Yiu, “The Definitive Guide to ARM Cortex-M3 and Cortex-M4 Processors”, 3rd Ed., Newnes, 2014.

Frequently Asked Questions

What causes UART DMA overrun errors on STM32?

UART DMA overrun occurs when the UART shift register completes reception of a new byte while the receive data register (RDR or DR) is still full. This happens when DMA cannot keep up with the incoming data rate, typically due to high baud rates, DMA channel contention, or the CPU not servicing the DMA interrupts in time.

How do I detect an overrun error in HAL vs LL drivers?

In HAL, check the UART handle's ErrorCode field for HAL_UART_ERROR_ORE inside the error callback. In LL, read the ISR (or SR for older families) and test the ORE bit. LL requires manually clearing the flag by writing to ICR or reading SR/DR.

What is the correct recovery sequence for UART DMA overrun?

In LL: (1) Disable UART DMA RX, (2) Flush RDR/DR and clear ORE, (3) Abort and reinitialize the DMA stream, (4) Re-enable UART DMA RX. In HAL, the IRQ handler automatically clears the error and aborts DMA, so you only need to call the DMA receive function again to restart it.

Why does increasing DMA priority not always fix overrun?

DMA priority only affects arbitration between DMA channels. If the overrun is caused by CPU interrupt latency (blocking the DMA TC interrupt) or the UART baud rate exceeding the DMA maximum transfer rate, raising DMA priority has no effect. The bottleneck must be identified first.

Can I use circular DMA mode to prevent overrun?

Circular DMA mode helps by continuously cycling through the buffer, but it does not eliminate overrun if the producer (UART) outruns the consumer (application reading the buffer). You must still size the buffer for worst-case latency and implement flow control or backpressure.

Tags

stm32uartdmaoverrunhalllerror-recovery

Share


Previous Article
Managing Vendor SDK Updates Without Breaking Embedded Builds
Jithin Tom

Jithin Tom

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

Related Posts

Optimizing Cortex-M Tail-Chaining for Sub-Microsecond Latency
Optimizing Cortex-M Tail-Chaining for Sub-Microsecond Latency
August 15, 2026
5 min
© 2026, All Rights Reserved.
Powered By Netlyft

Quick Links

Advertise with usAbout UsContact Us

Social Media