Skip to content

Commit

Permalink
Add support for Raspberry Pi's internal UART peripherals
Browse files Browse the repository at this point in the history
  • Loading branch information
bugadani committed Sep 26, 2022
1 parent c6258f7 commit 9d7e0a9
Show file tree
Hide file tree
Showing 14 changed files with 244 additions and 48 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/rpi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ jobs:
echo "CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER=arm-linux-gnueabihf-gcc" >> $GITHUB_ENV
- name: Build binary
run: cargo build --release --all --target=${{ inputs.target }}
run: cargo build --release --all --target=${{ inputs.target }} --features=raspberry

- uses: papeloto/action-zip@v1
with:
Expand Down
11 changes: 11 additions & 0 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion cargo-espflash/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ fn flash(
if args.flash_args.monitor {
let pid = flasher.get_usb_pid()?;
monitor(
flasher.into_serial(),
flasher.into_interface(),
Some(&elf_data),
pid,
args.connect_args.monitor_baud.unwrap_or(115_200),
Expand Down
2 changes: 2 additions & 0 deletions espflash/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ md5 = "0.7.0"
miette = { version = "5.3.0", features = ["fancy"] }
parse_int = "0.6.0"
regex = "1.6.0"
rppal = { version = "0.13", optional = true }
serde = { version = "1.0.144", features = ["derive"] }
serde-hex = "0.1.0"
serde_json = "1.0.85"
Expand All @@ -70,3 +71,4 @@ xmas-elf = "0.8.0"
[features]
default = ["cli"]
cli = ["clap", "crossterm", "dialoguer", "update-informer"]
raspberry = ["rppal"]
6 changes: 6 additions & 0 deletions espflash/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ SUBCOMMANDS:
write-bin Writes a binary file to a specific address in the chip's flash
```

## Compile-time features

- `raspberry`: enables configuring DTR and RTS GPIOs which are necessary to use a Raspberry Pi's
internal UART peripherals. This feature is optional (external USB <-> UART converters work
without it) and adds a dependency on [`rppal`](https://crates.io/crates/rppal).

## Configuration

You can also specify the serial port and/or expected VID/PID values by setting them in the configuration file. This file is in different locations depending on your operating system:
Expand Down
2 changes: 1 addition & 1 deletion espflash/src/bin/espflash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ fn flash(mut args: FlashArgs, config: &Config) -> Result<()> {
let pid = flasher.get_usb_pid()?;

monitor(
flasher.into_serial(),
flasher.into_interface(),
Some(&elf_data),
pid,
args.connect_args.monitor_baud.unwrap_or(115_200),
Expand Down
4 changes: 4 additions & 0 deletions espflash/src/cli/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ pub struct Config {
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
pub struct Connection {
pub serial: Option<String>,
#[cfg(feature = "raspberry")]
pub rts: Option<u8>,
#[cfg(feature = "raspberry")]
pub dtr: Option<u8>,
}

#[derive(Debug, Deserialize, Serialize, Default, Clone)]
Expand Down
26 changes: 18 additions & 8 deletions espflash/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,16 @@ use std::{
use clap::Args;
use config::Config;
use miette::{IntoDiagnostic, Result, WrapErr};
use serialport::{FlowControl, SerialPortType, UsbPortInfo};
use serialport::{SerialPortType, UsbPortInfo};
use strum::VariantNames;

use crate::{
cli::{monitor::monitor, serial::get_serial_port_info},
elf::ElfFirmwareImage,
error::{Error, NoOtadataError},
error::NoOtadataError,
flasher::{FlashFrequency, FlashMode, FlashSize},
image_format::ImageFormatType,
interface::Interface,
partition_table, Chip, Flasher, ImageFormatId, InvalidPartitionTable, MissingPartitionTable,
PartitionTable,
};
Expand All @@ -40,6 +41,17 @@ pub struct ConnectArgs {
/// Serial port connected to target device
#[clap(short = 'p', long)]
pub port: Option<String>,

/// DTR pin to use for the internal UART hardware. Uses BCM numbering.
#[cfg(feature = "raspberry")]
#[cfg_attr(feature = "raspberry", clap(long))]
pub dtr: Option<u8>,

/// RTS pin to use for the internal UART hardware. Uses BCM numbering.
#[cfg(feature = "raspberry")]
#[cfg_attr(feature = "raspberry", clap(long))]
pub rts: Option<u8>,

/// Use RAM stub for loading
#[clap(long)]
pub use_stub: bool,
Expand Down Expand Up @@ -126,10 +138,8 @@ pub fn connect(args: &ConnectArgs, config: &Config) -> Result<Flasher> {
// Attempt to open the serial port and set its initial baud rate.
println!("Serial port: {}", port_info.port_name);
println!("Connecting...\n");
let serial = serialport::new(&port_info.port_name, 115_200)
.flow_control(FlowControl::None)
.open()
.map_err(Error::from)

let interface = Interface::new(&port_info, args, config)
.wrap_err_with(|| format!("Failed to open serial port {}", port_info.port_name))?;

// NOTE: since `get_serial_port_info` filters out all non-USB serial ports, we
Expand All @@ -150,7 +160,7 @@ pub fn connect(args: &ConnectArgs, config: &Config) -> Result<Flasher> {
};

Ok(Flasher::connect(
serial,
interface,
port_info,
args.baud,
args.use_stub,
Expand All @@ -169,7 +179,7 @@ pub fn serial_monitor(args: ConnectArgs, config: &Config) -> Result<()> {
let pid = flasher.get_usb_pid()?;

monitor(
flasher.into_serial(),
flasher.into_interface(),
None,
pid,
args.monitor_baud.unwrap_or(115_200),
Expand Down
23 changes: 12 additions & 11 deletions espflash/src/cli/monitor.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use std::{
io::{stdout, ErrorKind, Read, Write},
io::{stdout, ErrorKind},
time::Duration,
};

Expand All @@ -9,9 +9,8 @@ use crossterm::{
};
use espmonitor::{handle_serial, load_bin_context, SerialState};
use miette::{IntoDiagnostic, Result};
use serialport::SerialPort;

use crate::connection::reset_after_flash;
use crate::{connection::reset_after_flash, interface::Interface};

/// Converts key events from crossterm into appropriate character/escape
/// sequences which are then sent over the serial connection.
Expand Down Expand Up @@ -84,7 +83,7 @@ impl Drop for RawModeGuard {
}

pub fn monitor(
mut serial: Box<dyn SerialPort>,
mut serial: Interface,
elf: Option<&[u8]>,
pid: u16,
baud: u32,
Expand All @@ -96,8 +95,10 @@ pub fn monitor(

// Explicitly set the baud rate when starting the serial monitor, to allow using
// different rates for flashing.
serial.set_baud_rate(baud)?;
serial.set_timeout(Duration::from_millis(5))?;
serial.serial_port_mut().set_baud_rate(baud)?;
serial
.serial_port_mut()
.set_timeout(Duration::from_millis(5))?;

let _raw_mode = RawModeGuard::new();

Expand All @@ -109,12 +110,12 @@ pub fn monitor(
serial_state = SerialState::new(symbols);
} else {
serial_state = SerialState::new(None);
reset_after_flash(&mut *serial, pid)?;
reset_after_flash(&mut serial, pid)?;
}

let mut buff = [0; 1024];
loop {
let read_count = match serial.read(&mut buff) {
let read_count = match serial.serial_port_mut().read(&mut buff) {
Ok(count) => Ok(count),
Err(e) if e.kind() == ErrorKind::TimedOut => Ok(0),
Err(e) if e.kind() == ErrorKind::Interrupted => continue,
Expand All @@ -131,16 +132,16 @@ pub fn monitor(
match key.code {
KeyCode::Char('c') => break,
KeyCode::Char('r') => {
reset_after_flash(&mut *serial, pid)?;
reset_after_flash(&mut serial, pid)?;
continue;
}
_ => {}
}
}

if let Some(bytes) = handle_key_event(key) {
serial.write_all(&bytes)?;
serial.flush()?;
serial.serial_port_mut().write_all(&bytes)?;
serial.serial_port_mut().flush()?;
}
}
}
Expand Down
46 changes: 26 additions & 20 deletions espflash/src/connection.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
use std::{
io::{BufWriter, Write},
thread::sleep,
time::Duration,
};
use std::{io::BufWriter, thread::sleep, time::Duration};

use binread::{io::Cursor, BinRead, BinReaderExt};
use bytemuck::{Pod, Zeroable};
use serialport::{SerialPort, UsbPortInfo};
use serialport::UsbPortInfo;
use slip_codec::SlipDecoder;

use crate::{
command::{Command, CommandType},
encoder::SlipEncoder,
error::{ConnectionError, Error, ResultExt, RomError, RomErrorKind},
interface::Interface,
};

const DEFAULT_CONNECT_ATTEMPTS: usize = 7;
Expand All @@ -29,7 +26,7 @@ pub struct CommandResponse {
}

pub struct Connection {
serial: Box<dyn SerialPort>,
serial: Interface,
port_info: UsbPortInfo,
decoder: SlipDecoder,
}
Expand All @@ -44,7 +41,7 @@ struct WriteRegParams {
}

impl Connection {
pub fn new(serial: Box<dyn SerialPort>, port_info: UsbPortInfo) -> Self {
pub fn new(serial: Interface, port_info: UsbPortInfo) -> Self {
Connection {
serial,
port_info,
Expand Down Expand Up @@ -119,7 +116,7 @@ impl Connection {

pub fn reset(&mut self) -> Result<(), Error> {
let pid = self.port_info.pid;
Ok(reset_after_flash(&mut *self.serial, pid)?)
Ok(reset_after_flash(&mut self.serial, pid)?)
}

pub fn reset_to_flash(&mut self, extra_delay: bool) -> Result<(), Error> {
Expand Down Expand Up @@ -161,29 +158,36 @@ impl Connection {
}

pub fn set_timeout(&mut self, timeout: Duration) -> Result<(), Error> {
self.serial.set_timeout(timeout)?;
self.serial.serial_port_mut().set_timeout(timeout)?;
Ok(())
}

pub fn set_baud(&mut self, speed: u32) -> Result<(), Error> {
self.serial.set_baud_rate(speed)?;
self.serial.serial_port_mut().set_baud_rate(speed)?;

Ok(())
}

pub fn get_baud(&self) -> Result<u32, Error> {
Ok(self.serial.baud_rate()?)
Ok(self.serial.serial_port().baud_rate()?)
}

pub fn with_timeout<T, F: FnMut(&mut Connection) -> Result<T, Error>>(
&mut self,
timeout: Duration,
mut f: F,
) -> Result<T, Error> {
let old_timeout = self.serial.timeout();
self.serial.set_timeout(timeout)?;
let old_timeout = {
let serial = self.serial.serial_port_mut();
let old_timeout = serial.timeout();
serial.set_timeout(timeout)?;
old_timeout
};

let result = f(self);
self.serial.set_timeout(old_timeout)?;

self.serial.serial_port_mut().set_timeout(old_timeout)?;

result
}

Expand All @@ -199,8 +203,10 @@ impl Connection {
}

pub fn write_command(&mut self, command: Command) -> Result<(), Error> {
self.serial.clear(serialport::ClearBuffer::Input)?;
let mut writer = BufWriter::new(&mut self.serial);
let serial = self.serial.serial_port_mut();

serial.clear(serialport::ClearBuffer::Input)?;
let mut writer = BufWriter::new(serial);
let mut encoder = SlipEncoder::new(&mut writer)?;
command.write(&mut encoder)?;
encoder.finish()?;
Expand Down Expand Up @@ -261,11 +267,11 @@ impl Connection {
}

pub fn flush(&mut self) -> Result<(), Error> {
self.serial.flush()?;
self.serial.serial_port_mut().flush()?;
Ok(())
}

pub fn into_serial(self) -> Box<dyn SerialPort> {
pub fn into_interface(self) -> Interface {
self.serial
}

Expand All @@ -274,7 +280,7 @@ impl Connection {
}
}

pub fn reset_after_flash(serial: &mut dyn SerialPort, pid: u16) -> Result<(), serialport::Error> {
pub fn reset_after_flash(serial: &mut Interface, pid: u16) -> Result<(), serialport::Error> {
sleep(Duration::from_millis(100));

if pid == USB_SERIAL_JTAG_PID {
Expand Down
13 changes: 13 additions & 0 deletions espflash/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::{
command::CommandType,
flasher::{FlashFrequency, FlashMode, FlashSize},
image_format::ImageFormatId,
interface::SerialConfigError,
partition_table::{CoreType, SubType, Type},
Chip,
};
Expand Down Expand Up @@ -90,6 +91,12 @@ https://github.com/espressif/esp32c3-direct-boot-example"
help("Make sure the correct device is connected to the host system")
)]
SerialNotFound(String),
#[error("Incorrect serial port configuration")]
#[diagnostic(
code(espflash::serial_config),
help("Make sure you have specified the DTR signal if you are using an internal UART peripherial")
)]
SerialConfiguration(SerialConfigError),
#[error("Canceled by user")]
Canceled,
#[error("The flash mode '{0}' is not valid")]
Expand Down Expand Up @@ -245,6 +252,12 @@ impl From<binread::Error> for Error {
}
}

impl From<SerialConfigError> for Error {
fn from(err: SerialConfigError) -> Self {
Self::SerialConfiguration(err)
}
}

#[derive(Copy, Clone, Debug, Error, Diagnostic)]
#[allow(dead_code)]
#[repr(u8)]
Expand Down
Loading

0 comments on commit 9d7e0a9

Please sign in to comment.