Skip to content

cpu/rp2350_common/periph/uart: enable RX/TX FIFO and fix related ISR/TX flow#22297

Open
cakirmert wants to merge 3 commits into
RIOT-OS:masterfrom
cakirmert:rp2350-uart-fifo
Open

cpu/rp2350_common/periph/uart: enable RX/TX FIFO and fix related ISR/TX flow#22297
cakirmert wants to merge 3 commits into
RIOT-OS:masterfrom
cakirmert:rp2350-uart-fifo

Conversation

@cakirmert
Copy link
Copy Markdown

Contribution description

This change fixes the RP2350 UART driver in
cpu/rp2350_common/periph/uart.c. The current driver leaves the UART
in character mode, with the FIFO disabled. In that mode the receiver
has only a 1-byte holding register, so any time the RX path takes
longer than one byte time (about 87 microseconds at 115200 baud) the
next byte overwrites the previous one and is lost. This is the root
cause; everything else in the diff follows from enabling the FIFO.

Enable the RX and TX FIFOs. Setting FEN in UARTLCR_H switches
the UART from character mode to FIFO mode, giving RX a 32-byte
buffer instead of one byte (RP2350 datasheet section 12.1.3).

Add the RX timeout interrupt. With FIFO mode, the RX interrupt
fires when the FIFO crosses its trigger level (about 4 bytes). The
trailing bytes of a burst that don't reach trigger level need to come
out somehow; RTIM in UARTIMSC raises an interrupt when the FIFO
holds data for about 32 baud-clocks. Without RTIM, the last few bytes
of a frame can sit in the FIFO indefinitely (section 12.1.6).

Drain the whole FIFO inside the ISR. The previous ISR read one
byte per fire. With a 32-byte FIFO and a fast sender, the handler now
loops while UARTFR.RXFE is clear so it consumes every byte that has
arrived.

Rewrite the TX polling loop. The previous code polled
UARTRIS.TXRIS. In FIFO mode that bit is an edge condition meaning
"FIFO crossed the trigger threshold downward", which may never happen
for short writes and can hang the loop. The new loop uses two level
conditions from UARTFR: wait while TXFF is set for each byte
(don't write into a full FIFO), then wait for BUSY to clear after
the last byte (so the function returns only when the bytes have left
the wire). These work in both FIFO and character mode (section
12.1.8).

Replace puts() in the ISR error path with a counter. When the
UART flags a parity, framing, or break error in UARTDR, the
previous code called puts() from inside the ISR. At 115200 baud the
old 56-byte string takes about 5 ms to send. An ISR that stalls for
5 ms misses tens of subsequent bytes. This change is preventive
rather than a fix for an observed bug, but the structural problem is
unavoidable: ISR time has to stay bounded. The replacement is a
volatile uint32_t rp2350_uart_rx_error_count symbol that
applications can read for diagnostics, and the faulty byte is still
passed to the rx_cb so framing alignment is preserved (bit-level
corruption is recoverable at the protocol layer; byte loss desyncs
framing and is much harder to recover from).

Inline comments in the diff carry the same datasheet references
(sections 12.1.3, 12.1.6, 12.1.8). No public API change. The new
counter symbol is the only addition outside the existing API.

The PR also adds tests/cpu/rp2350_uart_fifo - a small testrunner
test that exercises the FIFO + ISR paths via external loopback.
The test sends a 64-byte burst on UART1 and verifies all 64 bytes
come back through the rx_cb path. Requires a physical jumper from
GP8 (UART1 TX) to GP9 (UART1 RX) on the same board, matching the
convention used by tests/periph/uart. Without the FIFO enabled,
the 1-byte RX holding register cannot absorb a 64-byte burst at
115200 baud; bytes drop and the test fails. With the fix it passes.

Testing procedure

The PR ships an automated test in tests/cpu/rp2350_uart_fifo. To
run it, place a physical jumper from GP8 (UART1 TX) to GP9 (UART1
RX) on a Pi Pico 2 H, then:

BOARD=rpi-pico-2-arm make -C tests/cpu/rp2350_uart_fifo flash test

The test sends a 64-byte burst via uart_write and verifies that
all 64 bytes come back through the rx_cb. Expected output:

rp2350 uart fifo loopback test
PASS: 64 bytes round-tripped via external loopback
ALL TESTS PASSED

Why this exercises the fix: without the RX FIFO enabled, a 64-byte
burst into the 1-byte holding register at 115200 baud cannot all be
captured. Bytes drop. With the FIFO and the full ISR drain in this
PR, all 64 bytes are buffered and delivered.

Verified on a Pi Pico 2 H (RP2350) for both rpi-pico-2-arm and
rpi-pico-2-riscv. The driver lives in shared cpu/rp2350_common
code so the fix applies identically to either architecture; the
test's Makefile sets DEVELHELP ?= 0 to keep the RISC-V port's
per-trap debug prints (which would otherwise add ISR latency at
115200 baud and confound the stress test) out of the build.

The driver was additionally exercised under sustained real-world
load on Pico 2 H during firmware development before this PR was
opened.

Issues/PRs references

Re-submission of the UART FIFO fix from the now-closed #22269,
self-authored per the note on that PR.

Declaration of AI-Tools / LLMs usage:

AI-Tools / LLMs that were used are:

  • Claude Code (Anthropic). Used as a reference and accelerator;
    hardware-tested and committed by me.

@github-actions github-actions Bot added Area: tests Area: tests and testing framework Area: cpu Area: CPU/MCU ports labels May 13, 2026
* sub-trigger bytes of a burst sit in the FIFO until enough new
* bytes arrive, which never happens at the end of a frame.
* See RP2350 datasheet section 12.1.6 Interrupts. */
dev->UARTIMSC = UART_UARTIMSC_RXIM_BITS | UART_UARTIMSC_RTIM_BITS;
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without RTIM, sub-trigger bytes (the last few bytes of a burst that don't fill the FIFO past trigger level) sit indefinitely. RTIM raises an interrupt when the FIFO has data for ~32 baud-clocks, so trailing bytes get drained.

* can rely on "all bytes have left the wire" once uart_write
* returns. See RP2350 datasheet section 12.1.8 UARTFR. */
for (size_t i = 0; i < len; i++) {
while (dev->UARTFR & UART_UARTFR_TXFF_BITS) { }
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previous code polled UARTRIS.TXRIS, which in FIFO mode is an edge condition meaning 'FIFO crossed trigger threshold downward.' For short writes that threshold may never be crossed, so the polling loop hung. UARTFR.TXFF/UARTFR.BUSY are level conditions that work in both FIFO and character mode.

* path. The faulty byte is still passed to rx_cb so the protocol
* layer can decide what to do; bit-level corruption is recoverable
* there, whereas byte loss is not. */
volatile uint32_t rp2350_uart_rx_error_count;
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this matters: the previous puts() in the ISR error path printed a 56-byte string. At 115200 baud that takes ~4.9 ms - the inter-byte budget at 115200 is ~87 µs - so a single fire would stall the ISR long enough to drop tens of subsequent bytes. The counter is bounded-time. The faulty byte still goes to rx_cb so the protocol layer can decide; bit-level corruption is recoverable by signature/CRC retries, byte loss desyncs framing and is much harder to recover from.

@cakirmert
Copy link
Copy Markdown
Author

@AnnsAnns re-submitting the UART FIFO fix from #22269 as you mentioned, self-authored this time. UART driver fix plus a tests/cpu/rp2350_uart_fifo external-loopback test that exercises the FIFO + ISR path on a single Pico with a GP8↔GP9 jumper. Hardware-verified on both rpi-pico-2-arm and rpi-pico-2-riscv.

@cakirmert cakirmert marked this pull request as ready for review May 13, 2026 22:55
cakirmert added 2 commits May 23, 2026 10:34
Enable the RP2350 UART RX/TX FIFOs and drain pending RX data fully from the ISR. Also enable RX timeout interrupts, poll TX with UARTFR.TXFF/BUSY, and keep ISR error reporting out of the hot path.
Send a 64-byte burst over UART_DEV(1) and verify all bytes return through the RX callback path. The test uses RP2350 UART internal loopback, so it covers FIFO buffering plus full ISR drain without depending on external UART wiring.
Restore the UART FIFO test to the external GP8-GP9 loopback path instead of enabling RP2350 internal UART loopback in the test. The external loopback is the hardware path already proven on the ARM board through the ESP32-S3 console bridge; the RISC-V path still needs a separate retest after BOOTSEL/jumper setup is confirmed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: cpu Area: CPU/MCU ports Area: tests Area: tests and testing framework

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant