drivers: serial: uart_esp32: default CONFIG_UART_ESP32_RX_FIFO_THRESH causes silent frame truncation on inter-character timing protocols (Modbus RTU)
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:
- The first 22 bytes being treated as a complete frame (failing CRC / packet integrity validation and getting discarded).
- 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
- Build an application with
CONFIG_UART_INTERRUPT_DRIVEN=yandCONFIG_UART_ESP32=yon any ESP32 target (tested onesp32c6_devkitc). - Implement an inter-character timer (e.g. Modbus RTU server running at 9600 or 115200 baud).
- Send Modbus RTU Function Code 16 (Write Multiple Registers) frames of varying lengths:
- 6 registers (21 bytes): Packet $\le 21$ bytes ->
RXFIFO_FULLdoes not trigger -> line idle triggersRXFIFO_TOUT-> all 21 bytes handed to app at once -> Frame accepted / CRC PASS. - 7 registers (23 bytes): Packet $\ge 22$ bytes ->
RXFIFO_FULLtriggers at byte 22 -> 22 bytes handed to app -> T1.5 timer starts and expires beforeRXFIFO_TOUTdelivers byte 23 -> CRC FAIL / Frame dropped silently.
- 6 registers (21 bytes): Packet $\le 21$ bytes ->
- Set
CONFIG_UART_ESP32_RX_FIFO_THRESH=1inprj.conf-> Re-test 7+ register frames -> All frames pass consistently.
Relevant log output
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 usinguart_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