cpu/rp2350_common/periph/uart: enable RX/TX FIFO and fix related ISR/TX flow#22297
cpu/rp2350_common/periph/uart: enable RX/TX FIFO and fix related ISR/TX flow#22297cakirmert wants to merge 3 commits into
Conversation
| * 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; |
There was a problem hiding this comment.
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) { } |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
|
@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. |
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.
019fe0c to
9be187a
Compare
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.
Contribution description
This change fixes the RP2350 UART driver in
cpu/rp2350_common/periph/uart.c. The current driver leaves the UARTin 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_Hswitchesthe 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
UARTIMSCraises an interrupt when the FIFOholds 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.RXFEis clear so it consumes every byte that hasarrived.
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 whileTXFFis set for each byte(don't write into a full FIFO), then wait for
BUSYto clear afterthe 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 theUART flags a parity, framing, or break error in
UARTDR, theprevious code called
puts()from inside the ISR. At 115200 baud theold 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_countsymbol thatapplications can read for diagnostics, and the faulty byte is still
passed to the
rx_cbso framing alignment is preserved (bit-levelcorruption 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 testrunnertest 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. Torun it, place a physical jumper from GP8 (UART1 TX) to GP9 (UART1
RX) on a Pi Pico 2 H, then:
The test sends a 64-byte burst via
uart_writeand verifies thatall 64 bytes come back through the rx_cb. Expected output:
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_commoncode so the fix applies identically to either architecture; the
test's Makefile sets
DEVELHELP ?= 0to keep the RISC-V port'sper-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:
hardware-tested and committed by me.