Skip to content

Commit

Permalink
esp-rs#349 Add UnixTight reset strategy.
Browse files Browse the repository at this point in the history
Adds a reset approach for *nix called UnixTight reset by Espressif to avoid
timing issues with USB-serial connections on Linux and macOS. See
espressif/esptool#712 for details.
  • Loading branch information
AVee committed Apr 16, 2023
1 parent f098b0e commit de24719
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 38 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions espflash/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ thiserror = "1.0.38"
toml = "0.7.3"
update-informer = { version = "0.6.0", optional = true }
xmas-elf = "0.9.0"
libc = "0.2"

[features]
default = ["cli"]
Expand Down
103 changes: 71 additions & 32 deletions espflash/src/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,6 @@ impl Connection {
for _ in 0..DEFAULT_CONNECT_ATTEMPTS {
if self.connect_attempt(extra_delay).is_err() {
extra_delay = !extra_delay;

info!(
"Unable to connect, retrying with {} delay...",
if extra_delay { "extra" } else { "default" }
);
} else {
return Ok(());
}
Expand All @@ -69,8 +64,19 @@ impl Connection {

/// Try to connect to a device
fn connect_attempt(&mut self, extra_delay: bool) -> Result<(), Error> {
self.reset_to_flash(extra_delay)?;
if self.port_info.pid == USB_SERIAL_JTAG_PID {
self.usb_jtag_reset()
} else {
#[cfg(unix)]
if self.unix_tight_reset(extra_delay).is_ok() {
return Ok(())
}

self.classic_reset(extra_delay)
}
}

fn wait_for_connection(&mut self) -> Result<(), Error> {
for _ in 0..5 {
self.flush()?;
if self.sync().is_ok() {
Expand Down Expand Up @@ -119,43 +125,76 @@ impl Connection {
Ok(reset_after_flash(&mut self.serial, pid)?)
}

// Reset the device to flash mode
pub fn reset_to_flash(&mut self, extra_delay: bool) -> Result<(), Error> {
if self.port_info.pid == USB_SERIAL_JTAG_PID {
self.serial.write_data_terminal_ready(false)?;
self.serial.write_request_to_send(false)?;
fn classic_reset(&mut self, extra_delay: bool) -> Result<(), Error> {
info!(
"Attempting Classic reset with {} delay...",
if extra_delay { "extra" } else { "default" }
);

sleep(Duration::from_millis(100));
self.serial.write_data_terminal_ready(false)?;
self.serial.write_request_to_send(false)?;

self.serial.write_data_terminal_ready(true)?;
self.serial.write_request_to_send(false)?;
self.serial.write_data_terminal_ready(true)?;
self.serial.write_request_to_send(true)?;

sleep(Duration::from_millis(100));
self.serial.write_data_terminal_ready(false)?;
self.serial.write_request_to_send(true)?;

self.serial.write_request_to_send(true)?;
self.serial.write_data_terminal_ready(false)?;
self.serial.write_request_to_send(true)?;
sleep(Duration::from_millis(100));

sleep(Duration::from_millis(100));
self.serial.write_data_terminal_ready(true)?;
self.serial.write_request_to_send(false)?;

self.serial.write_data_terminal_ready(false)?;
self.serial.write_request_to_send(false)?;
} else {
self.serial.write_data_terminal_ready(false)?;
self.serial.write_request_to_send(true)?;
let millis = if extra_delay { 500 } else { 50 };
sleep(Duration::from_millis(millis));

sleep(Duration::from_millis(100));
self.serial.write_data_terminal_ready(false)?;
self.wait_for_connection()
}

self.serial.write_data_terminal_ready(true)?;
self.serial.write_request_to_send(false)?;
fn unix_tight_reset(&mut self, extra_delay: bool) -> Result<(), Error> {
info!(
"Attempting UnixTight reset with {} delay...",
if extra_delay { "extra" } else { "default" }
);

let millis = if extra_delay { 500 } else { 50 };
sleep(Duration::from_millis(millis));
self.serial.write_dtr_rts(false, false)?;
self.serial.write_dtr_rts(true, true)?;
self.serial.write_dtr_rts(false, true)?; // IO = HIGH, EN = LOW, chip in reset

self.serial.write_data_terminal_ready(false)?;
}
sleep(Duration::from_millis(100));

Ok(())
self.serial.write_dtr_rts(true, false)?; // IO0 = LOW, EN = HIGH, chip out of reset

let millis = if extra_delay { 500 } else { 50 };
sleep(Duration::from_millis(millis));

self.serial.write_dtr_rts(false, false)?; // IO0 = HIGH, done
self.serial.write_data_terminal_ready(false)?; // Needed in some environments to ensure IO0 = HIGH
self.wait_for_connection()
}

fn usb_jtag_reset(&mut self) -> Result<(), Error> {
info!("Attempting USB-JTAG reset...");
self.serial.write_data_terminal_ready(false)?;
self.serial.write_request_to_send(false)?;

sleep(Duration::from_millis(100));

self.serial.write_data_terminal_ready(true)?;
self.serial.write_request_to_send(false)?;

sleep(Duration::from_millis(100));

self.serial.write_request_to_send(true)?;
self.serial.write_data_terminal_ready(false)?;
self.serial.write_request_to_send(true)?;

sleep(Duration::from_millis(100));

self.serial.write_data_terminal_ready(false)?;
self.serial.write_request_to_send(false)?;
self.wait_for_connection()
}

/// Set timeout for the serial port
Expand Down
50 changes: 44 additions & 6 deletions espflash/src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,21 @@

use std::io::Read;

#[cfg(unix)]
use { std::os::unix::io::AsRawFd, std::io, libc };

use miette::{Context, Result};
#[cfg(feature = "raspberry")]
use rppal::gpio::{Gpio, OutputPin};
use serialport::{FlowControl, SerialPort, SerialPortInfo};

use crate::error::Error;

#[cfg(unix)]
type Port = serialport::TTYPort;
#[cfg(windows)]
type Port = serialport::COMPort;

/// Errors relating to the configuration of a serial port
#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
Expand All @@ -29,7 +37,7 @@ pub enum SerialConfigError {
/// implemented.
pub struct Interface {
/// Hardware serial port used for communication
pub serial_port: Box<dyn SerialPort>,
pub serial_port: Port,
/// Data Transmit Ready pin
#[cfg(feature = "raspberry")]
pub dtr: Option<OutputPin>,
Expand All @@ -49,10 +57,10 @@ fn write_gpio(gpio: &mut OutputPin, level: bool) {
}

/// Open a serial port
fn open_port(port_info: &SerialPortInfo) -> Result<Box<dyn SerialPort>> {
fn open_port(port_info: &SerialPortInfo) -> Result<Port> {
serialport::new(&port_info.port_name, 115_200)
.flow_control(FlowControl::None)
.open()
.open_native()
.map_err(Error::from)
.wrap_err_with(|| format!("Failed to open serial port {}", port_info.port_name))
}
Expand Down Expand Up @@ -112,6 +120,36 @@ impl Interface {
self.serial_port.write_data_terminal_ready(pin_state)
}

#[cfg(unix)]
pub fn write_dtr_rts(&self, dtr: bool, rts: bool) -> serialport::Result<()> {
let fd = self.serial_port.as_raw_fd();
unsafe {
let mut status: i32 = 0;
let res = libc::ioctl(fd, libc::TIOCMGET, &status);
if res != 0 {
return Err(io::Error::last_os_error().into())
}

if dtr {
status |= libc::TIOCM_DTR
} else {
status &= !libc::TIOCM_DTR
}

if rts {
status |= libc::TIOCM_RTS
} else {
status &= !libc::TIOCM_RTS
}

let res = libc::ioctl(fd, libc::TIOCMSET, &status);
if res != 0 {
return Err(io::Error::last_os_error().into())
}
}
Ok(())
}

/// Set the level of the RTS pin
pub fn write_request_to_send(&mut self, pin_state: bool) -> serialport::Result<()> {
#[cfg(feature = "raspberry")]
Expand All @@ -125,17 +163,17 @@ impl Interface {

/// Turn an [Interface] into a [SerialPort]
pub fn into_serial(self) -> Box<dyn SerialPort> {
self.serial_port
Box::new(self.serial_port)
}

/// Turn an [Interface] into a `&`[SerialPort]
pub fn serial_port(&self) -> &dyn SerialPort {
self.serial_port.as_ref()
&self.serial_port
}

/// Turn an [Interface] into a `&mut `[SerialPort]
pub fn serial_port_mut(&mut self) -> &mut dyn SerialPort {
self.serial_port.as_mut()
&mut self.serial_port
}
}

Expand Down

0 comments on commit de24719

Please sign in to comment.