Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions crates/fbuild-cli/src/cli/serial_probe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,12 +232,14 @@ fn read_port(port_name: &str, baud: u32, seconds: f64, send: Option<&str>) -> Re
.timeout(Duration::from_millis(100))
.open()
.map_err(|e| FbuildError::SerialError(format!("open `{port_name}` @ {baud}: {e}")))?;
handle
.write_data_terminal_ready(dtr)
.map_err(|e| FbuildError::SerialError(format!("set DTR={dtr} on `{port_name}`: {e}")))?;
// RTS before DTR: clearing DTR first passes through (DTR=0, RTS=1),
// which resets ESP USB-Serial-JTAG chips (FastLED/fbuild#1617).
handle
.write_request_to_send(rts)
.map_err(|e| FbuildError::SerialError(format!("set RTS={rts} on `{port_name}`: {e}")))?;
handle
.write_data_terminal_ready(dtr)
.map_err(|e| FbuildError::SerialError(format!("set DTR={dtr} on `{port_name}`: {e}")))?;

output::progress(format!(
"probe read: port={port_name} baud={baud} family={family:?} \
Expand Down
36 changes: 36 additions & 0 deletions crates/fbuild-serial/src/esp_reset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,25 @@ impl<T: serialport::SerialPort + ?Sized> DtrRtsControl for T {
}
}

/// Drive a freshly opened port to its idle `(dtr, rts)` state.
///
/// The OS asserts DTR=1, RTS=1 on open. RTS is written first: clearing
/// DTR first would pass through `(DTR=0, RTS=1)`, which the ESP
/// USB-Serial-JTAG reads as "hold the chip in reset" and releases into a
/// reboot (on the ESP32-P4 a full USB re-enumeration). Going via
/// `(DTR=1, RTS=0)` only sets the boot strap (FastLED/fbuild#1617).
/// Failures are logged and ignored — some adapters reject control-line
/// writes but the port is still usable.
pub fn apply_idle_control_lines<P: DtrRtsControl + ?Sized>(port: &mut P, dtr: bool, rts: bool) {
if let Err(e) = port.write_request_to_send(rts) {
tracing::warn!("failed to set RTS={rts}: {e}");
}
if let Err(e) = port.write_data_terminal_ready(dtr) {
tracing::warn!("failed to set DTR={dtr}: {e}");
}
tracing::debug!("open-time idle lines applied: RTS={rts} then DTR={dtr}");
}

/// Hold time for the RTS=high (EN=low) pulse, in milliseconds.
///
/// Matches `esptool`'s classic-hardware `hard_reset` timing: long enough to
Expand Down Expand Up @@ -250,6 +269,23 @@ mod tests {
}
}

#[test]
fn idle_lines_clear_rts_before_dtr() {
let mut port = RecordedPort::default();
apply_idle_control_lines(&mut port, false, false);
assert_eq!(port.events, vec![("RTS", false), ("DTR", false)]);
}

#[test]
fn idle_lines_still_set_dtr_when_rts_write_fails() {
let mut port = RecordedPort {
fail_on: Some("rts"),
..RecordedPort::default()
};
apply_idle_control_lines(&mut port, true, true);
assert_eq!(port.events, vec![("DTR", true)]);
}

#[test]
fn esp_hard_reset_emits_canonical_sequence() {
let mut port = RecordedPort::default();
Expand Down
30 changes: 2 additions & 28 deletions crates/fbuild-serial/src/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,20 +248,7 @@ impl SharedSerialManager {
let (dtr, rts) = family_for_open
.map(|f| f.idle_dtr_rts())
.unwrap_or((true, true));
match serial.write_data_terminal_ready(dtr) {
Ok(()) => tracing::debug!(
family = ?family_for_open,
"manager: open-time DTR={dtr} asserted"
),
Err(e) => tracing::warn!("failed to set DTR={dtr}: {}", e),
}
match serial.write_request_to_send(rts) {
Ok(()) => tracing::debug!(
family = ?family_for_open,
"manager: open-time RTS={rts} asserted"
),
Err(e) => tracing::warn!("failed to set RTS={rts}: {}", e),
}
crate::esp_reset::apply_idle_control_lines(serial.as_mut(), dtr, rts);
Ok(serial)
})
.await;
Expand Down Expand Up @@ -1214,20 +1201,7 @@ impl SharedSerialManager {
let mut serial = serialport::new(&port_for_open, baud_rate)
.timeout(SERIAL_READ_TIMEOUT)
.open()?;
match serial.write_data_terminal_ready(dtr) {
Ok(()) => tracing::debug!(
family = ?family_for_open,
"manager: open-time DTR={dtr} asserted"
),
Err(e) => tracing::warn!("failed to set DTR: {}", e),
}
match serial.write_request_to_send(rts) {
Ok(()) => tracing::debug!(
family = ?family_for_open,
"manager: open-time RTS={rts} asserted"
),
Err(e) => tracing::warn!("failed to set RTS: {}", e),
}
crate::esp_reset::apply_idle_control_lines(serial.as_mut(), dtr, rts);
Ok(serial)
})
.await;
Expand Down
Loading