Repository navigation
Description
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 newci/util/serial_probe.pyhelper. 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#3339would have been a 15-minute investigation if this table existed."
Out of scope
- Implementing the empirical probe subcommand. Tracked separately (mentioned in conversation thread for fbuild-serial: vendor a serial-probe CLI + shared BOARD_FINGERPRINTS table (companion to closed #684, parallel to FastLED #3339) #686).
- Maintaining the doc as datasheet sources move — pin the URLs as captured-on-date.
Refs
- FastLED/fbuild#684 (closed) — parent:
hard_reset_blockingDTR=low fix. Hints at the matrix but doesn't pin it. - FastLED/fbuild#686 —
BOARD_FINGERPRINTStable. 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
Labels
Type
Projects
- StatusShow more project fieldsTriage
Activity
docs(serial): USB CDC DTR/RTS control-line semantics matrix (#689) (#701
docs(agents): agents/docs/ MVP + root CLAUDE.md routing table (#695) (#…