Skip to content

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

Description

@ashleyislandgal-max

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

Activity

  1. Josverl commented on Sep 1, 2026

    @Josverl
    SponsorContributor

    @ashleyislandgal-max ,

    What is/are the host OSes you have seen this behaviour on ?
    That may be relevant as in the past there has been a lot of work to correctly handle the significant differences in the RTS/DTR behaviour between Windows, Linux, and in some cases MacOS.

    /Tip: use 'triple backtick' to improve the code formatting in the post above.
    ```py
    # code goes here
    ``` around your code snippets to get syntax highlighting and proper indenting.

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions