#119492·zephyr

drivers: serial: uart_esp32: default CONFIG_UART_ESP32_RX_FIFO_THRESH causes silent frame truncation on inter-character timing protocols (Modbus RTU)

Author: PragatiGarg-eatonCreated Sep 17, 2026Updated Sep 18, 2026
Labelspriority: lowarea: UARTplatform: ESP32

Describe the bug

In the ESP32 serial driver (drivers/serial/uart_esp32.c), CONFIG_UART_ESP32_RX_FIFO_THRESH defaults to 0x16 (22 bytes).

When using interrupt-driven UART (CONFIG_UART_INTERRUPT_DRIVEN) with protocols that rely on inter-character quiet-time timeouts for frame delineation (such as Modbus RTU / T1.5 timer, SLIP, PPP, or custom serial framing), any incoming packet of length $\ge 22$ bytes is delivered in multiple chunks rather than as a continuous frame.

The first 22 bytes trigger UART_INTR_RXFIFO_FULL mid-frame and are handed up to the application. The remaining bytes ($N - 22$) sit in the hardware FIFO awaiting either another threshold trigger or UART_INTR_RXFIFO_TOUT. Because the protocol's inter-character timer (e.g., Modbus T1.5, which is 1.7 ms at 9600 baud) is shorter than the inter-chunk delivery gap, the upper-layer timer expires before the tail bytes are delivered via RXFIFO_TOUT. This results in:

  1. The first 22 bytes being treated as a complete frame (failing CRC / packet integrity validation and getting discarded).
  2. The remaining bytes being treated as an orphan fragment (also discarded).

Expected behavior

Incoming bytes should be reported per-byte or with configurable granularity so timing-sensitive protocols do not experience artificial intra-frame gaps.

Additionally, CONFIG_UART_ESP32_RX_FIFO_THRESH is currently a global Kconfig setting that applies across all UART instances on the SoC, preventing users from having a high-throughput console (e.g., threshold 22 on UART0) alongside a timing-sensitive protocol (e.g., threshold 1 on UART1).

Impact

  • Silent packet drops on Modbus RTU command interpreters, and quiet-time framing protocols when frame size $\ge 22$ bytes.
  • Discrepancy in behavior between ESP32 and other Zephyr UART drivers (e.g., STM32), which notify per character by default.

Regression

  • This is a regression.

Steps to reproduce

  1. Build an application with CONFIG_UART_INTERRUPT_DRIVEN=y and CONFIG_UART_ESP32=y on any ESP32 target (tested on esp32c6_devkitc).
  2. Implement an inter-character timer (e.g. Modbus RTU server running at 9600 or 115200 baud).
  3. Send Modbus RTU Function Code 16 (Write Multiple Registers) frames of varying lengths:
    • 6 registers (21 bytes): Packet $\le 21$ bytes -> RXFIFO_FULL does not trigger -> line idle triggers RXFIFO_TOUT -> all 21 bytes handed to app at once -> Frame accepted / CRC PASS.
    • 7 registers (23 bytes): Packet $\ge 22$ bytes -> RXFIFO_FULL triggers at byte 22 -> 22 bytes handed to app -> T1.5 timer starts and expires before RXFIFO_TOUT delivers byte 23 -> CRC FAIL / Frame dropped silently.
  4. Set CONFIG_UART_ESP32_RX_FIFO_THRESH=1 in prj.conf -> Re-test 7+ register frames -> All frames pass consistently.

Relevant log output

bash

Impact

Functional Limitation – Some features not working as expected, but system usable.

Environment

  • OS: Linux
  • Zephyr version: v3.7.1 / main
  • SoC/Board: esp32c6_devkitc (and applicable to all ESP32 variants using uart_esp32.c)
  • Toolchain: Zephyr SDK

Additional Context

@sylvioalves Please let me know whether you agree with the issue; this functionality doesn't meet the expectations and worth fixing.

Note: If so, I am actively working on a PR to address this issue and currently running it through validation. I can submit the PR shortly.

Source: zephyrproject-rtos/zephyr