mirror of
https://github.com/ferrous-systems/embedded-trainings-2020.git
synced 2025-02-13 07:55:14 +00:00
replace tools workspace with xtask tasks
This commit is contained in:
parent
80aeec1551
commit
b08a82bacd
14 changed files with 182 additions and 170 deletions
2
advanced/.cargo/config.toml
Normal file
2
advanced/.cargo/config.toml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
[alias]
|
||||||
|
xtask = "run --manifest-path ../xtask/Cargo.toml --"
|
2
beginner/.cargo/config.toml
Normal file
2
beginner/.cargo/config.toml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
[alias]
|
||||||
|
xtask = "run --manifest-path ../xtask/Cargo.toml --"
|
|
@ -1,7 +0,0 @@
|
||||||
[workspace]
|
|
||||||
members = [
|
|
||||||
"change-channel",
|
|
||||||
"dongle-flash",
|
|
||||||
"serial-term",
|
|
||||||
"usb-list",
|
|
||||||
]
|
|
|
@ -1,12 +0,0 @@
|
||||||
[package]
|
|
||||||
authors = ["Jorge Aparicio <jorge.aparicio@ferrous-systems.com>"]
|
|
||||||
edition = "2018"
|
|
||||||
license = "MIT OR Apache-2.0"
|
|
||||||
name = "change-channel"
|
|
||||||
version = "0.0.0"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
anyhow = "1.0.27"
|
|
||||||
consts = { path = "../../advanced/common/consts" }
|
|
||||||
hidapi = "1.2.2"
|
|
||||||
pids = { path = "../../common/pids" }
|
|
|
@ -1,33 +0,0 @@
|
||||||
use std::env;
|
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, ensure};
|
|
||||||
use hidapi::HidApi;
|
|
||||||
|
|
||||||
fn main() -> Result<(), anyhow::Error> {
|
|
||||||
let args = env::args()
|
|
||||||
.skip(1) // skip program name
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
ensure!(!args.is_empty(), "expected exactly one argument");
|
|
||||||
|
|
||||||
let api = HidApi::new()?;
|
|
||||||
let dev = api
|
|
||||||
.device_list()
|
|
||||||
.filter(|dev| dev.vendor_id() == consts::VID && check_pid(dev.product_id()))
|
|
||||||
.next()
|
|
||||||
.ok_or_else(|| anyhow!("device not found"))?
|
|
||||||
.open_device(&api)?;
|
|
||||||
|
|
||||||
let chan = args[0].parse::<u8>()?;
|
|
||||||
if chan < 11 || chan > 26 {
|
|
||||||
bail!("channel is out of range (`11..=26`)")
|
|
||||||
}
|
|
||||||
const REPORT_ID: u8 = 0;
|
|
||||||
dev.write(&[REPORT_ID, chan])?;
|
|
||||||
println!("requested channel change to channel {}", chan);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_pid(pid: u16) -> bool {
|
|
||||||
pid == pids::LOOPBACK || pid == pids::PUZZLE
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
[package]
|
|
||||||
authors = ["Jorge Aparicio <jorge.aparicio@ferrous-systems.com>"]
|
|
||||||
edition = "2018"
|
|
||||||
license = "MIT OR Apache-2.0"
|
|
||||||
name = "dongle-flash"
|
|
||||||
version = "0.0.0"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
anyhow = "1.0.31"
|
|
||||||
ihex = "1.1.2"
|
|
||||||
serialport = "3.3.0"
|
|
||||||
tempfile = "3.1.0"
|
|
||||||
xmas-elf = "0.7.0"
|
|
|
@ -1,12 +0,0 @@
|
||||||
[package]
|
|
||||||
authors = ["Jorge Aparicio <jorge.aparicio@ferrous-systems.com>"]
|
|
||||||
edition = "2018"
|
|
||||||
license = "MIT OR Apache-2.0"
|
|
||||||
name = "serial-term"
|
|
||||||
version = "0.0.0"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
anyhow = "1.0.30"
|
|
||||||
consts = { path = "../../advanced/common/consts" }
|
|
||||||
ctrlc = "3.1.4"
|
|
||||||
serialport = "3.3.0"
|
|
|
@ -1,52 +0,0 @@
|
||||||
use core::sync::atomic::{AtomicBool, Ordering};
|
|
||||||
use std::{
|
|
||||||
io::{self, Read as _, Write as _},
|
|
||||||
thread,
|
|
||||||
time::Duration,
|
|
||||||
};
|
|
||||||
|
|
||||||
use serialport::SerialPortType;
|
|
||||||
|
|
||||||
fn main() -> Result<(), anyhow::Error> {
|
|
||||||
let mut once = true;
|
|
||||||
let dongle = loop {
|
|
||||||
if let Some(dongle) = serialport::available_ports()?
|
|
||||||
.into_iter()
|
|
||||||
.filter(|info| match &info.port_type {
|
|
||||||
SerialPortType::UsbPort(usb) => usb.vid == consts::VID,
|
|
||||||
_ => false,
|
|
||||||
})
|
|
||||||
.next()
|
|
||||||
{
|
|
||||||
break dongle;
|
|
||||||
} else if once {
|
|
||||||
once = false;
|
|
||||||
|
|
||||||
eprintln!("(waiting for the Dongle to be connected)");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut port = serialport::open(&dongle.port_name)?;
|
|
||||||
|
|
||||||
static CONTINUE: AtomicBool = AtomicBool::new(true);
|
|
||||||
|
|
||||||
// properly close the serial device on Ctrl-C
|
|
||||||
ctrlc::set_handler(|| CONTINUE.store(false, Ordering::Relaxed))?;
|
|
||||||
|
|
||||||
let stdout = io::stdout();
|
|
||||||
let mut stdout = stdout.lock();
|
|
||||||
let mut read_buf = [0; 64];
|
|
||||||
while CONTINUE.load(Ordering::Relaxed) {
|
|
||||||
if port.bytes_to_read()? != 0 {
|
|
||||||
let n = port.read(&mut read_buf)?;
|
|
||||||
stdout.write_all(&read_buf[..n])?;
|
|
||||||
stdout.flush()?;
|
|
||||||
} else {
|
|
||||||
// time span between two consecutive FS USB packets
|
|
||||||
thread::sleep(Duration::from_millis(1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eprintln!("(closing the serial port)");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
[package]
|
|
||||||
authors = ["Jorge Aparicio <jorge.aparicio@ferrous-systems.com>"]
|
|
||||||
edition = "2018"
|
|
||||||
license = "MIT OR Apache-2.0"
|
|
||||||
name = "usb-list"
|
|
||||||
version = "0.0.0"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
consts = { path = "../../advanced/common/consts" }
|
|
||||||
pids = { path = "../../common/pids" }
|
|
||||||
rusb = "0.5.5"
|
|
|
@ -1,19 +0,0 @@
|
||||||
use std::error::Error;
|
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn Error>> {
|
|
||||||
for dev in rusb::devices()?.iter() {
|
|
||||||
let desc = dev.device_descriptor()?;
|
|
||||||
let suffix = match (desc.vendor_id(), desc.product_id()) {
|
|
||||||
(0x1366, 0x1015) => " <- J-Link on the nRF52840 Development Kit",
|
|
||||||
(0x1915, 0x521f) => " <- nRF52840 Dongle (in bootloader mode)",
|
|
||||||
(0x2020, pids::LOOPBACK) => " <- nRF52840 Dongle (loopback.hex)",
|
|
||||||
(0x2020, pids::PUZZLE) => " <- nRF52840 Dongle (puzzle.hex)",
|
|
||||||
(consts::VID, consts::PID) => " <- nRF52840 on the nRF52840 Development Kit",
|
|
||||||
_ => "",
|
|
||||||
};
|
|
||||||
|
|
||||||
println!("{:?}{}", dev, suffix);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
17
xtask/Cargo.toml
Normal file
17
xtask/Cargo.toml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
[package]
|
||||||
|
authors = ["Jorge Aparicio <jorge.aparicio@ferrous-systems.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
name = "xtask"
|
||||||
|
version = "0.0.0"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
color-eyre = "0.5.10"
|
||||||
|
consts = { path = "../advanced/common/consts" }
|
||||||
|
ctrlc = "3.1.4"
|
||||||
|
hidapi = "1.2.2"
|
||||||
|
ihex = "1.1.2"
|
||||||
|
pids = { path = "../common/pids" }
|
||||||
|
rusb = "0.5.5"
|
||||||
|
serialport = "3.3.0"
|
||||||
|
tempfile = "3.2.0"
|
||||||
|
xmas-elf = "0.7.0"
|
1
xtask/src/config.rs
Normal file
1
xtask/src/config.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pub const DONGLE_PATH: &[&str] = &["boards", "dongle"];
|
58
xtask/src/main.rs
Normal file
58
xtask/src/main.rs
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
#![deny(warnings)]
|
||||||
|
|
||||||
|
mod config;
|
||||||
|
mod tasks;
|
||||||
|
|
||||||
|
use std::{env, path::PathBuf};
|
||||||
|
|
||||||
|
fn main() -> color_eyre::Result<()> {
|
||||||
|
color_eyre::install()?;
|
||||||
|
|
||||||
|
// first arg is the name of the executable; skip it
|
||||||
|
let args = env::args().skip(1).collect::<Vec<_>>();
|
||||||
|
let args = args.iter().map(|arg| &arg[..]).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
match &args[..] {
|
||||||
|
["change-channel", channel] => tasks::change_channel(channel),
|
||||||
|
["dongle-flash", hex] => tasks::dongle_flash(hex),
|
||||||
|
["serial-term"] => tasks::serial_term(),
|
||||||
|
["usb-list"] => tasks::usb_list(),
|
||||||
|
_ => {
|
||||||
|
eprintln!(
|
||||||
|
"cargo xtask
|
||||||
|
Workshop-specific tools
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
cargo xtask [COMMAND]
|
||||||
|
|
||||||
|
COMMANDS:
|
||||||
|
change-channel [NUMBER] instructs the nRF Dongle to listen to a different radio channel
|
||||||
|
dongle-flash [PATH] flashes the hex file on the dongle (NOTE PATH is relative to the boards/dongle directory)
|
||||||
|
serial-term displays the log output of the Dongle
|
||||||
|
usb-list list all connected USB devices; highlights workshop devices
|
||||||
|
"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// changes directory into the given path, relative to the root of the repository
|
||||||
|
fn cd(segments: &[&str]) -> color_eyre::Result<()> {
|
||||||
|
let mut path = repository_root()?;
|
||||||
|
for segment in segments {
|
||||||
|
path.push(segment);
|
||||||
|
}
|
||||||
|
env::set_current_dir(path)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn repository_root() -> color_eyre::Result<PathBuf> {
|
||||||
|
// path to this crate (the directory that contains this crate's Cargo.toml)
|
||||||
|
Ok(PathBuf::from(env::var("CARGO_MANIFEST_DIR")?)
|
||||||
|
// from there go one level up
|
||||||
|
.parent()
|
||||||
|
.unwrap()
|
||||||
|
.to_owned())
|
||||||
|
}
|
|
@ -1,11 +1,16 @@
|
||||||
use core::convert::TryFrom;
|
|
||||||
use std::{
|
use std::{
|
||||||
env, fs,
|
convert::TryFrom,
|
||||||
|
fs,
|
||||||
|
io::{self, Write as _},
|
||||||
path::Path,
|
path::Path,
|
||||||
process::{Command, Stdio},
|
process::{Command, Stdio},
|
||||||
|
sync::atomic::{AtomicBool, Ordering},
|
||||||
|
thread,
|
||||||
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, ensure};
|
use color_eyre::eyre::{anyhow, bail, Report};
|
||||||
|
use hidapi::HidApi;
|
||||||
use ihex::record::Record;
|
use ihex::record::Record;
|
||||||
use serialport::SerialPortType;
|
use serialport::SerialPortType;
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
@ -14,13 +19,37 @@ use xmas_elf::{
|
||||||
ElfFile,
|
ElfFile,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::config;
|
||||||
|
|
||||||
|
pub fn change_channel(channel: &str) -> color_eyre::Result<()> {
|
||||||
|
fn check_pid(pid: u16) -> bool {
|
||||||
|
pid == pids::LOOPBACK || pid == pids::PUZZLE
|
||||||
|
}
|
||||||
|
|
||||||
|
let api = HidApi::new()?;
|
||||||
|
let dev = api
|
||||||
|
.device_list()
|
||||||
|
.filter(|dev| dev.vendor_id() == consts::VID && check_pid(dev.product_id()))
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| anyhow!("device not found"))?
|
||||||
|
.open_device(&api)?;
|
||||||
|
|
||||||
|
let chan = channel.parse::<u8>()?;
|
||||||
|
if chan < 11 || chan > 26 {
|
||||||
|
bail!("channel is out of range (`11..=26`)")
|
||||||
|
}
|
||||||
|
const REPORT_ID: u8 = 0;
|
||||||
|
dev.write(&[REPORT_ID, chan])?;
|
||||||
|
println!("requested channel change to channel {}", chan);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dongle_flash(hex: &str) -> color_eyre::Result<()> {
|
||||||
const VID: u16 = 0x1915;
|
const VID: u16 = 0x1915;
|
||||||
const PID: u16 = 0x521f;
|
const PID: u16 = 0x521f;
|
||||||
|
|
||||||
fn main() -> Result<(), anyhow::Error> {
|
crate::cd(config::DONGLE_PATH)?;
|
||||||
let args = env::args().skip(1 /* program name */).collect::<Vec<_>>();
|
|
||||||
|
|
||||||
ensure!(args.len() == 1, "expected exactly one argument");
|
|
||||||
|
|
||||||
let dongle = serialport::available_ports()?
|
let dongle = serialport::available_ports()?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -35,7 +64,7 @@ connect the Dongle to your laptop or PC and press the reset button to put it in
|
||||||
if the red LED was blinking and you got this message then the device wasn't correctly enumerated; remove it and try again")
|
if the red LED was blinking and you got this message then the device wasn't correctly enumerated; remove it and try again")
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let path = Path::new(&args[0]);
|
let path = Path::new(hex);
|
||||||
let filename = Path::new(
|
let filename = Path::new(
|
||||||
path.file_name()
|
path.file_name()
|
||||||
.ok_or_else(|| anyhow!("{} has no file name", path.display()))?,
|
.ok_or_else(|| anyhow!("{} has no file name", path.display()))?,
|
||||||
|
@ -52,13 +81,13 @@ if the red LED was blinking and you got this message then the device wasn't corr
|
||||||
// here we map the ELF loadable segments -- these correspond to sections like `.text`, `.rodata`
|
// here we map the ELF loadable segments -- these correspond to sections like `.text`, `.rodata`
|
||||||
// and `.data` (initial values) -- to ihex records
|
// and `.data` (initial values) -- to ihex records
|
||||||
let bytes = fs::read(path)?;
|
let bytes = fs::read(path)?;
|
||||||
let elf_file = ElfFile::new(&bytes).map_err(anyhow::Error::msg)?;
|
let elf_file = ElfFile::new(&bytes).map_err(Report::msg)?;
|
||||||
let mut records = vec![];
|
let mut records = vec![];
|
||||||
for ph in elf_file.program_iter() {
|
for ph in elf_file.program_iter() {
|
||||||
if ph.get_type() == Ok(Type::Load) {
|
if ph.get_type() == Ok(Type::Load) {
|
||||||
let start = ph.physical_addr();
|
let start = ph.physical_addr();
|
||||||
|
|
||||||
match ph.get_data(&elf_file).map_err(anyhow::Error::msg)? {
|
match ph.get_data(&elf_file).map_err(Report::msg)? {
|
||||||
SegmentData::Undefined(bytes) => {
|
SegmentData::Undefined(bytes) => {
|
||||||
// this is what `objcopy -O ihex` uses and it works with `nrfutil`
|
// this is what `objcopy -O ihex` uses and it works with `nrfutil`
|
||||||
const RECORD_SIZE: usize = 16;
|
const RECORD_SIZE: usize = 16;
|
||||||
|
@ -148,3 +177,65 @@ if the red LED was blinking and you got this message then the device wasn't corr
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn serial_term() -> color_eyre::Result<()> {
|
||||||
|
let mut once = true;
|
||||||
|
let dongle = loop {
|
||||||
|
if let Some(dongle) = serialport::available_ports()?
|
||||||
|
.into_iter()
|
||||||
|
.filter(|info| match &info.port_type {
|
||||||
|
SerialPortType::UsbPort(usb) => usb.vid == consts::VID,
|
||||||
|
_ => false,
|
||||||
|
})
|
||||||
|
.next()
|
||||||
|
{
|
||||||
|
break dongle;
|
||||||
|
} else if once {
|
||||||
|
once = false;
|
||||||
|
|
||||||
|
eprintln!("(waiting for the Dongle to be connected)");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut port = serialport::open(&dongle.port_name)?;
|
||||||
|
|
||||||
|
static CONTINUE: AtomicBool = AtomicBool::new(true);
|
||||||
|
|
||||||
|
// properly close the serial device on Ctrl-C
|
||||||
|
ctrlc::set_handler(|| CONTINUE.store(false, Ordering::Relaxed))?;
|
||||||
|
|
||||||
|
let stdout = io::stdout();
|
||||||
|
let mut stdout = stdout.lock();
|
||||||
|
let mut read_buf = [0; 64];
|
||||||
|
while CONTINUE.load(Ordering::Relaxed) {
|
||||||
|
if port.bytes_to_read()? != 0 {
|
||||||
|
let n = port.read(&mut read_buf)?;
|
||||||
|
stdout.write_all(&read_buf[..n])?;
|
||||||
|
stdout.flush()?;
|
||||||
|
} else {
|
||||||
|
// time span between two consecutive FS USB packets
|
||||||
|
thread::sleep(Duration::from_millis(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
eprintln!("(closing the serial port)");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn usb_list() -> color_eyre::Result<()> {
|
||||||
|
for dev in rusb::devices()?.iter() {
|
||||||
|
let desc = dev.device_descriptor()?;
|
||||||
|
let suffix = match (desc.vendor_id(), desc.product_id()) {
|
||||||
|
(0x1366, 0x1015) => " <- J-Link on the nRF52840 Development Kit",
|
||||||
|
(0x1915, 0x521f) => " <- nRF52840 Dongle (in bootloader mode)",
|
||||||
|
(0x2020, pids::LOOPBACK) => " <- nRF52840 Dongle (loopback.hex)",
|
||||||
|
(0x2020, pids::PUZZLE) => " <- nRF52840 Dongle (puzzle.hex)",
|
||||||
|
(consts::VID, consts::PID) => " <- nRF52840 on the nRF52840 Development Kit",
|
||||||
|
_ => "",
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("{:?}{}", dev, suffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Reference in a new issue