Skip to content

docs: USB CDC DTR/RTS control-line semantics matrix per board family #689

Description

@zackees

TL;DR

USB CDC ACM SET_CONTROL_LINE_STATE exposes DTR and RTS to host software. What these two bits actually do on the target chip is vendor-specific — and that fact has now burned multiple sessions on different boards. There's no canonical reference inside the FastLED + fbuild stack that maps (BoardFamily, DTR-state, RTS-state) → effect. Every future board port will rediscover the wrong default unless there's somewhere to look first.

This issue asks for a docs/usb-cdc-control-line-matrix.md (or wherever fbuild docs live) that pins down the rules in one place, with links to the datasheets / app notes that back them.

Where this came from

The audit on FastLED/fbuild#684 and the LPC845-BRK bring-up incident (FastLED/FastLED#3300 / #3325) exposed that:

  • DTR=False is fine on ESP32 native USB CDC (post-reset idle).
  • DTR=False is fatal on the LPC11U35 USB-VCOM bridge (LPC845-BRK), CP2102 in CDC mode with some firmware revs, and FTDI FT232 in some modes — the bridge treats DTR as a "host ready" gate and silently drops every transmit byte from the target.
  • Nothing in either repo documents this matrix. The fact lives in scattered comments inside esp_reset.rs, three Python sites that just got fixed, and one short paragraph in the docstring of the new ci/util/serial_probe.py helper. None of these are a reference a future contributor would find.

Proposed shape

A single doc — docs/usb-cdc-control-line-matrix.md in this repo (or crates/fbuild-serial/docs/, maintainer preference) — structured as:

Per-chip matrix

+----------------------------+--------------+----------------+------------------------+
| USB bridge / native CDC     | DTR effect   | RTS effect     | Source                |
+----------------------------+--------------+----------------+------------------------+
| ESP32-S3 native USB CDC    | → BOOT pin   | → EN/RESET pin | esptool reset.py +     |
|                            | (via SOC USB | (via SOC USB   | ESP-IDF USB CDC docs   |
|                            | peripheral)  | peripheral)    |                        |
+----------------------------+--------------+----------------+------------------------+
| LPC11U35 USB-VCOM bridge   | host-ready   | (ignored on    | mbed DAPLink docs;     |
|   (LPC845-BRK, LPCXpresso) | gate; False  | most rev's)    | NXP UM10473 §USB-      |
|                            | drops bytes  |                | to-Serial bridge       |
+----------------------------+--------------+----------------+------------------------+
| FTDI FT232R / FT232H       | exposed as   | exposed as     | FTDI AN232B-04         |
|                            | RS-232 DTR;  | RS-232 RTS;    | "Data Throughput"      |
|                            | board-      | board-         |                        |
|                            | dependent    | dependent      |                        |
+----------------------------+--------------+----------------+------------------------+
| CP2102 / CP2104             | routable to | routable to   | SiLabs AN144           |
|                            | any GPIO;   | any GPIO;     |                        |
|                            | not gating  | not gating    |                        |
|                            | by default  | by default    |                        |
+----------------------------+--------------+----------------+------------------------+
| CH340 / CH343               | DTR pulse  | RTS pulse     | WCH datasheet          |
|                            | usually    | unused        | (Mandarin); see        |
|                            | wired to   |               | Arduino auto-reset     |
|                            | RESET via  |               | note                   |
|                            | 100nF cap  |               |                        |
+----------------------------+--------------+----------------+------------------------+
| PJRC Teensy USB-Serial     | exposed as | exposed as    | PJRC HID specs         |
|                            | host       | host          |                        |
|                            | signal;    | signal;       |                        |
|                            | reset via  |               |                        |
|                            | 1200-bps    |               |                        |
|                            | touch       |               |                        |
+----------------------------+--------------+----------------+------------------------+
| RP2040 native USB CDC      | informational| informational | RP2040 datasheet §USB   |
|                            | only        | only          |                        |
+----------------------------+--------------+----------------+------------------------+
| SAMD21/51 native USB CDC   | host signal | host signal   | Atmel SAM USB CDC ref   |
|                            | + 1200-bps  | + 1200-bps    |                        |
|                            | touch reset | touch reset   |                        |
+----------------------------+--------------+----------------+------------------------+

Concrete rules (the "what to set when you open a port" cheat sheet)

Default safe state: DTR=True, RTS=True. Works for every CDC bridge as
"host ready" and is the ESP post-reset idle state (no reset trigger
because the CDC peripheral consumes the bits at re-enumeration, not on
live toggle).

ESP32 reset sequence (esptool ClassicReset):
    DTR=False, RTS=True   →  EN low (reset hold)
    sleep(100ms)
    DTR=True,  RTS=False  →  EN high (release reset), BOOT low (run firmware)
    Final state: DTR=False, RTS=False. [ESP-correct.]

LPC845-BRK reset:
    Use pyocd / probe-rs CMSIS-DAP SWD reset.
    Do NOT touch DTR/RTS — bridge is sensitive to DTR for data flow.
    Post-reset: re-assert DTR=True, RTS=True before monitor open.

Teensy / SAMD51 / RP2040 reset:
    Open port at 1200 baud, close. Bootloader engages on disconnect.
    Re-enumerate at the bootloader's VID:PID.
    Post-reset: DTR=True, RTS=True at app baud.

Empirical probe (the test that produces this table for a new board)

Use ci/util/serial_probe.py probe-dtr-rts <port> (FastLED side) — sweeps the 4 combinations, reports bytes seen, identifies which combo gates data and which triggers reset. Note: that subcommand was proposed during the audit; if not yet implemented in #686 or here, it's a worthy add.

Cross-links

  • crates/fbuild-serial/src/esp_reset.rs — references this doc from its module header.
  • crates/fbuild-serial/src/manager.rs::open_port — references this doc from the DTR/RTS-assertion block (lines 120-134, the fix(esp32): prevent ESP32-S3 serial reset deadlock and boot-mode lockup #532 reference).
  • Per-family override files in crates/fbuild-build/src/<family>/configs/*.json — reference this doc when adding a new family.
  • FastLED agents/docs/onboarding-a-new-board.md (if it exists; otherwise file separately) — links here too.

Acceptance criteria

  • docs/usb-cdc-control-line-matrix.md (or equivalent path) exists with the per-chip table + concrete rules.
  • At least 5 chips covered with datasheet links: ESP32 native USB CDC, LPC11U35 bridge, FTDI FT232, CP2102, CH340.
  • At least 2 native USB CDC chips: RP2040, SAMD21/51.
  • PJRC Teensy entry (because its 1200-bps touch is a recurring gotcha).
  • The 3 cross-link sites updated to point at this doc.
  • One concrete worked example in the doc: "How FastLED#3339 would have been a 15-minute investigation if this table existed."

Out of scope

Refs

  • FastLED/fbuild#684 (closed) — parent: hard_reset_blocking DTR=low fix. Hints at the matrix but doesn't pin it.
  • FastLED/fbuild#686BOARD_FINGERPRINTS table. This doc is the why behind those entries.
  • FastLED/FastLED#3300 / #3325 / #3339 — the LPC bring-up incident. The cost of not having this doc was multiple sessions of false-trail debugging.
  • FastLED/FastLED#3336 — agent hook + doc rule forbidding ad-hoc PowerShell SerialPort probes. The "why" is exactly the matrix this issue requests.

Filed during the audit fan-out on FastLED/FastLED#3339.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status
    Triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions