#19677·micropython

ports/esp32/usb.c: Native USB CDC drops output / hangs with WebSerial (strict dtr && rts check)

Author: ashleyislandgal-maxCreated Sep 1, 2026Updated Sep 1, 2026
Labelsbugport-esp32

Port, board and/or hardware

ESP32-S2 / ESP32-S3 using native USB CDC (CONFIG_USB_ENABLED=1)

MicroPython version

MicroPython v1.19.1 on 2026-08-23; LOLIN_S2_MINI with ESP32-S2FN4R2

  1. Strict dtr && rts check breaks WebSerial / Browser IDEs In ports/esp32/usb.c, line-state tracking was implemented as:

void usb_callback_line_state_changed(int itf, cdcacm_event_t *event) { int dtr = event->line_state_changed_data.dtr; int rts = event->line_state_changed_data.rts; // If dtr && rts are both true, the CDC is connected to a HOST. usb_cdc_connected = dtr && rts; }

The Problem: The WebSerial API in Chrome/Edge (used by ViperIDE, Adafruit Web IDEs, Web Serial Terminals, etc.) and some OS CDC drivers open serial connections with DTR=1, RTS=0 or don't explicitly assert RTS when flow control is disabled.

The Impact: Requiring both dtr && rts causes MicroPython's USB driver to evaluate usb_cdc_connected = 0. MicroPython then silently drops stdout output (usb_tx_strn), leaving WebSerial client terminals with missing prompt echos, partial output, or disconnect/reconnect loops.

The Fix: Checking dtr || rts or using TinyUSB's native API tud_cdc_n_connected(CDC_ITF) handles browser WebSerial and custom terminal clients properly.

  1. Infinite spin loop in usb_tx_strn() on queue backpressure The write loop was written as:

while (usb_cdc_connected && len) { size_t l = tinyusb_cdcacm_write_queue(CDC_ITF, (uint8_t *)str, len); str += l; len -= l; tud_cdc_n_write_flush(CDC_ITF); }

The Problem: If tinyusb_cdcacm_write_queue returns 0 (because TinyUSB's TX buffer is temporarily full), len never decrements. If usb_cdc_connected remains true, the while loop spins indefinitely without making progress or timing out. The Impact: Printing large amounts of text or fast REPL output can lock up the entire MicroPython main thread (and task execution) until serial buffer drains.

The Fix: Adding a retry limit / timeout or checking tud_cdc_n_connected() inside the loop prevents hard freezes.

Reproduction

see above description

Expected behaviour

No response

Observed behaviour

see above description

Additional Information

No, I've provided everything above.

Code of Conduct

Yes, I agree