Skip to content

Let Ctrl-C stop a UART write that is stuck on flow control - #11578

Open
mikeysklar wants to merge 4 commits into
adafruit:mainfrom
mikeysklar:uart-write-ctrl-c
Open

mikeysklar wants to merge 4 commits into
adafruit:mainfrom
mikeysklar:uart-write-ctrl-c

Conversation

@mikeysklar

Copy link
Copy Markdown
Collaborator

What

Ctrl-C now stops busio.UART.write() on RP2, ESP32 and Zephyr boards.

Why

When CTS is held off, the write waited forever and only a reset recovered.

Fixes #10621.

u = busio.UART(board.GP4, board.GP5, cts=board.GP6, baudrate=9600)
u.write(b"x" * 200)  # CTS held off, then Ctrl-C
Board Test main This PR
Pico W, raspberrypi port CTS off, Ctrl-C 0/10 10/10, stops in 0.2 s
CTS low or no flow control, write completes 6/6 20/20
nRF54LM20 DK, Zephyr CTS off, Ctrl-C 0/10 10/10, stops in 0.1 s
Writes of 200 and 4096 bytes, byte-exact 9/9 30/30
Metro ESP32-S3 Build only builds

Nordic and STM ports still hang; that needs a separate fix.

Written with Claude; tested on hardware by me.

🤖 Generated with Claude Code

Comment thread ports/zephyr-cp/supervisor/serial.c Outdated
@mikeysklar

Copy link
Copy Markdown
Collaborator Author

The console and usb_cdc keep the old write on purpose.

The new write stops on a pending REPL Ctrl-C or reload, which would cut console output short.

It also blocks when the host port is closed, where TinyUSB ports drop data.

It's Zephyr-only, so I renamed it to zephyr_uart_write_blocking() and explained this in its comment, in 6705695.

@mikeysklar

Copy link
Copy Markdown
Collaborator Author

Correction on your first question: the USB console didn't need to change.

common_hal_usb_cdc_serial_write() already uses the blocking write, so I put that call back in 9c046af.

Only the UART console calls it directly, since common_hal_busio_uart_write() now stops on Ctrl-C.

On the console, that Ctrl-C arrives on the same stream, so it's the case you described.

@dhalbert dhalbert left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

whoops, I didn't actually submit these review questions. The serial.c one is moot now, I think.

Comment thread ports/zephyr-cp/common-hal/busio/UART.c
Comment thread ports/zephyr-cp/supervisor/serial.c Outdated
@mikeysklar

mikeysklar commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator Author

Sorry for the confusion here.

Zephyr's serial writes have three separate bugs, which I split into three PRs: Ctrl-C (this one), the write timeout, and lost USB output.

To your question: yes, uart_poll_out() waits until each byte goes out.

On a plain UART console that could hang if the other side holds off, and Ctrl-C can't stop it. Our console UARTs don't use flow control, so in practice they always finish.

On USB it never hangs; it throws bytes away when the buffer fills. That's the third bug.

busio.UART has no write timeout, and with this PR Ctrl-C stops its writes.

Coming next:

  • Write timeout reads back correctly
  • USB console stops losing output
  • Longer UART pastes, console-only Ctrl-C

mikeysklar and others added 4 commits October 14, 2026 17:10
On raspberrypi and espressif, write() loops until every byte is in the
TX FIFO. When CTS is held deasserted the FIFO never drains, so the loop
never ends and Ctrl-C is ignored. Check mp_hal_is_interrupted() in the
wait loops, the same way read() already does, and return the number of
bytes written so far.

Fixes adafruit#10621.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
write() sent each byte with uart_poll_out(), which waits inside the
driver until the byte is out. When CTS is held off it never returns, so
Ctrl-C was ignored.

Feed the driver from the TX interrupt in serial_cb() instead. write()
only waits for that to finish, stops early on Ctrl-C, and returns the
bytes handed to the driver.

The console and usb_cdc keep using uart_poll_out(). With the host port
closed, the CDC ACM driver drops their output instead of blocking, which
matches the other ports, and console output is never cut short by a
pending Ctrl-C or reload.

Related to adafruit#10621.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
common_hal_busio_uart_write_poll() is Zephyr-only, so drop the
common_hal_ prefix. Explain in its comment why the console and usb_cdc
use it instead of common_hal_busio_uart_write().

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
It already uses the blocking write, so only the UART console needs the
direct call.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@mikeysklar

Copy link
Copy Markdown
Collaborator Author

Rebased on main, which now has #11586 and #11587. Retested on the nRF54LM20 DK:

Test main This PR
CTS held off, then Ctrl-C 0/10, hangs until the host opens its port 10/10, stops in 0.1 s
200 bytes with RTS/CTS 10/10
4096 bytes, with and without RTS/CTS, byte-exact 20/20
usb_cdc.console.write() 10/10

@dhalbert ready for another look when you have time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

busio UART hardware flow control blocks control-C on raspberrypi and espressif

2 participants