embassy-rs/embassy · critical

BufferedUarte UART overrun

Error message

BufferedUarte UART overrun

What it means

The BufferedUarte interrupt handler reads the UARTE ERRORSRC register after an error event and panics if the overrun bit is set, meaning a received byte arrived while the RX FIFO/DMA path was full and was lost. Because data was irrecoverably dropped, the driver treats overrun as fatal rather than silently returning truncated input.

Solutions

  1. Increase the internal buffer size passed when creating the BufferedUarte (and spawn the reader task with higher priority)
  2. Ensure a task continuously calls `read()` so the buffer is drained promptly; avoid long-lived locks or blocking work on the reader task
  3. Enable hardware flow control (CTS) so the peer stops sending when the buffer fills
  4. Lower the baud rate to match the achievable processing throughput

Example fix

// before
let mut rx_buf = [0u8; 32];
let mut uarte = BufferedUarte::new(p.UARTE0, p.TIMER0, p.PPI_CH0, p.PPI_CH1, irq, p.PIN_8, p.PIN_11, ParityBit::NONE, Parity::EXCLUDED, Baudrate::BAUD1M, &mut rx_buf);
// after
static RX_BUFFER: StaticCell<[u8; 1024]> = StaticCell::new();
let rx_buf = &mut *RX_BUFFER.init([0u8; 1024]);
let mut uarte = BufferedUarte::new(p.UARTE0, p.TIMER0, p.PPI_CH0, p.PPI_CH1, irq, p.PIN_8, p.PIN_11, ParityBit::NONE, Parity::EXCLUDED, Baudrate::BAUD1M, rx_buf);
Defensive patterns

Strategy: try-catch

Try / catch

// Overrun panics in the IRQ handler; it cannot be caught. Guard by sizing buffers
// and draining continuously:
// loop {
//     let n = uarte.read(&mut buf).await?; // keep the consumer always pending on read
//     process(&buf[..n]);
// }
// If the panic is fatal, validate throughput: baud_rate / 10 bytes-per-sec must be
// well below buffer_size drained per scheduling period.

Prevention

When it happens

Trigger: Calling any read API on `BufferedUarte` (or using it via embedded-io traits) while the UART receives bytes faster than the task drains the internal buffer; the ERROR event fires with `errorsrc().overrun() == 1` inside `on_interrupt`.

Common situations: High baud rates (e.g. 1M+ baud) with a small shared read buffer; a blocked consumer task that never calls `read()` while the peer streams continuously; `low_power`/wake configurations where DMA restart is delayed; missing flow control (CTS/RTS) on a fast link.

Related errors


AI-assisted analysis of embassy-rs/embassy@463a07b963 (2026-09-10). Data as JSON: /api/errors/de315c71debbac6e. Report an issue: GitHub.

Appendix: source

Thrown at embassy-nrf/src/buffered_uarte/v2.rs:89

}

impl<U: UarteInstance> interrupt::typelevel::Handler<U::Interrupt> for InterruptHandler<U> {
    unsafe fn on_interrupt() {
        let r = U::regs();
        let ss = U::state();
        let s = U::buffered_state();

        if let Some(mut rx) = unsafe { s.rx_buf.try_writer() } {
            let buf_len = s.rx_buf.len();
            let half_len = buf_len / 2;

            if r.events_error().read() != 0 {
                r.events_error().write_value(0);
                let errs = r.errorsrc().read();
                r.errorsrc().write_value(errs);

                if errs.overrun() {
                    panic!("BufferedUarte UART overrun");
                }
            }

            if r.events_dma().rx().end().read() != 0 {
                //trace!("  irq_rx: endrx");
                r.events_dma().rx().end().write_value(0);

                if s.rx_started.swap(false, Ordering::Relaxed) {
                    // Received some bytes, wake task.
                    let rxed = r.dma().rx().amount().read().amount() as usize;
                    rx.push_done(rxed);
                    ss.rx_waker.wake();
                }
            }

            if !s.rx_started.load(Ordering::Relaxed) {
                let (ptr, len) = rx.push_buf();

View on GitHub (pinned to 463a07b963)