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.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
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.