Merge branch '111-use-defmt-logging' of https://github.com/ferrous-systems/embedded-trainings-2020 into 111-use-defmt-logging

This commit is contained in:
Mirabellensaft 2021-12-20 15:58:25 +01:00
commit 14d5b2c93c
83 changed files with 359 additions and 704 deletions

5
.gitignore vendored
View file

@ -1,3 +1,6 @@
target/
book/
Cargo.lock
Cargo.lock
.vscode/
*.DS_Store

Binary file not shown.

Binary file not shown.

View file

@ -51,6 +51,7 @@ impl Request {
windex: u16,
wlength: u16,
) -> Result<Self, ()> {
// Request Codes
// see table 9-4 (USB specification)
const SET_ADDRESS: u8 = 5;

View file

@ -15,14 +15,17 @@ consts = { path = "../common/consts" }
cortex-m = "0.6.4"
cortex-m-rt = "0.6.13"
cortex-m-rtic = "0.5.1"
dk = { path = "../../boards/dk", features = ["advanced"] }
heapless = "0.5.5"
panic-probe = { version = "0.2.0", features = ["print-defmt"] }
defmt = "0.2.1"
defmt-rtt = "0.2.0"
dk = { path = "../../boards/dk", features = ["advanced"] }
panic-probe = { version = "0.2.0", features = ["print-defmt"] }
usb = { path = "../common/usb" }
usb2 = "0.0.1"
[dependencies.heapless]
version = "0.7.1"
features = ["defmt-impl"]
# optimize code in both profiles
[profile.dev]
codegen-units = 1

View file

@ -11,7 +11,7 @@ fn main() -> ! {
// board initialization
dk::init().unwrap();
defmt::info!("fib(100) = {:?}", fib(100));
fib(100);
loop {
asm::bkpt();
@ -21,7 +21,8 @@ fn main() -> ! {
#[inline(never)]
fn fib(n: u32) -> u32 {
// allocate and initialize one kilobyte of stack memory to provoke stack overflow
let _use_stack = [0xAA; 1024];
let use_stack = [0xAA; 1024];
defmt::info!("allocating [{}; 1024]; round #{}", use_stack[1023], n);
if n < 2 {
1

View file

@ -44,7 +44,7 @@ fn on_event(_usbd: &USBD, event: Event) {
Event::UsbReset => todo!(),
Event::UsbEp0DataDone => todo!(),
// leave this at it is for now.
Event::UsbEp0Setup => {
defmt::info!("goal reached; move to the next section");
dk::exit()

View file

@ -91,7 +91,6 @@ fn ep0setup(usbd: &USBD, ep0in: &mut Ep0In, state: &mut State) -> Result<(), ()>
defmt::info!("EP0: {:?}", defmt::Debug2Format(&request));
// ^^^^^^^^^^^^^^^^^^^ this adapter is currently needed to log
// `StandardRequest` with `defmt`
match request {
// section 9.4.3
// this request is valid in any state
@ -116,7 +115,7 @@ fn ep0setup(usbd: &USBD, ep0in: &mut Ep0In, state: &mut State) -> Result<(), ()>
Descriptor::Configuration { index } => {
if index == 0 {
let mut resp = heapless::Vec::<u8, heapless::consts::U64>::new();
let mut resp = heapless::Vec::<u8, 64>::new();
let conf_desc = usb2::configuration::Descriptor {
wTotalLength: (usb2::configuration::Descriptor::SIZE

View file

@ -116,7 +116,7 @@ fn ep0setup(usbd: &USBD, ep0in: &mut Ep0In, state: &mut State) -> Result<(), ()>
Descriptor::Configuration { index } => {
if index == 0 {
let mut resp = heapless::Vec::<u8, heapless::consts::U64>::new();
let mut resp = heapless::Vec::<u8, 64>::new();
let conf_desc = usb2::configuration::Descriptor {
wTotalLength: (usb2::configuration::Descriptor::SIZE

View file

@ -3,7 +3,7 @@
#![no_std]
use cortex_m_rt::entry;
use heapless::{consts, Vec};
use heapless::Vec;
// this imports `beginner/apps/lib.rs` to retrieve our global logger + panicking-behavior
use firmware as _;
@ -12,21 +12,18 @@ fn main() -> ! {
dk::init().unwrap();
// a stack-allocated `Vec` with capacity for 6 bytes
let mut buffer = Vec::<u8, consts::U6>::new();
// ^^ capacity; this is a type not a value
let mut buffer = Vec::<u8, 6>::new();
// content type ^^ ^ capacity
// `heapless::Vec` exposes the same API surface as `std::Vec` but some of its methods return a
// `Result` to indicate whether the operation failed due to the `heapless::Vec` being full
defmt::info!("start: {:?}", defmt::Debug2Format(&buffer));
// ^^^^^^^^^^^^^^^^^^^ this adapter is currently needed to log
// `heapless` data structures (like `Vec` here)
// with `defmt`
defmt::info!("start: {:?}", buffer);
buffer.push(0).expect("buffer full");
defmt::info!("after `push`: {:?}", defmt::Debug2Format(&buffer));
defmt::info!("after `push`: {:?}", buffer);
buffer.extend_from_slice(&[1, 2, 3]).expect("buffer full");
defmt::info!("after `extend`: {:?}", defmt::Debug2Format(&buffer));
defmt::info!("after `extend`: {:?}", &buffer);
// TODO try this operation
// buffer.extend_from_slice(&[4, 5, 6, 7]).expect("buffer full");

View file

@ -8,10 +8,3 @@ use panic_probe as _;
fn panic() -> ! {
cortex_m::asm::udf()
}
/// Terminates the application and makes `probe-run` exit with exit-code = 0
pub fn exit() -> ! {
loop {
cortex_m::asm::bkpt();
}
}

View file

@ -7,10 +7,13 @@ rustflags = [
]
[target.thumbv7em-none-eabihf]
# set custom cargo runner to flash & run on embedded target when we call `cargo run`
# for more information, check out https://github.com/knurling-rs/probe-run
runner = "probe-run --chip nRF52840_xxAA"
rustflags = [
"-C", "link-arg=-Tlink.x",
]
[build]
# cross-compile to this target
target = "thumbv7em-none-eabihf" # = ARM Cortex-M4

View file

@ -9,11 +9,15 @@ version = "0.0.0"
cortex-m = "0.7.3"
cortex-m-rt = "0.7.1"
dk = { path = "../../boards/dk", features = ["beginner"] }
heapless = "0.7.9"
panic-probe = { version = "0.3.0", features = ["print-defmt"] }
defmt = "0.3.0"
defmt-rtt = "0.3.1"
[dependencies.heapless]
version = "0.7.9"
features = ["defmt-impl"]
# optimize code in both profiles
[profile.dev]
codegen-units = 1

View file

@ -1,6 +0,0 @@
[default.general]
chip = "nRF52840_xxAA"
[default.rtt]
enabled = true
channels = [{ up = 0 }]

View file

@ -20,7 +20,7 @@ fn main() -> ! {
for _ in 0..10 {
led.toggle();
timer.wait(Duration::from_secs(1));
defmt::debug!("LED toggled at {:?}", dk::uptime());
defmt::info!("LED toggled at {:?}", dk::uptime());
}
dk::exit()

View file

@ -1,13 +1,21 @@
#![no_main]
// this program does not use the standard library to avoid heap allocations.
// only the `core` library functions are available.
#![no_std]
// this program uses a custom entry point instead of `fn main()`
#![no_main]
use cortex_m::asm;
use cortex_m_rt::entry;
// this imports `beginner/apps/lib.rs` to retrieve our global logger + panicking-behavior
use apps as _;
// the custom entry point
// vvvvv
#[entry]
fn main() -> ! {
// ˆˆˆ
// ! is the 'never' type: this function never returns
// initializes the peripherals
dk::init().unwrap();

View file

@ -3,8 +3,8 @@
use cortex_m::asm;
use cortex_m_rt::entry;
// use apps as _; // this defines the panicking behaviour
// this imports `beginner/apps/lib.rs` to retrieve our global logger + panicking-behavior
use apps as _;
#[entry]
fn main() -> ! {
@ -38,5 +38,5 @@ fn index() -> usize {
#[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! {
defmt::panic!("lalal {}", info);
defmt::panic!("{}", info);
}

View file

@ -18,6 +18,7 @@ fn main() -> ! {
// puzzle.hex uses channel 25
radio.set_channel(Channel::_25);
// the IEEE 802.15.4 packet that will carry our data
let mut packet = Packet::new();
// first exchange a single packet with the Dongle

View file

@ -6,7 +6,7 @@ use cortex_m_rt::entry;
// NOTE you can use `FnvIndexMap` instead of `LinearMap`; the former may have better
// lookup performance when the dictionary contains a large number of items but performance is
// not important for this exercise
use heapless::{consts, LinearMap};
use heapless::LinearMap;
// this imports `beginner/apps/lib.rs` to retrieve our global logger + panicking-behavior
use apps as _;
@ -15,8 +15,9 @@ fn main() -> ! {
dk::init().unwrap();
// a dictionary with capacity for 2 elements
let mut dict = LinearMap::<_, _, consts::U2>::new();
// ^^ capacity; this is a type not a value
let mut dict = LinearMap::<_, _, 2>::new();
// content types ^^ ^^ ^ capacity
// (inferred by rust)
// do some insertions
dict.insert(b'A', b'*').expect("dictionary full");

View file

@ -4,7 +4,7 @@
use cortex_m_rt::entry;
use dk::ieee802154::{Channel, Packet};
use heapless::{consts, LinearMap};
use heapless::LinearMap;
// this imports `beginner/apps/lib.rs` to retrieve our global logger + panicking-behavior
use apps as _;
@ -20,7 +20,7 @@ fn main() -> ! {
radio.set_channel(Channel::_25);
// capacity (128) should be large enough for the ASCII range
let dict = LinearMap::<u8, u8, consts::U128>::new();
let mut dict = LinearMap::<u8, u8, 128>::new();
let mut packet = Packet::new();
// TODO do the whole ASCII range [0, 127]

View file

@ -5,7 +5,7 @@
use core::str;
use cortex_m_rt::entry;
use heapless::{consts, Vec};
use heapless::Vec;
// this imports `beginner/apps/lib.rs` to retrieve our global logger + panicking-behavior
use apps as _;
@ -14,17 +14,15 @@ fn main() -> ! {
dk::init().unwrap();
// a buffer with capacity for 2 bytes
let mut buffer = Vec::<u8, consts::U2>::new();
// ^^ capacity; this is a type not a value
let mut buffer = Vec::<u8, 2>::new();
// content type ^^ ^ capacity
// do some insertions
buffer.push(b'H').expect("buffer full");
buffer.push(b'i').expect("buffer full");
// look into the contents so far
defmt::info!("{:?}", defmt::Debug2Format(&buffer));
// ^^^^^^^^^^^^^^^^^^^ this adapter is currently needed to log `heapless`
// data structures (like `Vec` here) with `defmt`
defmt::info!("{:?}", buffer);
// or more readable
// NOTE utf-8 conversion works as long as you only push bytes in the ASCII range (0..=127)

View file

@ -6,7 +6,7 @@ use core::str;
use cortex_m_rt::entry;
use dk::ieee802154::{Channel, Packet};
use heapless::{consts, Vec};
use heapless::Vec;
// this imports `beginner/apps/lib.rs` to retrieve our global logger + panicking-behavior
use apps as _;
@ -38,7 +38,7 @@ fn main() -> ! {
);
/* # Decrypt the string */
let mut buf = Vec::<u8, consts::U128>::new();
let mut buf = Vec::<u8, 128>::new();
// iterate over the bytes
for input in packet.iter() {

View file

@ -6,7 +6,7 @@ use core::str;
use cortex_m_rt::entry;
use dk::ieee802154::{Channel, Packet};
use heapless::{consts, LinearMap, Vec};
use heapless::{LinearMap, Vec};
// this imports `beginner/apps/lib.rs` to retrieve our global logger + panicking-behavior
use apps as _;
@ -22,7 +22,7 @@ fn main() -> ! {
radio.set_channel(Channel::_25);
/* # Build a dictionary */
let dict = LinearMap::<u8, u8, consts::U128>::new();
let dict = LinearMap::<u8, u8, 128>::new();
let mut packet = Packet::new();
for source in 0..=127 {
@ -62,7 +62,7 @@ fn main() -> ! {
);
/* # Decrypt the string */
let mut buffer = Vec::<u8, consts::U128>::new();
let mut buffer = Vec::<u8, 128>::new();
// iterate over the bytes
for byte in packet.iter() {

View file

@ -6,7 +6,7 @@ use core::str;
use cortex_m_rt::entry;
use dk::ieee802154::{Channel, Packet};
use heapless::{consts, LinearMap, Vec};
use heapless::{LinearMap, Vec};
// this imports `beginner/apps/lib.rs` to retrieve our global logger + panicking-behavior
use apps as _;
@ -22,7 +22,7 @@ fn main() -> ! {
radio.set_channel(Channel::_25);
/* # Build a dictionary */
let dict = LinearMap::<u8, u8, consts::U128>::new();
let dict = LinearMap::<u8, u8, 128>::new();
let mut packet = Packet::new();
for source in 0..=127 {
@ -62,7 +62,7 @@ fn main() -> ! {
);
/* # Decrypt the string */
let mut buffer = Vec::<u8, consts::U128>::new();
let mut buffer = Vec::<u8, 128>::new();
// iterate over the bytes
for byte in packet.iter() {

View file

@ -6,7 +6,7 @@ use core::str;
use cortex_m_rt::entry;
use dk::ieee802154::{Channel, Packet};
use heapless::{consts, LinearMap};
use heapless::LinearMap;
// this imports `beginner/apps/lib.rs` to retrieve our global logger + panicking-behavior
use apps as _;
@ -22,8 +22,9 @@ fn main() -> ! {
radio.set_channel(Channel::_25);
/* # Build a dictionary */
let mut dict = LinearMap::<u8, u8, consts::U128>::new();
let mut dict = LinearMap::<u8, u8, 128>::new();
// the IEEE 802.15.4 packet that will carry our data
let mut packet = Packet::new();
for plainletter in 0..=127 {
packet.copy_from_slice(&[plainletter]);

View file

@ -6,7 +6,7 @@ use core::str;
use cortex_m_rt::entry;
use dk::ieee802154::{Channel, Packet};
use heapless::{consts, LinearMap, Vec};
use heapless::{LinearMap, Vec};
// this imports `beginner/apps/lib.rs` to retrieve our global logger + panicking-behavior
use apps as _;
@ -22,9 +22,10 @@ fn main() -> ! {
radio.set_channel(Channel::_25);
/* # Build a dictionary */
let mut dict = LinearMap::<u8, u8, consts::U128>::new();
// ^^^^^^^^^^^^ NOTE larger capacity
let mut dict = LinearMap::<u8, u8, 128>::new();
// ^^^ NOTE larger capacity
// the IEEE 802.15.4 packet that will carry our data
let mut packet = Packet::new();
for plainletter in 0..=127 {
// ^^^^^^^ NOTE complete ASCII range
@ -65,7 +66,7 @@ fn main() -> ! {
);
/* # Decrypt the string */
let mut buffer = Vec::<u8, consts::U128>::new();
let mut buffer = Vec::<u8, 128>::new();
// iterate over the bytes
for cipherletter in packet.iter() {

View file

@ -45,7 +45,7 @@ fn main() -> ! {
crc
);
}
Err(Error::Crc(crc)) => defmt::error!("invalid CRC: {=u16:x}", crc),
Err(Error::Crc(crc)) => defmt::error!("invalid CRC: {:X}", crc),
Err(Error::Timeout) => defmt::error!("no response within {} ms", TEN_MS / 1_000),
}

View file

@ -11,7 +11,7 @@ fn main() -> ! {
// board initialization
dk::init().unwrap();
defmt::info!("fib(100) = {:?}", fib(100));
fib(100);
loop {
asm::bkpt();
@ -21,7 +21,8 @@ fn main() -> ! {
#[inline(never)]
fn fib(n: u32) -> u32 {
// allocate and initialize one kilobyte of stack memory to provoke stack overflow
let _use_stack = [0xAA; 1024];
let use_stack = [0xAA; 1024];
defmt::info!("allocating [{}; 1024]; round #{}", use_stack[1023], n);
if n < 2 {
1

View file

@ -8,10 +8,3 @@ use panic_probe as _;
fn panic() -> ! {
cortex_m::asm::udf()
}
/// Terminates the application and makes `probe-run` exit with exit-code = 0
pub fn exit() -> ! {
loop {
cortex_m::asm::bkpt();
}
}

View file

@ -15,12 +15,12 @@ defmt = "0.3.0"
defmt-rtt = "0.3.1"
[features]
beginner = []
advanced = []
beginner = []
# set defmt logging levels here
default = [
"defmt-debug",
"defmt-info",
# "dependency-a/defmt-trace",
]

View file

@ -26,7 +26,6 @@ use defmt;
#[cfg(any(feature = "beginner", feature = "advanced"))]
use defmt_rtt as _; // global logger
#[cfg(feature = "advanced")]
use crate::{
peripheral::{POWER, USBD},
@ -246,7 +245,9 @@ pub fn init() -> Result<Board, ()> {
// set TX power to its maximum value
radio.set_txpower(ieee802154::TxPower::Pos8dBm);
defmt::debug!("Radio initialized and configured with TX power set to the maximum value");
defmt::debug!(
"Radio initialized and configured with TX power set to the maximum value"
);
radio
};

View file

@ -47,7 +47,7 @@ to: [81, 78, 109, 61, 120, 87, 125, 98, 100, 91, 97, 66, 57, 117, 49, 64, 48, 85
secret: "<p=-*Uh5&Ph6=PQ_z_6=Q_-Zh_-h&IPh?cj?>>?>h$IUQhL&P*Up&6w"
```
### Generate `puzzle.hex`
### Generate `puzzle` ELF
``` console
$ git clone --branch dongle-puzzle https://github.com/japaric/embedded2020
@ -55,9 +55,9 @@ $ git clone --branch dongle-puzzle https://github.com/japaric/embedded2020
$ cd embedded2020/firmware/apps
```
Copy the `puzzle.rs` from this folder into the `embedded2020/firmware/apps/src/bin` folder.
Find `puzzle.rs` in the `embedded2020/firmware/apps/src/bin` folder.
Update that copy of `puzzle.rs` with the `FROM`, `TO` and `SECRET` data that you got from `puzzlegen`
Update `puzzle.rs` with the `FROM`, `TO` and `SECRET` data that you got from `puzzlegen`
```` rust
static FROM: &[u8] = &[
@ -80,38 +80,34 @@ static TO: &[u8] = &[
static SECRET: &[u8] = b"<p=-*Uh5&Ph6=PQ_z_6=Q_-Zh_-h&IPh?cj?>>?>h$IUQhL&P*Up&6w";
````
Build the program; this will produce an ELF file.
Build the program; this will produce an ELF file called `puzzle` (no file ending).
``` console
$ cargo build --bin puzzle --release
```
Convert that ELF file into a .hex file.
Copy this ELF file from `embedded2020/firmware/target/thumbv7em-none-eabi/release` to `embedded-trainings-2020/boards/dongle`
``` console
$ arm-none-eabi-objcopy -O ihex ../target/thumbv7em-none-eabi/release/puzzle puzzle.hex
```
Test the produced `puzzle` file:
Test the produced `puzzle.hex` file:
- flash it onto a dongle using `cargo xtask dongle-flash`. The green LED on the dongle should turn on
- flash it onto a dongle using `nrfdfu puzzle`. The green LED on the dongle should turn on
- run `cargo xtask serial-term`; you should see the following output. `deviceid` will be different
``` text
deviceid=d90eedf1978d5fd2 channel=25 TxPower=+8dBm app=puzzle.hex
deviceid=d90eedf1978d5fd2 channel=25 TxPower=+8dBm app=puzzle
```
- run the `radio-puzzle-solution` program on a DK; it should be able to decrypt the new secret
- run `cargo xtask change-channel <some number between 11 and 26>` to test changing the Dongle's radio channel
- modify and re-run the `radio-puzzle-solution` program on a DK to solve the puzzle using a the channel you set in the previous step
### Generate `puzzle-nousb-*.hex`
### Generate `puzzle-nousb-*`
The procedure is similar to the one for generating the `puzzle.hex`. The differences are:
The procedure is similar to the one for generating the `puzzle` ELF file. The differences are:
- you copy `puzzle-nousb.rs` into the `embedded2020` repository
- you also need to change `const CHANNEL` in the `puzzle-nousb.rs` copy
- you need to produce one hex file per hard-coded radio channel.
- you build `puzzle-nousb.rs` in the `embedded2020` repository and copy `embedded2020/firmware/target/thumbv7em-none-eabi/release/puzzle-nousb` over
- you also need to change `const CHANNEL` in `puzzle-nousb.rs`
- you need to produce one ELF file per hard-coded radio channel.
Also test these `nousb` .hex files. Note that the green LED won't turn on when the dongle restarts! The green LED will toggle when a new packet is received and the blue LED will turn on when the decoded secret is received. Also, `cargo xtask change-channel` won't work with the `nousb` variants so you can skip that test.
Also test these `nousb` ELF files. Note that the green LED won't turn on when the dongle restarts! The green LED will toggle when a new packet is received and the blue LED will turn on when the decoded secret is received. Also, `cargo xtask change-channel` won't work with the `nousb` variants so you can skip that test.
## References

View file

@ -0,0 +1,5 @@
# For `nrfutil` Users only
The files in this directory are only relevant if you are still using the `nrfutil` python tool distributed by Nordic Semiconductors.
For a smoother installation and usage experience, we recommend you install [`nrfdfu`](https://crates.io/crates/nrfdfu) if at all possible.

BIN
boards/dongle/loopback Executable file

Binary file not shown.

View file

@ -1,35 +0,0 @@
#![deny(unused_must_use)]
#![no_main]
#![no_std]
use hal::{radio::{self, Channel}, led};
use panic_abort as _;
#[no_mangle]
fn main() -> ! {
let (mut rtx, mut rrx) = radio::claim(Channel::_21); // <- change this
let led = led::Green;
let task = async {
let mut packet = radio::Packet::new().await;
let mut on = true;
loop {
let crcres = rrx.read(&mut packet).await;
// togle LED on each new packet
if on {
led.on();
} else {
led.off();
}
on = !on;
if crcres.is_ok() {
packet.reverse();
rtx.write(&packet).await.ok();
}
}
};
executor::run!(task)
}

BIN
boards/dongle/loopback-nousb11 Executable file

Binary file not shown.

BIN
boards/dongle/loopback-nousb16 Executable file

Binary file not shown.

BIN
boards/dongle/loopback-nousb21 Executable file

Binary file not shown.

BIN
boards/dongle/loopback-nousb26 Executable file

Binary file not shown.

View file

@ -1,125 +0,0 @@
#![deny(unused_must_use)]
#![no_main]
#![no_std]
use core::{convert::TryFrom, fmt::Write as _};
use async_core::unsync::Mutex;
use hal::{
radio::{self, Channel},
usbd,
};
use heapless::{consts, String};
use panic_abort as _;
#[no_mangle]
fn main() -> ! {
let stx = Mutex::new(usbd::serial());
let (mut hidout, _) = usbd::hid();
let (rtx, mut rrx) = radio::claim(Channel::_20);
let mut output = String::<consts::U128>::new();
output.push_str("deviceid=").ok();
write!(output, "{:08x}{:08x}", hal::deviceid1(), hal::deviceid0()).ok();
write!(
output,
" channel={} TxPower=+8dBm app=loopback.hex\n",
rtx.channel()
)
.ok();
let rtx = Mutex::new(rtx);
let t1 = async {
let mut output = String::<consts::U128>::new();
let mut hidbuf = usbd::Packet::new().await;
let zlp = radio::Packet::new().await;
loop {
hidout.recv(&mut hidbuf).await;
semidap::info!("HID: {}", *hidbuf);
let arg = if hidbuf.len() == 1 {
// Linux / macOS
Some(hidbuf[0])
} else if hidbuf.len() == 64 {
// Windows (it zero pads the packet)
Some(hidbuf[0])
} else {
None
};
if let Some(arg) = arg {
if let Ok(chan) = Channel::try_from(arg) {
let mut rtx = rtx.lock().await;
rtx.set_channel(chan);
// send a zero-length packet to force the radio to listen on the new channel
rtx.write(&zlp).await.ok();
drop(rtx);
output.clear();
writeln!(output, "now listening on channel {}", chan).ok();
stx.lock().await.write(output.as_bytes());
} else {
stx.lock()
.await
.write(b"requested channel is out of range (11-26)\n");
}
} else {
stx.lock().await.write(b"invalid HID packet\n");
}
}
};
let t2 = async {
let mut packet = radio::Packet::new().await;
stx.lock().await.write(output.as_bytes());
loop {
let crcres = rrx.read(&mut packet).await;
let len = packet.len();
let lqi = if len >= 3 {
Some(packet.lqi())
} else {
// packet is too small; LQI is not valid
None
};
let mut busy = false;
if crcres.is_ok() {
packet.reverse();
busy = rtx.lock().await.write(&packet).await.is_err();
}
output.clear();
write!(
&mut output,
"received {} byte{}",
len,
if len == 1 { "" } else { "s" }
)
.ok();
let (res, crc) = match crcres {
Ok(x) => ("Ok", x),
Err(x) => ("Err", x),
};
write!(&mut output, " (CRC={}({:#06x})", res, crc).ok();
if let Some(lqi) = lqi {
write!(&mut output, ", LQI={}", lqi).ok();
}
output.push_str(")\n").ok();
if busy {
output.push_str("didn't reply -- channel was busy\n").ok();
stx.lock().await.write(output.as_bytes());
}
stx.lock().await.write(output.as_bytes());
}
};
executor::run!(t1, t2)
}

BIN
boards/dongle/puzzle Executable file

Binary file not shown.

View file

@ -1,80 +0,0 @@
#![deny(unused_must_use)]
#![no_main]
#![no_std]
use hal::{
led,
radio::{self, Channel, Packet},
};
use heapless::{consts, LinearMap};
use panic_abort as _;
const CHANNEL: Channel = Channel::_26;
static FROM: &[u8] = &[
// <redacted>
];
static TO: &[u8] = &[
// <redacted>
];
// store the secret rather than the plaintext -- otherwise `strings $elf` will reveal the answer
static SECRET: &[u8] = b"<redacted>";
#[no_mangle]
fn main() -> ! {
let (mut rtx, mut rrx) = radio::claim(CHANNEL);
let led = led::Green;
let mut dict = LinearMap::<_, _, consts::U128>::new();
for (&from, &to) in FROM.iter().zip(TO.iter()) {
dict.insert(from, to).ok();
}
let task = async {
let mut packet = Packet::new().await;
let mut on = true;
loop {
let crcres = rrx.read(&mut packet).await;
// toggle LED on each new packet
if on {
led.on();
} else {
led.off();
}
on = !on;
if crcres.is_ok() {
if packet.is_empty() {
packet.copy_from_slice(SECRET);
} else if packet.len() == 1 {
let p = packet[0];
let c = dict.get(&p).unwrap_or(&p);
packet.copy_from_slice(&[*c]);
} else {
// encrypt
for slot in packet.iter_mut() {
if let Some(c) = dict.get(slot) {
*slot = *c;
}
}
let matches = &packet[..] == SECRET;
packet.copy_from_slice(if matches {
led::Blue.on();
b"correct"
} else {
led::Blue.off();
b"incorrect"
});
}
rtx.write(&packet).await.ok();
}
}
};
executor::run!(task)
}

BIN
boards/dongle/puzzle-nousb11 Executable file

Binary file not shown.

BIN
boards/dongle/puzzle-nousb16 Executable file

Binary file not shown.

BIN
boards/dongle/puzzle-nousb21 Executable file

Binary file not shown.

BIN
boards/dongle/puzzle-nousb26 Executable file

Binary file not shown.

View file

@ -1,156 +0,0 @@
#![deny(unused_must_use)]
#![no_main]
#![no_std]
use core::{fmt::Write as _, convert::TryFrom};
use async_core::unsync::Mutex;
use hal::{radio::{self, Packet, Channel}, usbd, led};
use heapless::{consts, LinearMap, String};
use panic_abort as _;
static FROM: &[u8] = &[
// <redacted>
];
static TO: &[u8] = &[
// <redacted>
];
// store the secret rather than the plaintext -- otherwise `strings $elf` will reveal the answer
static SECRET: &[u8] = b"<redacted>";
#[no_mangle]
fn main() -> ! {
// so we can visually differentiate this one from `loopback.hex`
led::Green.on();
let stx = Mutex::new(usbd::serial());
let (mut hidout, _) = usbd::hid();
let (rtx, mut rrx) = radio::claim(Channel::_25);
let mut output = String::<consts::U128>::new();
let mut dict = LinearMap::<_, _, consts::U128>::new();
for (&from, &to) in FROM.iter().zip(TO.iter()) {
dict.insert(from, to).ok();
}
output.push_str("deviceid=").ok();
write!(output, "{:08x}{:08x}", hal::deviceid1(), hal::deviceid0()).ok();
write!(output, " channel={} TxPower=+8dBm app=puzzle.hex\n", rtx.channel()).ok();
let rtx = Mutex::new(rtx);
let t1 = async {
let mut output = String::<consts::U128>::new();
let mut hidbuf = usbd::Packet::new().await;
let zlp = radio::Packet::new().await;
loop {
hidout.recv(&mut hidbuf).await;
semidap::info!("HID: {}", *hidbuf);
let arg = if hidbuf.len() == 1 {
// Linux / macOS
Some(hidbuf[0])
} else if hidbuf.len() == 64 {
// Windows (it zero pads the packet)
Some(hidbuf[0])
} else {
None
};
if let Some(arg) = arg {
if let Ok(chan) = Channel::try_from(arg) {
let mut rtx = rtx.lock().await;
rtx.set_channel(chan);
// send a zero-length packet to force the radio to listen on the new channel
rtx.write(&zlp).await.ok();
drop(rtx);
output.clear();
writeln!(output, "now listening on channel {}", chan).ok();
stx.lock().await.write(output.as_bytes());
} else {
stx.lock()
.await
.write(b"requested channel is out of range (11-26)\n");
}
} else {
stx.lock().await.write(b"invalid HID packet\n");
}
}
};
let t2 = async {
let mut packet = Packet::new().await;
stx.lock().await.write(output.as_bytes());
loop {
let crcres = rrx.read(&mut packet).await;
let len = packet.len();
let lqi = if len >= 3 {
Some(packet.lqi())
} else {
// packet is too small; LQI is not valid
None
};
let mut busy = false;
if crcres.is_ok() {
if packet.is_empty() {
packet.copy_from_slice(SECRET);
} else if packet.len() == 1 {
let p = packet[0];
let c = dict.get(&p).unwrap_or(&p);
packet.copy_from_slice(&[*c]);
} else {
// encrypt
for slot in packet.iter_mut() {
if let Some(c) = dict.get(slot) {
*slot = *c;
}
}
let matches = &packet[..] == SECRET;
packet.copy_from_slice(if matches {
b"correct"
} else {
b"incorrect"
});
}
busy = rtx.lock().await.write(&packet).await.is_err();
}
output.clear();
write!(
&mut output,
"received {} byte{}",
len,
if len == 1 { "" } else { "s" }
)
.ok();
let (res, crc) = match crcres {
Ok(x) => ("Ok", x),
Err(x) => ("Err", x),
};
write!(&mut output, " (CRC={}({:#06x})", res, crc).ok();
if let Some(lqi) = lqi {
write!(&mut output, ", LQI={}", lqi).ok();
}
output.push_str(")\n").ok();
if busy {
output.push_str("didn't reply -- channel was busy\n").ok();
stx.lock().await.write(output.as_bytes());
}
stx.lock().await.write(output.as_bytes());
}
};
executor::run!(t1, t2)
}

View file

@ -0,0 +1,105 @@
## Python
**Windows**: Go to [https://www.python.org/downloads/](https://www.python.org/downloads/) and run the Python *3* installer
- in the installer check the "add Python 3.x to PATH" box
- also run the "Disable path length limit" action at the end, if you are on Windows 10 and the option is displayed to you
**Linux**: Install `pip` using the package manager; this will also install Python.
``` console
$ # If you're using Arch Linux
$ sudo pacman -S python-pip
$ # If you're using Ubuntu
$ sudo apt-get install python3-pip
```
**macOS**:
Ensure that you have python 3 and pip installed. Refer to the following link for [Instructions on how to install python 3 and pip](https://docs.python-guide.org/starting/install3/osx/)
```console
$ python --version
Python 3.7.7
$ pip --version
pip 20.0.2 from /usr/local/lib/python3.7/site-packages/pip (python 3.7)
```
## nrf tools
### nrfutil
**All**: Open a terminal and install [nrfutil](https://github.com/NordicSemiconductor/pc-nrfutil) as follows. *If you are familiar with python, it is advised to do this in a [virtual environment](https://docs.python.org/3/library/venv.html).*
``` console
$ pip install nrfutil
(..)
$ nrfutil version
nrfutil version 6.1.0
```
**NOTE** as of version 6.1.0 nrfutil indicates that it does "not" support Python 3.9 in its pip manifest. Due to this, the above `pip install` command will fail if you have a Python 3.9 interpreter installed. Here's how to work around the issue:
*start of nrfutil python 3.9 workaround*
``` console
$ # these steps can also be done via a graphical interface
$ # fetch the source code of version 6.1.0
$ # or instead of curl you can enter the URL into your web browser
$ curl -LO https://github.com/NordicSemiconductor/pc-nrfutil/archive/v6.1.zip
$ # unzip the code
$ unzip v6.1.zip
$ # go into the new folder
$ cd pc-nrfutil-6.1
```
Apply the following 2 patches (can also be done manually by editing these 2 files: `requirements.txt` and `setup.py`)
``` diff
--- a/requirements.txt 2021-01-05 10:50:12.611556607 +0100
+++ b/requirements.txt 2021-01-05 10:50:09.718226891 +0100
@@ -4,7 +4,7 @@
ecdsa
intelhex
libusb1
-pc_ble_driver_py >= 0.14.2
+pc_ble_driver_py
piccata
protobuf
pyserial
```
``` diff
--- a/setup.py 2021-01-05 10:49:56.014910707 +0100
+++ b/setup.py 2021-01-05 10:50:26.004873175 +0100
@@ -148,7 +148,7 @@
'../libusb/x86/libusb-1.0.dll', '../libusb/x64/libusb-1.0.dll',
'../libusb/x64/libusb-1.0.dylib', '../libusb/LICENSE']
},
- python_requires='>=3.6, <3.9',
+ python_requires='>=3.6, <3.10',
install_requires=reqs,
zipfile=None,
tests_require=[
```
``` console
$ patch -p1 < requirements.patch
$ patch -p1 < setup.patch
```
Then install the patched `nrfutil`
``` console
$ pip install .
$ # verify installation
$ nrfutil version
nrfutil version 6.1.0
```
*end of nrfutil python 3.9 workaround*

View file

@ -68,5 +68,6 @@
- [`dongle-flash` is not working](./troubleshoot-dongle-flash.md)
- [Dongle USB functionality is not working](./troubleshoot-usb-dongle.md)
- [`cargo run` errors](./troubleshoot-cargo-run-error.md)
- [DEPRECATED - `nrfutil` setup](DEPRECATED-nrfutil-setup.md)
- [Appendix](./appendix.md)
- [Using GDB](./gdb.md)

View file

@ -2,17 +2,22 @@
The next step is to respond to the GET_DESCRIPTOR request with a device descriptor.
✅ Open the file `src/bin/usb-3.rs`. Implement the response to the GET_DESCRIPTOR request. Use the following guide for assistance.
❗️ Keep the cable connected to the J3 port for the rest of the workshop
To do this we'll use the `dk::usb::Ep0In` abstraction -- we'll look into what the abstraction does in a future section; for now we'll just use it.
✅ Open the file `src/bin/usb-3.rs`.
An instance of this abstraction is available in the `board` value (`#[init]` function). The first step is to make this `Ep0In` instance available to the `on_event` function.
Part of this response is already implemented. We'll go through this.
The `Ep0In` API has two methods: `start` and `end` (also see their API documentation). `start` is used to start a DATA stage; this method takes a *slice of bytes* (`[u8]`) as argument; this argument is the response data. The `end` method needs to be called after `start`, when the EP0DATADONE event is raised, to complete the control transfer. `Ep0In` will automatically issue the STATUS stage that must follow the DATA stage.
To implement responding to a GET_DESCRIPTOR Device request, extend `usb-3.rs` so that it uses `Ep0In` to respond to the `GET_DESCRIPTOR Device` request (and only to that request). The response must be a device descriptor with its fields set to these values:
We'll use the `dk::usb::Ep0In` abstraction. An instance of it is available in the `board` value (`#[init]` function). The first step is to make this `Ep0In` instance available to the `on_event` function.
The `Ep0In` API has two methods: `start` and `end`. `start` is used to start a DATA stage; this method takes a *slice of bytes* (`[u8]`) as argument; this argument is the response data. The `end` method needs to be called after `start`, when the EP0DATADONE event is raised, to complete the control transfer. `Ep0In` will automatically issue the STATUS stage that must follow the DATA stage.
✅ Handle the EP0DATADONE event by calling the `end` method of the `EP0In` API.
✅ Implement the response to the GET_DESCRIPTOR request. Extend `usb-3.rs` so that it uses `Ep0In` to respond to the `GET_DESCRIPTOR Device` request (and only to that request).
**Values of the device descriptor**
- `bLength = 18`, the size of the descriptor (must always be this value)
- `bDescriptorType = 1`, device descriptor type (must always be this value)
@ -26,13 +31,17 @@ To implement responding to a GET_DESCRIPTOR Device request, extend `usb-3.rs` so
>(\*) the `common` crate refers to the crate in the `advanced/common` folder. It is already part of the `firmware` crate dependencies.
**Use the `usb2::device::Descriptor` abstraction**
Although you can create the device descriptor by hand as an array filled with magic values we *strongly* recommend you use the `usb2::device::Descriptor` abstraction. The crate is already in the dependency list of the project; you can open its API documentation with the following command: `cargo doc -p usb2 --open`.
> NOTE: the `usb2::device::Descriptor` struct does not have `bLength` and `bDescriptorType` fields. Those fields have fixed values according to the USB spec so you cannot modify or set them. When `bytes()` is called on the `Descriptor` value the returned array, the binary representation of the descriptor, will contain those fields set to their correct value.
**The length of the device descriptor**
Note that the device descriptor is 18 bytes long but the host may ask for fewer bytes (see `wlength` field in the SETUP data). In that case you must respond with the amount of bytes the host asked for. The opposite may also happen: `wlength` may be larger than the size of the device descriptor; in this case your answer must be 18 bytes long (do *not* pad the response with zeroes).
The `usb2::device::Descriptor` struct does not have `bLength` and `bDescriptorType` fields. Those fields have fixed values according to the USB spec so you cannot modify or set them. When `bytes()` is called on the `Descriptor` value the returned array, the binary representation of the descriptor, will contain those fields set to their correct value.
Don't forget to also handle the `EP0DATADONE` event!
The device descriptor is 18 bytes long but the host may ask for fewer bytes (see `wlength` field in the SETUP data). In that case you must respond with the amount of bytes the host asked for. The opposite may also happen: `wlength` may be larger than the size of the device descriptor; in this case your answer must be 18 bytes long (do *not* pad the response with zeroes).
**Expected log output**
Once you have successfully responded to the GET_DESCRIPTOR Device request you should get logs like these (if you are logging like `usb-3` does):

View file

@ -10,11 +10,11 @@ From this section on, we'll use the nRF52840 Dongle in addition to the nRF52840
The Dongle does not contain an on-board debugger, like the DK, so we cannot use `probe-rs` tools to write programs into it. Instead, the Dongle's stock firmware comes with a *bootloader*.
When put in bootloader mode the Dongle will run a bootloader program instead of the last application that was flashed into it. This bootloader program will make the Dongle show up as a USB CDC ACM device (AKA Serial over USB device) that accepts new application images over this interface. We'll use the `nrfutil` tool to communicate with the bootloader-mode Dongle and flash new images into it.
When put in bootloader mode the Dongle will run a bootloader program instead of the last application that was flashed into it. This bootloader program will make the Dongle show up as a USB CDC ACM device (AKA Serial over USB device) that accepts new application images over this interface. We'll use the `nrfdfu` tool to communicate with the bootloader-mode Dongle and flash new images into it.
✅ Connect the Dongle to your computer. Put the Dongle in bootloader mode by pressing its *reset* button.
When the Dongle is in bootloader mode its red LED will oscillate in intensity. The Dongle will also appear as a USB CDC ACM device with vendor ID `0x1915` and product ID `0x521f`.
When the Dongle is in bootloader mode its red LED will pulsate. The Dongle will also appear as a USB CDC ACM device with vendor ID `0x1915` and product ID `0x521f`.
You can also use our `cargo xtask usb-list` tool, a minimal cross-platform version of the `lsusb` tool, to check out the status of the Dongle.
@ -29,27 +29,28 @@ Bus 001 Device 016: ID 1915:521f <- nRF52840 Dongle (in bootloader mode)
🔎 [`cargo xtask`](https://github.com/matklad/cargo-xtask) lets us extend `cargo` with custom commands which are installed as you run them for the first time. We've used it to add some helper tools to our workshop materials while keeping the preparation installations as minimal as possible.
Now that the device is in bootloader mode browse to the `boards/dongle` directory. You'll find some `*.hex` files there. These are pre-compiled Rust programs that have been converted into the Intel Hex format that the `nrfutil` tool expects.
Now that the device is in bootloader mode browse to the `boards/dongle` directory. You'll find some `ELF` files (without a file ending) there. These are pre-compiled Rust programs to be flashed onto your dongle.
For the next section you'll need to flash the `loopback.hex` file into the Dongle. There are two ways to do this. You can make 2 long `nrfutil` invocations or you can use our `cargo xtask dongle-flash` tool, which will invoke `nrfutil` for you. The `dongle-flash` way is shown below:
For the next section you'll need to flash the `loopback` file onto the Dongle.
✅ Run the following command:
``` console
$ cargo xtask dongle-flash boards/dongle/loopback.hex
$ nrfdfu boards/dongle/loopback
```
Expected output:
``` console
packaging iHex using nrfutil ...
DONE
[####################################] 100%
Device programmed.
[INFO nrfdfu] Sending init packet...
[INFO nrfdfu] Sending firmware image of size 37328...
[INFO nrfdfu] Done.
```
After the device has been programmed it will automatically reset and start running the new application.
The `loopback` application will *blink* the red LED in a heartbeat fashion: two fast blinks (LED on then off) followed by two periods of silence (LED off). The application will also make the Dongle enumerate itself as a CDC ACM device.
🔎 Alternatively, you can also use nordic's own [`nrfutil`](https://infocenter.nordicsemi.com/index.jsp?topic=%2Fug_nrfutil%2FUG%2Fnrfutil%2Fnrfutil_intro.html) tool to convert a .hex file and flash it for you, among many other things `nrfutil` is a very powerful tool, but also unstable at times, which is why we replaced the parts we needed from it with `nrfdfu`.
🔎 The `loopback` application will make the Dongle enumerate itself as a CDC ACM device.
✅ Run `cargo xtask usb-list` tool to see the newly enumerated Dongle in the output:

View file

@ -10,7 +10,7 @@ fn on_event(/* parameters */) {
if ep0setup(/* arguments */).is_err() {
// unsupported or invalid request:
// TODO add code to stall the endpoint
log::warn!("EP0: unexpected request; stalling the endpoint");
defmt::warn!("EP0IN: unexpected request; stalling the endpoint");
}
}
}
@ -32,4 +32,4 @@ Note that there's a difference between the error handling done here and the erro
(3) stopping the program, and e.g. requiring the user to reset it to make it work again, may not be desirable behavior.
For these reasons in embedded software errors tend to be handled as early as possible rather than propagated all the way up.
This does not preclude error *reporting*. The above snippet includes error reporting in the form of a `log::warn!` statement. This log statement may not be included in the final release of the program as it may not be useful, or even visible, to an end user but it is useful during development.
This does not preclude error *reporting*. The above snippet includes error reporting in the form of a `defmt::warn!` statement. This log statement may not be included in the final release of the program as it may not be useful, or even visible, to an end user but it is useful during development.

View file

@ -104,7 +104,7 @@ If the debugger is not supported by `probe-rs` then you'll need to use [OpenOCD]
[OpenOCD]: http://openocd.org/
If the board does not expose a JTAG, SWD or similar interface then the microcontroller probably comes with a bootloader as part of its stock firmware. In that case you'll need to use `dfu-util` or a vendor specific tool like `nrfutil` to flash programs onto the chip. This is the case of the nRF52840 Dongle.
If the board does not expose a JTAG, SWD or similar interface then the microcontroller probably comes with a bootloader as part of its stock firmware. In that case you'll need to use `dfu-util` or a vendor specific tool like [`nrfdfu`](https://crates.io/crates/nrfdfu) or [`nrfutil`](https://infocenter.nordicsemi.com/index.jsp?topic=%2Fug_nrfutil%2FUG%2Fnrfutil%2Fnrfutil_intro.html) to flash programs onto the chip. This is the case of the nRF52840 Dongle.
## Getting output

View file

@ -8,9 +8,9 @@ As a reminder, all GET_DESCRIPTOR request types are share the following properti
- `bRequest` is **6** (i.e. the GET_DESCRIPTOR Request Code, defined in table 9-4 of the [USB specification][usb_spec])
A GET_DESCRIPTOR *Configuration* request is determined the high bit of its `wValue` field:
A GET_DESCRIPTOR *Configuration* request is determined the high byte of its `wValue` field:
- The high bit of `wValue` is **2** (i.e. the `CONFIGURATION` descriptor type, defined in table 9-5 of the [USB specification][usb_spec])
- The high byte of `wValue` is **2** (i.e. the `CONFIGURATION` descriptor type, defined in table 9-5 of the [USB specification][usb_spec])
[usb_spec]: https://www.usb.org/document-library/usb-20-specification

View file

@ -56,13 +56,13 @@ Connect one end of a micro USB cable to the USB connector *J2* of the board and
💬 These directions assume you are holding the board "horizontally" with components (switches, buttons and pins) facing up. In this position, rotate the board, so that its convex shaped short side faces right. You'll find one USB connector (J2) on the left edge, another USB connector (J3) on the bottom edge and 4 buttons on the bottom right corner.
![Labeled Diagram of the nRF52840 Development Kit (DK)](hardware/labelled.jpg)
![Labeled Diagram of the nRF52840 Development Kit (DK)](hardware/nrf52840_dk_board.jpg)
After connecting the DK to your PC/laptop it will show up as:
**Windows**: a removable USB flash drive (named JLINK) and also as a USB Serial Device (COM port) in the Device Manager under the Ports section
**Linux**: a USB device under `lsusb`. The device will have a VID of `0x1366` and a PID of `0x1015` -- the `0x` prefix will be omitted in the output of `lsusb`:
**Linux**: a USB device under `lsusb`. The device will have a VID of `0x1366` and a PID of `0x10??` or `0x01??` (`?` is a hex digit) -- the `0x` prefix will be omitted in the output of `lsusb`:
``` console
$ lsusb
@ -105,10 +105,10 @@ $ ls /dev/tty.usbmodem*
/dev/tty.usbmodem0006834208031
```
The board has several switches to configure its behavior. The out of the box configuration is the one we want. If the above instructions didn't work for you, position the board so that the Button descriptions are horizontal and check the position of the on-board switches:
The board has several switches to configure its behavior. The out of the box configuration is the one we want. If the above instructions didn't work for you, check the position of the following switches (locate them using the diagram above):
- Switch SW6, on the top edge right corner, is set to the DEFAULT position; this is the right position of the two possible positions (nRF = DEFAULT).
- Switch SW7, which is slightly up and to the right of the center of the board, is set to the Def. position; this is the right position of the two possible positions (TRACE = Def.). Note that this switch is protected by Kapton tape.
- Switch SW8, on the bottom edge left corner, is set to the ON position; this is the left position of the two possible positions (Power = ON)
- Switch SW9, to the right the left edge USB connector (J2), is set to the VDD position; this is the center position of the three possible positions (nRF power source = VDD)
- Switch SW10, on the bottom edge left corner and to the right of the SW8 switch, is set to the OFF position; this is the left position of the two possible positions (VEXT -> nRF = OFF). Note that this switch is protected by Kapton tape.
- SW6 is set to the DEFAULT position (to the right - nRF = DEFAULT).
- SW7 (protected by Kapton tape) is set to the Def. position (to the right - TRACE = Def.).
- SW8 is set to the ON (to the left) position (Power = ON)
- SW9 is set to the VDD position (center - nRF power source = VDD)
- SW10 (protected by Kapton tape) is set to the OFF position (to the left - VEXT -> nRF = OFF).

Binary file not shown.

Before

Width:  |  Height:  |  Size: 701 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 836 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

View file

@ -17,12 +17,7 @@ All programming will take place in its `beginner/` and `advanced/` subfolders.
**Windows**: Go to [https://code.visualstudio.com](https://code.visualstudio.com) and run the installer.
**Linux**: Check your Linux distribution package manager (example below). If it's not there, follow the instructions on [https://code.visualstudio.com/docs/setup/linux](https://code.visualstudio.com/docs/setup/linux).
``` console
$ # Arch Linux
$ sudo pacman -S code
```
**Linux**: Follow the instructions for your distribution on [https://code.visualstudio.com/docs/setup/linux](https://code.visualstudio.com/docs/setup/linux).
**macOS**: Go to [https://code.visualstudio.com](https://code.visualstudio.com) and click on "Download for Mac"
@ -65,7 +60,7 @@ ATTRS{idVendor}=="1915", ATTRS{idProduct}=="521f", TAG+="uaccess"
ATTRS{idVendor}=="2020", TAG+="uaccess"
# nRF52840 Development Kit
ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1015", TAG+="uaccess"
ATTRS{idVendor}=="1366", ENV{ID_MM_DEVICE_IGNORE}="1", TAG+="uaccess"
```
3. Run the following command to make the new udev rules effective
@ -157,107 +152,8 @@ Installed package `probe-run v0.3.1` (..)
$ cargo install flip-link
(..)
Installed package `flip-link v0.1.5` (..)
```
## Python
**Windows**: Go to [https://www.python.org/downloads/](https://www.python.org/downloads/) and run the Python *3* installer
- in the installer check the "add Python 3.x to PATH" box
- also run the "Disable path length limit" action at the end, if you are on Windows 10 and the option is displayed to you
**Linux**: Install `pip` using the package manager; this will also install Python.
``` console
$ # Arch Linux
$ sudo pacman -S python-pip
```
**macOS**:
Ensure that you have python 3 and pip installed. Refer to the following link for [Instructions on how to install python 3 and pip](https://docs.python-guide.org/starting/install3/osx/)
```console
$ python --version
Python 3.7.7
$ pip --version
pip 20.0.2 from /usr/local/lib/python3.7/site-packages/pip (python 3.7)
```
## nrf tools
### nrfutil
**All**: Open a terminal and install [nrfutil](https://github.com/NordicSemiconductor/pc-nrfutil) as follows. *If you are familiar with python, it is advised to do this in a [virtual environment](https://docs.python.org/3/library/venv.html).*
``` console
$ pip install nrfutil
$ cargo install nrfdfu
(..)
$ nrfutil version
nrfutil version 6.1.0
Installed package `nrfdfu v0.1.0` (..)
```
**NOTE** as of version 6.1.0 nrfutil indicates that it does "not" support Python 3.9 in its pip manifest. Due to this, the above `pip install` command will fail if you have a Python 3.9 interpreter installed. Here's how to work around the issue:
*start of nrfutil python 3.9 workaround*
``` console
$ # these steps can also be done via a graphical interface
$ # fetch the source code of version 6.1.0
$ # or instead of curl you can enter the URL into your web browser
$ curl -LO https://github.com/NordicSemiconductor/pc-nrfutil/archive/v6.1.zip
$ # unzip the code
$ unzip v6.1.zip
$ # go into the new folder
$ cd pc-nrfutil-6.1
```
Apply the following 2 patches (can also be done manually by editing these 2 files: `requirements.txt` and `setup.py`)
``` diff
--- a/requirements.txt 2021-01-05 10:50:12.611556607 +0100
+++ b/requirements.txt 2021-01-05 10:50:09.718226891 +0100
@@ -4,7 +4,7 @@
ecdsa
intelhex
libusb1
-pc_ble_driver_py >= 0.14.2
+# pc_ble_driver_py >= 0.14.2
piccata
protobuf
pyserial
```
``` diff
--- a/setup.py 2021-01-05 10:49:56.014910707 +0100
+++ b/setup.py 2021-01-05 10:50:26.004873175 +0100
@@ -148,7 +148,7 @@
'../libusb/x86/libusb-1.0.dll', '../libusb/x64/libusb-1.0.dll',
'../libusb/x64/libusb-1.0.dylib', '../libusb/LICENSE']
},
- python_requires='>=3.6, <3.9',
+ python_requires='>=3.6, <3.10',
install_requires=reqs,
zipfile=None,
tests_require=[
```
``` console
$ patch -p1 < requirements.patch
$ patch -p1 < setup.patch
```
Then install the patched `nrfutil`
``` console
$ pip install .
$ # verify installation
$ nrfutil version
nrfutil version 6.1.0
```
*end of nrfutil python 3.9 workaround*

View file

@ -10,7 +10,7 @@ Inside the 'configuration 1' rectangle there are two rectangles labeled 'control
Inside the 'interface 0' rectangle there are three rectangles labeled 'endpoint 1 IN', 'endpoint 2 IN' and 'endpoint 2 OUT'. Between these three rectangle there is a label that says 'bNumEndpoints=3'; it indicates that this interface has only three endpoints.">
<p>
An interface is closest to a USB device's function. For example, a USB mouse may expose a single HID (Human Interface Device) interface to report user input to the host. USB devices can expose multiple interfaces within a configuration. For example, the nRF52840 Dongle could expose both a CDC ACM interface (AKA virtual serial port) *and* a HID interface; the first interface could be used for (`log::info!`-style) logs; and the second one could provide a RPC (Remote Procedure Call) interface to the host for controlling the nRF52840's radio.
An interface is closest to a USB device's function. For example, a USB mouse may expose a single HID (Human Interface Device) interface to report user input to the host. USB devices can expose multiple interfaces within a configuration. For example, the nRF52840 Dongle could expose both a CDC ACM interface (AKA virtual serial port) *and* a HID interface; the first interface could be used for (`defmt::info!`-style) logs; and the second one could provide a RPC (Remote Procedure Call) interface to the host for controlling the nRF52840's radio.
An interface is made up of one or more *endpoints*. To give an example, a HID interface can use two (interrupt) endpoints, one IN and one OUT, for bidirectional communication with the host. A single endpoint cannot be used by more than one interface with the exception of the special "endpoint 0", which can be (and usually is) shared by all interfaces.

View file

@ -32,11 +32,11 @@ let array1: [u8; 3] = [0, 1, 2];
let array2: [u8; 4] = [0, 1, 2, 3];
let mut slice: &[u8] = &array1;
log::info!("{:?}", slice); // length = 3
defmt::info!("{:?}", slice); // length = 3
// now point to the other array
slice = &array2;
log::info!("{:?}", slice); // length = 4
defmt::info!("{:?}", slice); // length = 4
```
## Byte literals
@ -82,8 +82,8 @@ On the other hand, `"Hello"` is a string literal with type `&str`. `str` strings
In this workshop we'll work with ASCII strings so byte string literals that contain no escaped characters are OK to use as packet payloads.
You'll note that `log::info!("{:?}", b"Hello")` will print `[72, 101, 108, 108, 111]` rather than `"Hello"` and that the `{}` format specifier (`Display`) does not work. This is because the type of the literal is `&[u8; N]` and in Rust this type means "bytes"; those bytes could be ASCII data, UTF-8 data or something else.
You'll note that `defmt::info!("{:?}", b"Hello")` will print `[72, 101, 108, 108, 111]` rather than `"Hello"` and that the `{}` format specifier (`Display`) does not work. This is because the type of the literal is `&[u8; N]` and in Rust this type means "bytes"; those bytes could be ASCII data, UTF-8 data or something else.
To print this you'll need to convert the slice `&[u8]` into a string (`&str`) using the `str::from_utf8` function. This function will verify that the slice contains well formed UTF-8 data and interpret it as a UTF-8 string (`&str`). As long as we use ASCII data (printable ASCII characters) this conversion will not fail.
Something similar will happen with byte literals: `log::info!("{}", b'A')` will print `65` rather than `A`. To get the `A` output you can cast the byte literal (`u8` value) to the `char` type: `log::info!("{}", b'A' as char)`.
Something similar will happen with byte literals: `defmt::info!("{}", b'A')` will print `65` rather than `A`. To get the `A` output you can cast the byte literal (`u8` value) to the `char` type: `defmt::info!("{}", b'A' as char)`.

View file

@ -56,7 +56,7 @@ fn main() -> ! {
for plainletter in 0..=127 {
/* ... send letter to dongle ... */
log::info!("got response");
defmt::info!("got response");
/* ... store output ... */
timer.wait(Duration::from_millis(20));

View file

@ -1,11 +1,13 @@
# Radio Puzzle
![illustration showing that you send plaintext and the dongle responds with ciphertext](../img/puzzle_illustration.jpg)
Your task in this section is to decrypt the [substitution cipher] encrypted *ASCII* string stored in the Dongle. The string has been encrypted using *simple substitution*.
[substitution cipher]: https://en.wikipedia.org/wiki/Substitution_cipher
✅ Flash the `puzzle.hex` program on the Dongle. Follow the instructions from the "nRF52840 Dongle" section but flash the `puzzle.hex` program instead of the `loopback.hex` one -- don't forget to put the Dongle in bootloader mode before invoking `dongle-flash`.
✅ Flash the `puzzle.hex` program on the Dongle. Follow the instructions from the "nRF52840 Dongle" section but flash the `puzzle.hex` program instead of the `loopback.hex` one -- don't forget to put the Dongle in bootloader mode before invoking `nrfdfu`.
> Note: If you experienced USB issues with `loopback.hex` you use the `puzzle-nousb*.hex` variants.

View file

@ -12,7 +12,7 @@ RTIC makes a clearer distinction between the application's initialization phase,
✅ Build the `rtic-hello` example and look at the generated `rtic-expansion.rs` file.
The generated code should look like this. Note that interrupts are disabled during the execution of the `init` function:
You can use `rustfmt` on `target/rtic-expansion.rs` to make the generated code easier to read. Among other things, the file should contain the following lines. Note that interrupts are disabled during the execution of the `init` function:
``` rust
fn main() -> ! {

View file

@ -7,24 +7,32 @@ In `usb-2.rs`, you will find a short description of each register above the vari
[nrf product spec]: https://infocenter.nordicsemi.com/pdf/nRF52840_PS_v1.1.pdf
## Writing a parser for the data of this SETUP stage.
❗️ Keep the cable connected to the J3 port for the rest of the workshop
✅ Parse GET_DESCRIPTOR requests for DEVICE descriptors.
Modify `Request::parse()` in `advanced/common/usb/src/lib.rs` to recognize a GET_DESCRIPTOR request of type DEVICE so that the `get_descriptor_device` test passes. Note that the parser already handles SET_ADDRESS requests.
**Getting Started:**
**1. Writing code that can be tested**
When you need to write some `no_std` code that does not involve device-specific I/O you should consider writing it as a separate crate. This way, you can test it on your development machine (e.g. `x86_64`) using the standard `cargo test` functionality.
So that's what we'll do here. In `advanced/common/usb/lib.rs` you'll find starter code for writing a `no_std` SETUP data parser. The starter code contains some unit tests; you can run them with `cargo test` (from within the `usb` folder) or you can use Rust Analyzer's "Test" button in VS code.
The definition of `Descriptor::Configuration` as well as the associated test has been "commented out" using an `#[cfg(TODO)]` attribute because it is not handled by the firmware yet. Delete the `#[cfg(TODO)]` so that the unit tests can access it. This pattern is used for enum members and test functions throughout this workshop, so keep it in mind should you see it again.
✅ Parse the data of this SETUP stage.
❗️ Keep the cable connected to the J3 port for the rest of the workshop
Start with the GET_DESCRIPTOR request, which is described in detail in section 9.4.3 of the [USB specification][usb_spec]. All the constants we'll be using are also described in Tables 9-3, 9-4 and 9-5 of the same document.
[usb_spec]: https://www.usb.org/document-library/usb-20-specification
**2. Description of GET_DESCRIPTOR request**
We can recognize a GET_DESCRIPTOR request by the following properties:
- `bmRequestType` is **0b10000000**
- `bRequest` is **6** (i.e. the GET_DESCRIPTOR Request Code, defined in table 9-4 in the USB spec)
**3. Description of DEVICE descriptor requests**
In this task, we only want to parse DEVICE descriptor requests. They have the following properties:
- the descriptor type is **1** (i.e. DEVICE, defined in table 9-5 of the USB spec)
@ -32,29 +40,36 @@ In this task, we only want to parse DEVICE descriptor requests. They have the fo
- the wIndex is **0** for our purposes
- ❗you need to fetch the descriptor type from the high byte of `wValue`, and the descriptor index from the the low byte of `wValue`
Check section 9.4.3 of the [USB specification][usb_spec] for a very detailed description of the requests. All the constants we'll be using are also described in Tables 9-3, 9-4 and 9-5 of the same document.
**4. Remember that you can define binary literals by prefixing them with `0b`.**
**5. You can use bit shifts (`>>`) and casts (`as u8`) to get the high/low bytes of `wValue`.**
**6. Return `Err` if properties aren't met.**
You will also find this information in the `// TODO implement ...` comment in the `Request::parse()` function of `lib.rs` file.
> NOTE: If you'd like to learn more, take a look at Section 9.4.3 Get Descriptor of the USB specification.
To complete the task, proceed like this:
1. **Parse GET_DESCRIPTOR requests for DEVICE descriptors:**
Modify `Request::parse()` in `advanced/common/usb/src/lib.rs` to recognize a GET_DESCRIPTOR request of type DEVICE so that the `get_descriptor_device` test passes. Note that the parser already handles SET_ADDRESS requests.
- remember the GET_DESCRIPTOR fields described at the start of this section
- remember that you can define binary literals by prefixing them with `0b`
- you can use bit shifts (`>>`) and casts (`as u8`) to get the high/low bytes of `wValue`
See `advanced/common/usb/solution-get-descriptor-device.rs` for a solution.
2. **Read incoming request information and pass it to the parser:**
modify `usb-2.rs` to read `USBD` registers and parse the SETUP data when an EPSETUP event is received.
- for a mapping of register names to the `USBD` API, check the entry for `nrf52840_hal::target::usbd` in the documentation you've created using `cargo doc`
- remember that we've learned how to read registers in `events.rs`
- you will need to put together the higher and lower bits of `wlength`, `windex` and `wvalue` to get the whole field
- > Note: If you're using a Mac, you need to catch `SetAddress` requests returned by the parser as these are sent before the first GetDescriptor request. You can handle them by doing nothing.
✅ Read incoming request information and pass it to the parser:
3. when you have successfully received a GET_DESCRIPTOR request for a Device descriptor you are done. You should see an output like this:
modify `usb-2.rs` to read `USBD` registers and parse the SETUP data when an EP0SETUP event is received.
**Getting Started:**
- for a mapping of register names to the `USBD` API, check the entry for `nrf52840_hal::target::usbd` in the documentation you've created using `cargo doc`
- let bmrequesttype = usbd.bmrequesttype.read().bits() as u8;
- remember that we've learned how to read registers in `events.rs`.
- you will need to put together the higher and lower bits of `wlength`, `windex` and `wvalue` to get the whole field
- > Note: If you're using a Mac, you need to catch `SetAddress` requests returned by the parser as these are sent before the first GetDescriptor request. You can handle them by doing nothing.
**Expected Result:**
When you have successfully received a GET_DESCRIPTOR request for a Device descriptor you are done. You should see an output like this:
``` console
INFO:usb_2 -- USB: UsbReset @ 438.842772ms
@ -65,7 +80,9 @@ INFO:usb_2 -- GET_DESCRIPTOR Device [length=64]
INFO:usb_2 -- Goal reached; move to the next section
```
`wlength` / `length` can vary depending on the OS, USB port (USB 2.0 vs USB 3.0) or the presence of a USB hub so you may see a different value.
> Note: `wlength` / `length` can vary depending on the OS, USB port (USB 2.0 vs USB 3.0) or the presence of a USB hub so you may see a different value.
You can find a solution to step 1. in `advanced/common/usb/solution-get-descriptor-device.rs`.
You can find a solution to step 2. in `advanced/firmware/src/bin/usb-2-solution.rs`.
You can find a solution to this step in `advanced/firmware/src/bin/usb-2-solution.rs`.
[usb_spec]: https://www.usb.org/document-library/usb-20-specification

View file

@ -12,4 +12,4 @@ The other time related API exposed by the `dk` HAL is the `dk::uptime` function.
✅ Try changing the `Duration` value passed to `Timer.wait`. Try values larger than one second and smaller than one second. What values of `Duration` make the blinking imperceptible?
❗If you set the duration to below 100ms, try removing the `log::info!` command in the loop. Too much logging will fill the logging buffer and cause the loop to slow down, resulting in the blink frequency to reduce after a while.
❗If you set the duration to below 2ms, try removing the `defmt::info!` command in the loop. Too much logging will fill the logging buffer and cause the loop to slow down, resulting in the blink frequency to reduce after a while.

View file

@ -4,14 +4,7 @@
✅ Let's check that you have installed all the tools listed in the previous section.
❗ The first two commands *must* return version `0.8.x`
``` console
$ cargo size --version
cargo-size 0.3.3
$ nrfutil version
nrfutil version 6.1.0
```
✅ Now let's make sure you've installed the tools shipped with the workshop material.

View file

@ -1,13 +0,0 @@
# `dongle-flash` not working
``` console
$ dongle-flash loopback.hex
packaging iHex using nrfutil ...
Error: No such file or directory (os error 2)
```
this indicates that `nrfutil`, the Python tool, is not installed or not available in your PATH. Instructions on how to install `nrfutil` can be found in the [`nrfutil` section of the Installation Instructions].
❗️ If you install `nrfutil` in a virtual environment you'll need to activate the environment; the `nrfutil` binary must be available in your PATH.
[`nrfutil` section of the Installation Instructions]: ./installation.md#nrfutil

View file

@ -38,7 +38,7 @@ In this case you should flash one of the `loopback-nousb*` programs:
Put the device in bootloader mode again. Now, run
```console
$ cargo xtask dongle-flash boards/dongle/loopback-nousb21.hex # you can pick 11, 16, 21 or 26
$ nrfdfu boards/dongle/loopback-nousb21 # you can pick 11, 16, 21 or 26
```
❗️ The number in the `loopback-nousb*` file name is the radio channel the Dongle will listen on. This means that when you program the Development Kit to send data to the Dongle, you need to ensure they are communicating on the same channel by setting

View file

@ -10,22 +10,24 @@ In this starter code the USBD peripheral is initialized in `init` and a task, na
❗️ Keep the cable connected to the J3 port for the rest of the workshop
Go to `fn on_event`, line 39. In this section you'll need to implement the following USB events until you reach the EP0SETUP event:
This code will panic because `USBRESET` is not implemented yet.
- `USBRESET`. This event indicates that the host issued a USB reset signal. According to the USB specification this will move the device from any state to the `Default` state. Since we are currently not dealing with any other state, you can handle this state by doing nothing.
- `EP0SETUP`. The USBD peripheral has detected the SETUP stage of a control transfer. If you get to this point move to the next section.
- `EP0DATADONE`. The USBD peripheral is signaling the end of the DATA stage of a control transfer. You won't encounter this event just yet.
When you are done you should see this output:
✅ Go to `fn on_event`, line 39. In this section you'll need to implement the following USB events `USBRESET` and `EP0SETUP` so that your log output will look like this:
``` console
(..)
INFO:usb_1 -- USB: UsbReset
INFO:usb_1 -- returning to the Default state
INFO:usb_1 -- USB: UsbEp0Setup
INFO:usb_1 -- goal reached; move to the next section
INFO:dk -- `dk::exit() called; exiting ...
```
Do not overthink this exercise; it is not a trick question. There is very little to do and no new functionality to add.
## Help
- `USBRESET`. This event indicates that the host issued a USB reset signal. According to the USB specification this will move the device from any state to the `Default` state. Since we are currently not dealing with any other state, you can handle this state by adding a log statement to provide information that this event occurred.
- `EP0DATADONE`. The USBD peripheral is signaling the end of the DATA stage of a control transfer. Since you won't encounter this event just yet, you can leave it as it is.
- `EP0SETUP`. The USBD peripheral has detected the SETUP stage of a control transfer. Add a log statement containing "goal reached; move to the next section" and exit the application.
You can find the solution in the `usb-1-solution.rs` file.

View file

@ -2,7 +2,17 @@
Open the `src/bin/led.rs` file.
The `dk` crate / library is a Hardware Abstraction Layer (HAL) over the nRF52840 Development Kit. The purpose of a HAL is to abstract away the device-specific details of the hardware, for example registers, and instead expose a higher level API more suitable for application development.
You'll see that it initializes your board using the `dk` crate:
```rust
let board = dk::init().unwrap();
```
This grants you access to the board's peripherals, like its LEDs.
The `dk` crate / library is a Board Support Crate tailored to this workshop to make accessing the peripherals used in this workshop extra seamless.
You can find its source code at `boards/dk/src/`.
`dk` is based on the [`nrf52840-hal`] crate, which is a Hardware Abstraction Layer (HAL) over the nRF52840 Development Kit. The purpose of a HAL is to abstract away the device-specific details of the hardware, for example registers, and instead expose a higher level API more suitable for application development.
The `dk::init` function we have been calling in all programs initializes a few of the nRF52840 peripherals and returns a `Board` structure that provides access to those peripherals. We'll first look at the `Leds` API.
@ -16,12 +26,25 @@ The `dk::init` function we have been calling in all programs initializes a few o
$ cargo doc -p dk --open
```
✅ Check the API docs of the `Led` abstraction then run the `led` program. Change the `led` program, so that the bottom two leds are turned on, and the top two are turned off.
✅ Check the API docs of the `Led` abstraction. Change the `led` program, so that the bottom two LEDs are turned on, and the top two are turned off.
✅ Uncomment the `log::set_max_level` line. This will make the logs more verbose; they will now include logs from the board initialization function (`dk::init`) and from the `Led` API.
🔎 If you want to see logs from Led API of the `dk` Hardware Abstraction Layer, go to `boards/dk/Cargo.toml` and change the log level of the `dk` crate:
```diff
# set defmt logging levels here
default = [
- "defmt-debug",
+ "defmt-trace",
# "dependency-a/defmt-trace",
]
```
Among the logs you'll find the line "I/O pins have been configured for digital output". At this point the electrical pins of the nRF52840 microcontroller have been configured to drive the 4 LEDs on the board.
After the `dk::init` logs you'll find logs about the `Led` API. As the logs indicate an LED becomes active when the output of the pin is a *logical zero*, which is also referred as the "low" state. This "active low" configuration does not apply to all boards: it depends on how the pins have been wired to the LEDs. You should refer to the [board documentation] to find out which pins are connected to LEDs and whether "active low" or "active high" applies to it.
🔎 When writing your own embedded project, you can implement your own convenience layer similar to `dk`, or use the matching HAL crate for your board directly. Check out [awesome-embedded-rust] if there's a HAL crate for the board you'd like to use.
[`nrf52840-hal`]: https://docs.rs/nrf52840-hal/0.12.1/nrf52840_hal/
[board documentation]: https://infocenter.nordicsemi.com/index.jsp?topic=%2Fug_nrf52840_dk%2FUG%2Fnrf52840_DK%2Fintro.html
[awesome-embedded-rust]: https://github.com/rust-embedded/awesome-embedded-rust#hal-implementation-crates

View file

@ -12,6 +12,9 @@ hidapi = "1.2.2"
ihex = "1.1.2"
pids = { path = "../common/pids" }
rusb = "0.5.5"
serialport = "3.3.0"
# Note: we are using a fork due compatibility problems.
# See https://github.com/ferrous-systems/embedded-trainings-2020/issues/148 for more details.
# serialport = "4.0.2"
serialport = { git = "https://github.com/ferrous-systems/serialport-rs-hotfix.git", branch = "fix-usb-deprecation"}
tempfile = "3.2.0"
xmas-elf = "0.7.0"
xmas-elf = "0.7.0"

View file

@ -198,7 +198,7 @@ pub fn serial_term() -> color_eyre::Result<()> {
}
};
let mut port = serialport::open(&dongle.port_name)?;
let mut port = serialport::new(&dongle.port_name, 115200).open()?;
static CONTINUE: AtomicBool = AtomicBool::new(true);
@ -227,7 +227,15 @@ 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",
(0x1366, pid) => {
let higher_byte = pid >> 8;
// 0x1015 and 0x0105 are valid PIDs for a J-Link probe
if higher_byte == 0x10 || higher_byte == 0x01 {
" <- J-Link on the nRF52840 Development Kit"
} else {
""
}
}
(0x1915, 0x521f) => " <- nRF52840 Dongle (in bootloader mode)",
(0x2020, pids::LOOPBACK) => " <- nRF52840 Dongle (loopback.hex)",
(0x2020, pids::PUZZLE) => " <- nRF52840 Dongle (puzzle.hex)",