HomeAbout UsContact Us

Fixing UART Overrun Errors on NXP i.MX RT with FlexIO

By Jithin Tom
Published in Embedded C/C++
October 13, 2026
5 min read
Fixing UART Overrun Errors on NXP i.MX RT with FlexIO

Table Of Contents

01
Problem Statement
02
Root Cause Analysis
03
Solution Approaches
04
Cortex-M7 Cache Coherency and Alignment Rules
05
Complete Working Code Examples
06
Verification and Diagnostics
07
Summary
08
Related Reading
09
References
10
Frequently Asked Questions

Problem Statement

UART receiver overrun errors are among the most persistent failure modes in embedded real-time systems operating at high baud rates. On NXP i.MX RT crossover microcontrollers (powered by Arm Cortex-M7 cores running up to 600 MHz or 1 GHz), high core clock speeds can create a false sense of security regarding serial communication responsiveness.

Standard serial communication on the i.MX RT platform is handled by the Low-Power UART (LPUART) peripheral. In interrupt-driven architectures, each received character or FIFO watermark threshold triggers a peripheral interrupt request (IRQ) to the Nested Vectored Interrupt Controller (NVIC). If higher-priority interrupts (such as motor control FOC loops, USB, or Ethernet ISRs) or critical sections protected by interrupt-disabling primitives (__disable_irq()) preempt or block the UART ISR longer than the time required to fill the remaining hardware FIFO slots, incoming data overwrites unserviced buffer space.

When an overrun occurs:

  1. The hardware asserts the Receiver Overrun flag (STAT[OR] in LPUART, or SHIFTERR in FlexIO).
  2. The newly arrived byte in the shift register is discarded.
  3. The receiver state machine stalls further data reception into the FIFO until firmware explicitly clears the overrun flag.

In sensor telemetry, industrial fieldbuses, and high-throughput communication protocols, overrun errors produce silent payload truncations, frame checksum failures, and protocol state machine desynchronization.


Root Cause Analysis

The fundamental cause of a UART overrun error is a timing mismatch between wire-speed byte arrival and the system’s servicing latency. Key contributing factors include:

  • High Baud Rates: At 921,600 baud or higher, byte frame intervals shrink to microseconds, leaving minimal temporal margin for interrupt dispatch and context saving.
  • Hardware FIFO Depth & Watermark Configuration: The i.MX RT LPUART module contains a parameterized FIFO buffer (typically 4 entries on LPUART1–LPUART8). If the receive FIFO watermark (WATER[RXWATER]) is misconfigured, the interrupt triggers too late to provide sufficient latency headroom.
  • Interrupt Latency & Priority Inversion: Interrupt latency on Arm Cortex-M7 consists of a deterministic 12-cycle hardware stacking penalty plus variable software delay caused by higher-priority ISR execution or prolonged critical sections.
  • Arm Cortex-M7 D-Cache Hazards: When offloading serial transfers to DMA, reading memory without proper cache invalidation can cause firmware to read stale cache lines or stall the core during bus arbitration.

Asynchronous Serial Timing and FIFO Saturation Limits

Consider standard 8N1 asynchronous serial framing (1 start bit, 8 data bits, no parity bit, 1 stop bit = 10 bits per character frame). The timing equations governing bit duration, character duration, and FIFO fill time are defined as follows:

Baud Rate (B) = 921,600 bits/second
Bit Duration (T_bit) = 1 / B = 1 / 921,600 s = 1.08507 us (1085.07 ns)
Framing (8N1): 1 Start bit + 8 Data bits + 0 Parity bits + 1 Stop bit = 10 bits/frame
Character Duration (T_frame) = 10 * T_bit = 10.8507 us
Hardware FIFO Depth (N_fifo) = 4 bytes (PARAM[RXFIFO] = 4)
Total Buffer Fill Duration (T_fill) = N_fifo * T_frame = 4 * 10.8507 us = 43.4028 us

If the receive watermark is set to zero (WATER[RXWATER] = 0), the first byte placed into an empty FIFO immediately sets the Receive Data Register Full flag (STAT[RDRF]) and generates an interrupt. The CPU has a window of exactly 3 incoming frames plus the stop-bit interval before the 5th frame overflows the FIFO:

Max Allowable Interrupt Latency = 3 * T_frame + T_frame = 43.4028 us

However, if WATER[RXWATER] is set to 3 (triggering when 4 bytes are present), the interrupt is not asserted until the 4th byte is fully received. The CPU has only a single byte frame duration to respond:

Allowable Interrupt Latency Margin = 1 * T_frame = 10.8507 us

At 3,000,000 baud (3 MBaud), this margin collapses catastrophically:

B = 3,000,000 bits/second
T_bit = 1 / 3,000,000 s = 333.33 ns
T_frame = 10 * 333.33 ns = 3.333 us
4-Byte FIFO Saturation Duration = 4 * 3.333 us = 13.333 us
Latency Margin (RXWATER = 3) = 3.333 us

Any ISR or flash wait state delay exceeding 3.33 us triggers a guaranteed hardware overrun.

+-----------------------------------------------------------------------------+
| UART OVERRUN TIMING ANALYSIS (921600 BAUD) |
+-----------------------------------------------------------------------------+
| Baud: 921600 (T_bit = 1.085 us) | Frame: 8N1 (10 bits = 10.85 us per byte) |
| Hardware FIFO: 4 bytes (PARAM[RXFIFO] = 4) | Fill Time: 43.4 us |
| |
| Time ---> 0 us 10.85 us 21.70 us 32.55 us 43.40 us|
| | | | | | |
| RX Pin +-[Start..Stop]+-[Start..Stop]+-[Start..Stop]+-[Start..Stop]+ ... |
| Bytes | Byte 0 | Byte 1 | Byte 2 | Byte 3 |Byte 4|
| +--------------+--------------+--------------+-------------+------+
| |
| Hardware +--------------+--------------+--------------+-------------+ |
| RX FIFO | Slot 0: B0 | Slot 1: B1 | Slot 2: B2 | Slot 3: B3 | FULL!|
| (4 bytes) +--------------+--------------+--------------+-------------+ |
| ^ | |
| | Watermark RXWATER = 0 triggers IRQ | |
| v |
| CPU ISR [------ High-Priority Task / ISR Delay: > 43.4 us -------] OVERRUN|
| Latency STAT[OR|
| assert |
| Byte 4 |
| DROPPED|
+-----------------------------------------------------------------------------+
| OFFLOADING WITH eDMA / FlexIO |
+-----------------------------------------------------------------------------+
| |
| RX Pin +-- Byte 0 --+-- Byte 1 --+-- Byte 2 --+-- Byte 3 --+-- Byte 4 --+|
| +------------+------------+------------+------------+------------+|
| | | | | | |
| Trigger v (eDMA req) v (eDMA req) v (eDMA req) v (eDMA req) v (eDMA)|
| eDMA +----------------------------------------------------------------+|
| Engine | Transfers each byte to SRAM in ~20 ns bus cycles (Zero CPU Load)|
| +----------------------------------------------------------------+|
| | | | | | |
| SRAM +-----+------+-----+------+-----+------+-----+------+-----+------+|
| Ring Buf | Slot 0: B0 | Slot 1: B1 | Slot 2: B2 | Slot 3: B3 | Slot 4: B4 | ...
| (256 B) +------------+------------+------------+------------+------------+|
| |
| Result: Zero Overrun Errors; Hardware Latency << 10.85 us byte period |
+-----------------------------------------------------------------------------+

Solution Approaches

1. Optimize LPUART Watermark and Interrupt Priorities

To maximize CPU response headroom without altering hardware architecture:

  • Set WATER[RXWATER] = 0 so that STAT[RDRF] fires on the first received character.
  • Elevate the LPUART IRQ priority in the NVIC to preempt lower-priority tasks.
  • Keep the ISR strictly focused on reading LPUARTx->DATA and writing into a lock-free circular ring buffer. Avoid parsing or logging within ISR context.

2. Offload LPUART to the eDMA Subsystem

The Enhanced Direct Memory Access (eDMA) controller on i.MX RT routes hardware DMA requests from LPUART (kDmaRequestMuxLPUART1Rx) directly to eDMA channels. Each received character triggers a single-byte minor loop transfer from LPUARTx->DATA to SRAM. The transfer completes in tens of nanoseconds on the internal AXI/AHB bus crossbar, eliminating the CPU interrupt latency bottleneck entirely.

3. Emulate High-Speed UART via FlexIO

When all dedicated LPUART peripherals are allocated, or when pin routing conflicts with high-speed memory interfaces (e.g., SEMC SDRAM or FlexSPI NOR Flash), NXP’s FlexIO (Flexible Input/Output) module provides a programmable hardware state machine.

FlexIO consists of:

  • Independent 32-bit shift registers with parallel buffers (SHIFTBUF[n]).
  • 8-bit dual-baud timers (TIMCMP[n]) operating up to 120 MHz.
  • Flexible pin routing logic mapped to external GPIOs (FXIO_D0 through FXIO_D31).

FlexIO shifts serial UART bits into SHIFTBUF[n] and raises a DMA request (kDmaRequestMuxFlexIO1Request0) upon word reception. By pairing FlexIO with eDMA, developers gain dedicated, jitter-immune UART channels that run reliably at multi-megabaud rates without CPU intervention.

+-----------------------------------------------------------------------------+
| FLEXIO UART RX TO eDMA PIPELINE |
+-----------------------------------------------------------------------------+
| |
| RX Pin (FXIO_D1) |
| | |
| +----------> [FlexIO Timer 1] |
| | - Dual 8-bit baud counter (Baud clock generator) |
| | - Triggered on falling edge (Start bit detection) |
| | - Generates shift pulses on bit centers |
| | | |
| | v (Shift clock) |
| +----------> [FlexIO Shifter 1] |
| - Configured in SMR receive mode |
| - Shifts 8 bits serially from pin FXIO_D1 |
| - Latches complete character into SHIFTBUF[1] |
| | |
| v (Shifter Status Flag: SHIFTSTAT[1] = 1) |
| [DMAMUX Request: kDmaRequestMuxFlexIO1Request0] |
| | |
| v (Hardware DMA Trigger) |
| [eDMA Channel Transfer Control Descriptor (TCD)] |
| - Source: &FLEXIO1->SHIFTBUF[1] |
| - Destination: SRAM Circular Buffer (Non-cacheable) |
| - Clears SHIFTSTAT[1] automatically on read |
| |
+-----------------------------------------------------------------------------+

Cortex-M7 Cache Coherency and Alignment Rules

A critical requirement on the i.MX RT platform is memory coherency. The Cortex-M7 core features a 32 KB L1 Data Cache (D-Cache). Because eDMA writes data directly to physical SRAM over the system bus, the D-Cache does not automatically snoop peripheral bus transactions.

To avoid data corruption or stale reads:

  1. Cache Invalidation: Before reading buffer contents updated by eDMA, firmware must invalidate the corresponding D-Cache lines using SCB_InvalidateDCache_by_Addr().
  2. Cache Line Alignment: Buffers must be aligned to 32-byte cache line boundaries (SDK_ALIGN(buf, 32)) and sized to a multiple of 32 bytes. This prevents false sharing, where invalidating a cache line inadvertently corrupts adjacent variables.
  3. Non-Cacheable MPU Regions: Alternatively, place DMA buffers in a dedicated non-cacheable SRAM section configured via the Arm Memory Protection Unit (MPU).

Complete Working Code Examples

Example 1: LPUART with Circular eDMA and Idle-Line Detection

This example configures LPUART1 on i.MX RT using the MCUXpresso SDK. Incoming data is transferred directly into an SRAM ring buffer via eDMA channel 0. The LPUART Idle-Line interrupt detects frame completions without byte-by-byte CPU overhead.

#include "fsl_common.h"
#include "fsl_lpuart.h"
#include "fsl_edma.h"
#include "fsl_dmamux.h"
#include "fsl_clock.h"
#define LPUART_BASE LPUART1
#define LPUART_CLK_FREQ CLOCK_GetFreq(kCLOCK_UartClk)
#define LPUART_DMA_MUX_SRC kDmaRequestMuxLPUART1Rx
#define LPUART_EDMA_CHANNEL 0U
#define RX_BUFFER_SIZE 256U
/* Align buffer to 32-byte Cortex-M7 D-Cache line boundary */
SDK_ALIGN(static uint8_t s_rxBuffer[RX_BUFFER_SIZE], 32);
static edma_handle_t s_edmaHandle;
static volatile uint32_t s_rxReadIndex = 0U;
void LPUART1_IRQHandler(void)
{
uint32_t status = LPUART_GetStatusFlags(LPUART_BASE);
/* Check and clear Overrun Error Flag */
if ((status & kLPUART_RxOverrunFlag) != 0U)
{
LPUART_ClearStatusFlags(LPUART_BASE, kLPUART_RxOverrunFlag);
}
/* Check for IDLE line detection (end of burst frame) */
if ((status & kLPUART_IdleLineFlag) != 0U)
{
LPUART_ClearStatusFlags(LPUART_BASE, kLPUART_IdleLineFlag);
/* Determine current write position from remaining major loop count */
uint32_t remainingBytes = EDMA_GetRemainingMajorLoopCount(DMA0, LPUART_EDMA_CHANNEL);
uint32_t currentWriteIndex = RX_BUFFER_SIZE - remainingBytes;
/* Invalidate D-Cache to ensure CPU reads updated SRAM data */
SCB_InvalidateDCache_by_Addr((uint32_t *)s_rxBuffer, RX_BUFFER_SIZE);
while (s_rxReadIndex != currentWriteIndex)
{
uint8_t receivedByte = s_rxBuffer[s_rxReadIndex];
s_rxReadIndex = (s_rxReadIndex + 1U) % RX_BUFFER_SIZE;
(void)receivedByte; /* Forward to application queue or parser */
}
}
SDK_ISR_EXIT_BARRIER;
}
void init_lpuart_edma(void)
{
lpuart_config_t uartConfig;
edma_config_t edmaConfig;
edma_transfer_config_t transferConfig;
/* 1. Initialize DMAMUX and route LPUART1 RX to eDMA Channel 0 */
DMAMUX_Init(DMAMUX0);
DMAMUX_SetSource(DMAMUX0, LPUART_EDMA_CHANNEL, LPUART_DMA_MUX_SRC);
DMAMUX_EnableChannel(DMAMUX0, LPUART_EDMA_CHANNEL);
/* 2. Initialize eDMA engine */
EDMA_GetDefaultConfig(&edmaConfig);
EDMA_Init(DMA0, &edmaConfig);
EDMA_CreateHandle(&s_edmaHandle, DMA0, LPUART_EDMA_CHANNEL);
/* 3. Configure circular transfer control descriptor (TCD) */
EDMA_PrepareTransfer(&transferConfig,
(void *)&LPUART_BASE->DATA,
sizeof(uint8_t),
(void *)s_rxBuffer,
sizeof(uint8_t),
sizeof(uint8_t),
RX_BUFFER_SIZE,
kEDMA_PeripheralToMemory);
EDMA_SubmitTransfer(&s_edmaHandle, &transferConfig);
/* Configure circular destination address reload upon major loop complete */
DMA0->TCD[LPUART_EDMA_CHANNEL].DOFF = 1;
DMA0->TCD[LPUART_EDMA_CHANNEL].DLAST_SGA = -(int32_t)RX_BUFFER_SIZE;
EDMA_EnableAutoStopRequest(DMA0, LPUART_EDMA_CHANNEL, false);
/* 4. Configure LPUART1 */
LPUART_GetDefaultConfig(&uartConfig);
uartConfig.baudRate_Bps = 921600U;
uartConfig.enableTx = true;
uartConfig.enableRx = true;
uartConfig.rxFifoWatermark = 0U; /* Trigger DMA on first available byte */
LPUART_Init(LPUART_BASE, &uartConfig, LPUART_CLK_FREQ);
/* Enable RX DMA request and Idle-Line Interrupt */
LPUART_EnableRxDMA(LPUART_BASE, true);
LPUART_EnableInterrupts(LPUART_BASE, kLPUART_IdleLineInterruptEnable);
EnableIRQ(LPUART1_IRQn);
/* Start eDMA channel reception */
EDMA_StartTransfer(&s_edmaHandle);
}

Example 2: FlexIO UART Receiver with eDMA Offload

This example configures the FlexIO module as an emulated UART receiver operating with eDMA. Incoming bytes on pin FXIO_D1 are transferred directly to SRAM without CPU intervention.

#include "fsl_common.h"
#include "fsl_flexio_uart.h"
#include "fsl_edma.h"
#include "fsl_dmamux.h"
#include "fsl_clock.h"
#define BOARD_FLEXIO_BASE FLEXIO1
#define FLEXIO_UART_TX_PIN 0U /* FXIO_D0 */
#define FLEXIO_UART_RX_PIN 1U /* FXIO_D1 */
#define FLEXIO_EDMA_CHANNEL 1U
#define FLEXIO_DMA_MUX_SRC kDmaRequestMuxFlexIO1Request0
#define RX_BUFFER_SIZE 256U
SDK_ALIGN(static uint8_t s_flexioRxBuffer[RX_BUFFER_SIZE], 32);
static FLEXIO_UART_Type s_flexioUartDev;
static edma_handle_t s_flexioEdmaHandle;
void init_flexio_uart_edma(void)
{
flexio_uart_config_t uartConfig;
edma_config_t edmaConfig;
edma_transfer_config_t transferConfig;
/* 1. Map hardware pins, shifters, and timers to the FlexIO UART instance */
s_flexioUartDev.flexioBase = BOARD_FLEXIO_BASE;
s_flexioUartDev.TxPinIndex = FLEXIO_UART_TX_PIN;
s_flexioUartDev.RxPinIndex = FLEXIO_UART_RX_PIN;
s_flexioUartDev.shifterIndex[0] = 0U; /* TX shifter */
s_flexioUartDev.shifterIndex[1] = 1U; /* RX shifter */
s_flexioUartDev.timerIndex[0] = 0U; /* TX timer */
s_flexioUartDev.timerIndex[1] = 1U; /* RX timer */
/* 2. Configure FlexIO UART parameters */
FLEXIO_UART_GetDefaultConfig(&uartConfig);
uartConfig.baudRate_Bps = 921600U;
uartConfig.enableTx = true;
uartConfig.enableRx = true;
FLEXIO_UART_Init(&s_flexioUartDev, &uartConfig, CLOCK_GetFreq(kCLOCK_Flexio1Clk));
/* 3. Configure DMAMUX and eDMA Channel 1 */
DMAMUX_Init(DMAMUX0);
DMAMUX_SetSource(DMAMUX0, FLEXIO_EDMA_CHANNEL, FLEXIO_DMA_MUX_SRC);
DMAMUX_EnableChannel(DMAMUX0, FLEXIO_EDMA_CHANNEL);
EDMA_GetDefaultConfig(&edmaConfig);
EDMA_Init(DMA0, &edmaConfig);
EDMA_CreateHandle(&s_flexioEdmaHandle, DMA0, FLEXIO_EDMA_CHANNEL);
/* 4. Set up eDMA transfer directly from FlexIO RX buffer address */
uint32_t rxBufferAddr = FLEXIO_UART_GetRxBufferAddress(&s_flexioUartDev);
EDMA_PrepareTransfer(&transferConfig,
(void *)rxBufferAddr,
sizeof(uint8_t),
(void *)s_flexioRxBuffer,
sizeof(uint8_t),
sizeof(uint8_t),
RX_BUFFER_SIZE,
kEDMA_PeripheralToMemory);
EDMA_SubmitTransfer(&s_flexioEdmaHandle, &transferConfig);
/* Enable continuous auto-reload for circular streaming */
DMA0->TCD[FLEXIO_EDMA_CHANNEL].DOFF = 1;
DMA0->TCD[FLEXIO_EDMA_CHANNEL].DLAST_SGA = -(int32_t)RX_BUFFER_SIZE;
EDMA_EnableAutoStopRequest(DMA0, FLEXIO_EDMA_CHANNEL, false);
/* 5. Enable FlexIO RX DMA request and start transfer */
FLEXIO_UART_EnableRxDMA(&s_flexioUartDev, true);
EDMA_StartTransfer(&s_flexioEdmaHandle);
}
bool check_flexio_overrun(void)
{
/* In FlexIO, overrun asserts the Shifter Error flag */
uint32_t errorFlags = FLEXIO_GetShifterErrorFlags(BOARD_FLEXIO_BASE);
uint32_t rxShifterMask = (1U << s_flexioUartDev.shifterIndex[1]);
if ((errorFlags & rxShifterMask) != 0U)
{
/* Clear error flag by writing 1 to the corresponding bit */
FLEXIO_ClearShifterErrorFlags(BOARD_FLEXIO_BASE, rxShifterMask);
return true;
}
return false;
}

Verification and Diagnostics

To rigorously verify that UART overrun errors are fully resolved:

  1. Hardware Register Flag Monitoring:
    • For LPUART: Inspect LPUARTx->STAT. Bit 19 is the OR (Receiver Overrun) flag. Ensure STAT[OR] == 0 during continuous throughput testing.
    • For FlexIO: Inspect FLEXIOx->SHIFTERR. Ensure the bit corresponding to the RX shifter index remains zero.
  2. Logic Analyzer Trigger Instrumentation: Configure an unused high-speed GPIO (e.g., GPIO1_IO02) to toggle high immediately if an overrun flag is detected in firmware. Connect a logic analyzer with channel 0 on UART RX and channel 1 on the debug GPIO. Trigger on the rising edge of the debug pin to capture the exact bus traffic preceding an overrun.
  3. Sustained Burst Stress Testing: Generate continuous pseudo-random binary data (PRBS) or incremental sequences (0x00 through 0xFF) at maximum baud rate using a hardware signal generator or USB-to-UART bridge (e.g., FT232H). Run transfers across 100 million characters and verify zero frame errors, zero overrun assertions, and complete checksum matching.
  4. DMA Bus Contention Profiling: Perform concurrent high-bandwidth operations (e.g., SDRAM benchmarking, LCD display DMA refresh, Ethernet bulk transfers) while receiving UART data to ensure that AXI/AHB crossbar priority arbitrations do not starve the eDMA UART channel.

Summary

UART overrun errors on NXP i.MX RT microcontrollers stem from the mismatch between microsecond byte arrival times and interrupt handling latencies. While adjusting LPUART FIFO watermarks (WATER[RXWATER] = 0) improves CPU response headroom, real-time deterministic communication at high baud rates requires hardware offloading.

By coupling LPUART or FlexIO with the eDMA controller, data bytes are transferred directly into SRAM circular buffers within nanoseconds of arrival. This completely decouples serial reception from CPU load, neutralizes interrupt latency bottlenecks, and guarantees zero-loss serial communication across mission-critical embedded systems.



References

  1. NXP Semiconductors, i.MX RT1060 Processor Reference Manual, Rev. 3, Doc ID: IMXRT1060RM. https://www.nxp.com/docs/en/reference-manual/IMXRT1060RM.pdf
  2. NXP Semiconductors, Emulating UART with the FlexIO on i.MX RT1010, Application Note AN12772. https://www.nxp.com/docs/en/application-note/AN12772.pdf
  3. NXP Semiconductors, Emulating UART by Using FlexIO, Application Note AN5034. https://www.nxp.com/docs/en/application-note/AN5034.pdf
  4. NXP Semiconductors, i.MX RT Series System Memory and eDMA Performance Optimization, Application Note AN12437. https://www.nxp.com/docs/en/application-note/AN12437.pdf
  5. Arm Ltd., Arm Cortex-M7 Devices Generic User Guide, DUI0646C. https://developer.arm.com/documentation/dui0646/latest

Frequently Asked Questions

What causes UART overrun errors on NXP i.MX RT microcontrollers?

UART overrun errors occur when the hardware receive FIFO fills up and an incoming character's stop bit arrives before the CPU or DMA reads the unread data, asserting the receiver overrun flag.

How does FlexIO help prevent UART overrun errors?

FlexIO emulates a UART receiver in hardware and offloads reception to the eDMA controller, transferring bytes directly to SRAM in clock cycles without CPU interrupt latency bottlenecks.

What are the key steps to debug UART overrun errors?

Check the peripheral status flags (STAT[OR] in LPUART or SHIFTERR in FlexIO), tune FIFO watermarks, configure eDMA ring buffers, and implement Arm Cortex-M7 D-Cache invalidation.

Tags

uartnxpflexioembedded-cerror-handling

Share


Previous Article
Embedded Linux Kernel Driver Debugging: Common Issues
Jithin Tom

Jithin Tom

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

Related Posts

Bitwise Modulo in C: Optimizing Embedded Systems
Bitwise Modulo in C: Optimizing Embedded Systems
September 26, 2026
5 min
© 2026, All Rights Reserved.
Powered By Netlyft

Quick Links

Advertise with usAbout UsContact Us

Social Media