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. (
#684 requested idle_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 outside fbuild-serial entirely). 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_native call esp_reset directly; 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.rspubenumBoardFamily{/// 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,}implBoardFamily{/// Universal post-attach / post-reset idle state for this family./// Set by `manager::open_port` after the port handle is acquired.pubfnidle_dtr_rts(&self) -> (bool,bool){useBoardFamily::*;matchself{Esp32NativeUsbCdc | Esp32ExternalUart => (false,false),CdcAcmBridge{ .. } | Teensy | NativeUsbCdcReset1200Bps
| ArduinoAutoReset => (true,true),}}pubfnreset_method(&self) -> ResetMethod{useBoardFamily::*;useResetMethod::*;matchself{Esp32NativeUsbCdc | Esp32ExternalUart => DtrRtsPulse,CdcAcmBridge{ .. } => SwdViaCmsisDap,Teensy | NativeUsbCdcReset1200Bps => TouchBaud1200,ArduinoAutoReset => DtrPulse,}}}pubenumResetMethod{/// 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
Acceptance criteria
Out of scope
- Implementing
TouchBaud1200 (SAMD51 / RP2040 / Teensy) and DtrPulse (Arduino UNO) — variants can be unimplemented!() 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
TL;DR
Companion structural piece to #684 (closed) and #686. Today
fbuild-serialhas board-specific behaviour scattered acrossesp_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
BoardFamilyenum + aResetMethodenum + a small dispatch table.Where this came from
The audit on #684 + #686 identified that:
#684requestedidle_dtr_rts()API; not landed as a concrete enum.)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.fbuild-deploy/esp32_nativecallesp_resetdirectly; LPC callers must do their own thing. New families will keep adding ad-hoc paths.Proposed shape
Why a single enum is the right shape
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.family.reset_method()to pick the right primitive instead of every deploy module hard-coding ESP DTR/RTS.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*.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).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
TouchBaud1200(SAMD51 / RP2040 / Teensy) andDtrPulse(Arduino UNO) — variants can beunimplemented!()initially; track each implementation in its own follow-up.fbuild-serial. The dispatch just routes to "caller must do this elsewhere."Refs
idle_dtr_rts()API; this issue makes it concrete withBoardFamily.BOARD_FINGERPRINTStable. Complementary: fingerprint says "what board is this,"BoardFamilysays "how do I talk to it."BoardFamily::CdcAcmBridge+ResetMethod::SwdViaCmsisDapvariants.