Repository navigation
Closed
Description
TL;DR
Companion structural piece to #684 (closed) and #686. Today fbuild-serial has board-specific behaviour scattered across esp_reset.rs, manager.rs, boot_mode.rs, and the deploy paths. The next board family port (LPC8xx is on-hand; STM32, RP2040, SAMD51, Teensy, BMP all queued) will multiply that scatter unless there's a single point of consultation for "what does this board want for DTR/RTS, reset, idle state, line-control semantics?"
Proposed structural addition: a BoardFamily enum + a ResetMethod enum + a small dispatch table.
Where this came from
The audit on #684 + #686 identified that:
- Per-board DTR/RTS idle state is named but not encoded. (
#684requestedidle_dtr_rts()API; not landed as a concrete enum.) - Reset method per family has no abstraction. ESP32 → DTR/RTS pulse (
esp_reset.rs). LPC845-BRK → pyocd CMSIS-DAP SWD (handled outsidefbuild-serialentirely). Teensy / SAMD / RP2040 → 1200-bps-touch trick (unimplemented). UNO → DTR pulse via auto-reset cap. - Today's reset dispatch is implicit: callers in
fbuild-deploy/esp32_nativecallesp_resetdirectly; LPC callers must do their own thing. New families will keep adding ad-hoc paths.
Proposed shape
// In a new file: crates/fbuild-serial/src/board_family.rs
pub enum BoardFamily {
/// ESP32 native USB CDC (S3/C3/C6/H2/P4). Reset via DTR/RTS pulse.
/// Idle: (false, false) — BOOT high, EN high, chip running.
Esp32NativeUsbCdc,
/// ESP32 via external USB-UART chip (CP2102/CH340/FTDI). Same DTR/RTS
/// semantics as native (chip exposes lines), so same idle + reset.
Esp32ExternalUart,
/// Bridge-based VCOM (LPC11U35 on LPC845-BRK, mbed DAPLink boards).
/// Reset via SWD/CMSIS-DAP, NOT DTR/RTS.
/// Idle: (true, true) — host-ready signal to the bridge.
CdcAcmBridge { vid: u16, pid: u16 },
/// PJRC Teensy (3.x/4.x). Reset via 1200-bps touch.
/// Idle: (true, true).
Teensy,
/// SAMD51 / RP2040 with native USB CDC. Reset via 1200-bps touch.
/// Idle: (true, true).
NativeUsbCdcReset1200Bps,
/// Classic Arduino (UNO / Mega) with capacitor-coupled DTR auto-reset.
/// Reset via DTR=true→false transition.
/// Idle: (true, true) — but be aware of capacitor-charge timing.
ArduinoAutoReset,
}
impl BoardFamily {
/// Universal post-attach / post-reset idle state for this family.
/// Set by `manager::open_port` after the port handle is acquired.
pub fn idle_dtr_rts(&self) -> (bool, bool) {
use BoardFamily::*;
match self {
Esp32NativeUsbCdc | Esp32ExternalUart => (false, false),
CdcAcmBridge { .. } | Teensy | NativeUsbCdcReset1200Bps
| ArduinoAutoReset => (true, true),
}
}
pub fn reset_method(&self) -> ResetMethod {
use BoardFamily::*;
use ResetMethod::*;
match self {
Esp32NativeUsbCdc | Esp32ExternalUart => DtrRtsPulse,
CdcAcmBridge { .. } => SwdViaCmsisDap,
Teensy | NativeUsbCdcReset1200Bps => TouchBaud1200,
ArduinoAutoReset => DtrPulse,
}
}
}
pub enum ResetMethod {
/// esptool ClassicReset (esp_reset.rs).
DtrRtsPulse,
/// Single DTR=true→false transition (Arduino auto-reset cap).
DtrPulse,
/// Open port at 1200 baud, close → SAMD/RP2040/Teensy bootloader.
TouchBaud1200,
/// CMSIS-DAP probe via pyocd / `probe-rs` — out of `fbuild-serial`'s
/// jurisdiction; caller dispatches.
SwdViaCmsisDap,
}Why a single enum is the right shape
- One place to add a new family → one place to update when a new VID:PID lands.
manager::open_port(port, family)can callfamily.idle_dtr_rts()directly; today it hard-codes(true, true)(fbuild-serial: hard_reset_blocking leaves DTR=low — fatal for CDC-ACM bridges (LPC11U35, FTDI CDC). Mirror the FastLED #3300/#3339 lessons. #684 fix) which is right for CDC bridges but wrong for ESP. The hardcode covers up the asymmetry instead of encoding it.- Deploy orchestrators can call
family.reset_method()to pick the right primitive instead of every deploy module hard-coding ESP DTR/RTS. - New board ports become declarative: one row in a fingerprint table (fbuild-serial: vendor a serial-probe CLI + shared BOARD_FINGERPRINTS table (companion to closed #684, parallel to FastLED #3339) #686) + one variant if a new family is needed.
Acceptance criteria
-
crates/fbuild-serial/src/board_family.rsexists with the enum +idle_dtr_rts()+reset_method(). -
manager::open_portaccepts an optionalBoardFamilyand consultsidle_dtr_rts(); default (whenNone) is the current(true, true)which is the most-common safe state. -
esp_reset::hard_reset_blockingrenamed toesp_dtr_rts_reset_blocking(sinceBoardFamilynow disambiguates) AND has a doc-comment + debug-assert that the caller passedBoardFamily::Esp32*. - A
dispatch_reset(family, port)helper infbuild-serialpicks the right method (callsesp_dtr_rts_reset_blockingfor ESP families; returns a typed "not handled here, use SWD" forCdcAcmBridge). - Doc-comment in
board_family.rslinks to the [BLOCKER] LPC845-BRK AutoResearch bring-up echo returns empty RX (suspected HardFault) — blocks #3102 FastLED#3300 incident.
Out of scope
- Implementing
TouchBaud1200(SAMD51 / RP2040 / Teensy) andDtrPulse(Arduino UNO) — variants can beunimplemented!()initially; track each implementation in its own follow-up. - The SWD/CMSIS-DAP reset path itself — that lives outside
fbuild-serial. The dispatch just routes to "caller must do this elsewhere."
Refs
- FastLED/fbuild#684 (closed) — parent. Requested
idle_dtr_rts()API; this issue makes it concrete withBoardFamily. - FastLED/fbuild#686 —
BOARD_FINGERPRINTStable. Complementary: fingerprint says "what board is this,"BoardFamilysays "how do I talk to it." - FastLED/fbuild#565 — LPC VCOM unwedging. Different layer (post-flash recovery), same family.
- FastLED/fbuild#551 — LPC8xx Stage 7 deployer. Will consume the
BoardFamily::CdcAcmBridge+ResetMethod::SwdViaCmsisDapvariants. - FastLED/FastLED#3300, FastLED/FastLED#3325, FastLED/FastLED#3339 — the LPC bring-up incident this comes out of.
Metadata
Metadata
Assignees
Labels
No labels
Type
Projects
- StatusShow more project fieldsTriage
Activity
feat(serial): board fingerprints + `fbuild serial probe` CLI (#686) (#…
docs(agents): agents/docs/ MVP + root CLAUDE.md routing table (#695) (#…
fix(serial): correct doctest variant name (#687)
feat(serial): BoardFamily reset_method + dispatch_reset (#687) (#703)
ci(hooks): forbidden_commands.py — block pyocd/esptool/dfu-util/picot…
feat(serial): USB-level bootloader re-enumeration watcher (#693) (#709)
feat(cli): fbuild bringup orchestrator scaffold (#697) (#711)