diff --git a/.gitignore b/.gitignore index 4f9f70c..b71c776 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ target/ book/ -Cargo.lock \ No newline at end of file +Cargo.lock + +.vscode/ +*.DS_Store diff --git a/Advanced Embedded Training.pdf b/Advanced Embedded Training.pdf index 56d1820..6aa7f9b 100644 Binary files a/Advanced Embedded Training.pdf and b/Advanced Embedded Training.pdf differ diff --git a/Beginner Embedded Training.pdf b/Beginner Embedded Training.pdf index 6abef03..4f9200a 100644 Binary files a/Beginner Embedded Training.pdf and b/Beginner Embedded Training.pdf differ diff --git a/advanced/common/usb/src/lib.rs b/advanced/common/usb/src/lib.rs index 6e2dde6..65fe58b 100644 --- a/advanced/common/usb/src/lib.rs +++ b/advanced/common/usb/src/lib.rs @@ -51,6 +51,7 @@ impl Request { windex: u16, wlength: u16, ) -> Result { + // Request Codes // see table 9-4 (USB specification) const SET_ADDRESS: u8 = 5; diff --git a/advanced/firmware/Cargo.toml b/advanced/firmware/Cargo.toml index 85fd4a6..cf2f9dd 100644 --- a/advanced/firmware/Cargo.toml +++ b/advanced/firmware/Cargo.toml @@ -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 diff --git a/advanced/firmware/src/bin/stack_overflow.rs b/advanced/firmware/src/bin/stack_overflow.rs index 2546126..ab08067 100644 --- a/advanced/firmware/src/bin/stack_overflow.rs +++ b/advanced/firmware/src/bin/stack_overflow.rs @@ -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 diff --git a/advanced/firmware/src/bin/usb-1.rs b/advanced/firmware/src/bin/usb-1.rs index c297f43..0f6dd2e 100644 --- a/advanced/firmware/src/bin/usb-1.rs +++ b/advanced/firmware/src/bin/usb-1.rs @@ -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() diff --git a/advanced/firmware/src/bin/usb-4-solution.rs b/advanced/firmware/src/bin/usb-4-solution.rs index 49ef01a..53c30e2 100644 --- a/advanced/firmware/src/bin/usb-4-solution.rs +++ b/advanced/firmware/src/bin/usb-4-solution.rs @@ -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::::new(); + let mut resp = heapless::Vec::::new(); let conf_desc = usb2::configuration::Descriptor { wTotalLength: (usb2::configuration::Descriptor::SIZE diff --git a/advanced/firmware/src/bin/usb-5-solution.rs b/advanced/firmware/src/bin/usb-5-solution.rs index 853bda1..bf12ddf 100644 --- a/advanced/firmware/src/bin/usb-5-solution.rs +++ b/advanced/firmware/src/bin/usb-5-solution.rs @@ -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::::new(); + let mut resp = heapless::Vec::::new(); let conf_desc = usb2::configuration::Descriptor { wTotalLength: (usb2::configuration::Descriptor::SIZE diff --git a/advanced/firmware/src/bin/vec.rs b/advanced/firmware/src/bin/vec.rs index f8b42b2..f009ee6 100644 --- a/advanced/firmware/src/bin/vec.rs +++ b/advanced/firmware/src/bin/vec.rs @@ -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::::new(); - // ^^ capacity; this is a type not a value + let mut buffer = Vec::::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"); diff --git a/advanced/firmware/src/lib.rs b/advanced/firmware/src/lib.rs index 7480423..3446a8d 100644 --- a/advanced/firmware/src/lib.rs +++ b/advanced/firmware/src/lib.rs @@ -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(); - } -} diff --git a/beginner/apps/.cargo/config.toml b/beginner/apps/.cargo/config.toml index b697f78..abfd4b6 100644 --- a/beginner/apps/.cargo/config.toml +++ b/beginner/apps/.cargo/config.toml @@ -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 \ No newline at end of file diff --git a/beginner/apps/Cargo.toml b/beginner/apps/Cargo.toml index 633f9b2..eecc55e 100644 --- a/beginner/apps/Cargo.toml +++ b/beginner/apps/Cargo.toml @@ -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 diff --git a/beginner/apps/Embed.toml b/beginner/apps/Embed.toml deleted file mode 100644 index 503a4eb..0000000 --- a/beginner/apps/Embed.toml +++ /dev/null @@ -1,6 +0,0 @@ -[default.general] -chip = "nRF52840_xxAA" - -[default.rtt] -enabled = true -channels = [{ up = 0 }] \ No newline at end of file diff --git a/beginner/apps/src/bin/blinky.rs b/beginner/apps/src/bin/blinky.rs index abf020c..a3e10b1 100644 --- a/beginner/apps/src/bin/blinky.rs +++ b/beginner/apps/src/bin/blinky.rs @@ -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() diff --git a/beginner/apps/src/bin/hello.rs b/beginner/apps/src/bin/hello.rs index fbb5eba..0c40dce 100644 --- a/beginner/apps/src/bin/hello.rs +++ b/beginner/apps/src/bin/hello.rs @@ -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(); diff --git a/beginner/apps/src/bin/panic.rs b/beginner/apps/src/bin/panic.rs index 272f2b6..b065acb 100644 --- a/beginner/apps/src/bin/panic.rs +++ b/beginner/apps/src/bin/panic.rs @@ -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); } \ No newline at end of file diff --git a/beginner/apps/src/bin/radio-puzzle-1.rs b/beginner/apps/src/bin/radio-puzzle-1.rs index 5c798b3..95f1946 100644 --- a/beginner/apps/src/bin/radio-puzzle-1.rs +++ b/beginner/apps/src/bin/radio-puzzle-1.rs @@ -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 diff --git a/beginner/apps/src/bin/radio-puzzle-2.rs b/beginner/apps/src/bin/radio-puzzle-2.rs index 083ed35..edab631 100644 --- a/beginner/apps/src/bin/radio-puzzle-2.rs +++ b/beginner/apps/src/bin/radio-puzzle-2.rs @@ -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"); diff --git a/beginner/apps/src/bin/radio-puzzle-3.rs b/beginner/apps/src/bin/radio-puzzle-3.rs index dea6b6d..96f9e39 100644 --- a/beginner/apps/src/bin/radio-puzzle-3.rs +++ b/beginner/apps/src/bin/radio-puzzle-3.rs @@ -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::::new(); + let mut dict = LinearMap::::new(); let mut packet = Packet::new(); // TODO do the whole ASCII range [0, 127] diff --git a/beginner/apps/src/bin/radio-puzzle-4.rs b/beginner/apps/src/bin/radio-puzzle-4.rs index 7b5950d..e4a8c69 100644 --- a/beginner/apps/src/bin/radio-puzzle-4.rs +++ b/beginner/apps/src/bin/radio-puzzle-4.rs @@ -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::::new(); - // ^^ capacity; this is a type not a value + let mut buffer = Vec::::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) diff --git a/beginner/apps/src/bin/radio-puzzle-5.rs b/beginner/apps/src/bin/radio-puzzle-5.rs index 80be341..e46643a 100644 --- a/beginner/apps/src/bin/radio-puzzle-5.rs +++ b/beginner/apps/src/bin/radio-puzzle-5.rs @@ -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::::new(); + let mut buf = Vec::::new(); // iterate over the bytes for input in packet.iter() { diff --git a/beginner/apps/src/bin/radio-puzzle-6.rs b/beginner/apps/src/bin/radio-puzzle-6.rs index 9ff925b..d1c326c 100644 --- a/beginner/apps/src/bin/radio-puzzle-6.rs +++ b/beginner/apps/src/bin/radio-puzzle-6.rs @@ -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::::new(); + let dict = LinearMap::::new(); let mut packet = Packet::new(); for source in 0..=127 { @@ -62,7 +62,7 @@ fn main() -> ! { ); /* # Decrypt the string */ - let mut buffer = Vec::::new(); + let mut buffer = Vec::::new(); // iterate over the bytes for byte in packet.iter() { diff --git a/beginner/apps/src/bin/radio-puzzle-7.rs b/beginner/apps/src/bin/radio-puzzle-7.rs index be25024..9116db8 100644 --- a/beginner/apps/src/bin/radio-puzzle-7.rs +++ b/beginner/apps/src/bin/radio-puzzle-7.rs @@ -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::::new(); + let dict = LinearMap::::new(); let mut packet = Packet::new(); for source in 0..=127 { @@ -62,7 +62,7 @@ fn main() -> ! { ); /* # Decrypt the string */ - let mut buffer = Vec::::new(); + let mut buffer = Vec::::new(); // iterate over the bytes for byte in packet.iter() { diff --git a/beginner/apps/src/bin/radio-puzzle-solution-2.rs b/beginner/apps/src/bin/radio-puzzle-solution-2.rs index 1752e5b..6007f0f 100644 --- a/beginner/apps/src/bin/radio-puzzle-solution-2.rs +++ b/beginner/apps/src/bin/radio-puzzle-solution-2.rs @@ -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::::new(); + let mut dict = LinearMap::::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]); diff --git a/beginner/apps/src/bin/radio-puzzle-solution.rs b/beginner/apps/src/bin/radio-puzzle-solution.rs index 6c834a6..4a7e00f 100644 --- a/beginner/apps/src/bin/radio-puzzle-solution.rs +++ b/beginner/apps/src/bin/radio-puzzle-solution.rs @@ -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::::new(); - // ^^^^^^^^^^^^ NOTE larger capacity + let mut dict = LinearMap::::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::::new(); + let mut buffer = Vec::::new(); // iterate over the bytes for cipherletter in packet.iter() { diff --git a/beginner/apps/src/bin/radio-recv.rs b/beginner/apps/src/bin/radio-recv.rs index d1f4fe3..e3642b0 100644 --- a/beginner/apps/src/bin/radio-recv.rs +++ b/beginner/apps/src/bin/radio-recv.rs @@ -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), } diff --git a/beginner/apps/src/bin/stack_overflow.rs b/beginner/apps/src/bin/stack_overflow.rs index b06cb53..24c73da 100644 --- a/beginner/apps/src/bin/stack_overflow.rs +++ b/beginner/apps/src/bin/stack_overflow.rs @@ -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 diff --git a/beginner/apps/src/lib.rs b/beginner/apps/src/lib.rs index 7480423..3446a8d 100644 --- a/beginner/apps/src/lib.rs +++ b/beginner/apps/src/lib.rs @@ -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(); - } -} diff --git a/boards/dk/Cargo.toml b/boards/dk/Cargo.toml index 989c418..a4213f6 100644 --- a/boards/dk/Cargo.toml +++ b/boards/dk/Cargo.toml @@ -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", ] diff --git a/boards/dk/src/lib.rs b/boards/dk/src/lib.rs index d010023..a368db6 100644 --- a/boards/dk/src/lib.rs +++ b/boards/dk/src/lib.rs @@ -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 { // 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 }; diff --git a/boards/dongle/README.md b/boards/dongle/README.md index cb24652..f7f8968 100644 --- a/boards/dongle/README.md +++ b/boards/dongle/README.md @@ -47,7 +47,7 @@ to: [81, 78, 109, 61, 120, 87, 125, 98, 100, 91, 97, 66, 57, 117, 49, 64, 48, 85 secret: ">?>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">?>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 ` 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 diff --git a/boards/dongle/deprecated_hex/README.md b/boards/dongle/deprecated_hex/README.md new file mode 100644 index 0000000..c633af8 --- /dev/null +++ b/boards/dongle/deprecated_hex/README.md @@ -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. \ No newline at end of file diff --git a/boards/dongle/loopback-nousb11.hex b/boards/dongle/deprecated_hex/loopback-nousb11.hex similarity index 100% rename from boards/dongle/loopback-nousb11.hex rename to boards/dongle/deprecated_hex/loopback-nousb11.hex diff --git a/boards/dongle/loopback-nousb16.hex b/boards/dongle/deprecated_hex/loopback-nousb16.hex similarity index 100% rename from boards/dongle/loopback-nousb16.hex rename to boards/dongle/deprecated_hex/loopback-nousb16.hex diff --git a/boards/dongle/loopback-nousb21.hex b/boards/dongle/deprecated_hex/loopback-nousb21.hex similarity index 100% rename from boards/dongle/loopback-nousb21.hex rename to boards/dongle/deprecated_hex/loopback-nousb21.hex diff --git a/boards/dongle/loopback-nousb26.hex b/boards/dongle/deprecated_hex/loopback-nousb26.hex similarity index 100% rename from boards/dongle/loopback-nousb26.hex rename to boards/dongle/deprecated_hex/loopback-nousb26.hex diff --git a/boards/dongle/loopback.hex b/boards/dongle/deprecated_hex/loopback.hex similarity index 100% rename from boards/dongle/loopback.hex rename to boards/dongle/deprecated_hex/loopback.hex diff --git a/boards/dongle/puzzle-nousb11.hex b/boards/dongle/deprecated_hex/puzzle-nousb11.hex similarity index 100% rename from boards/dongle/puzzle-nousb11.hex rename to boards/dongle/deprecated_hex/puzzle-nousb11.hex diff --git a/boards/dongle/puzzle-nousb16.hex b/boards/dongle/deprecated_hex/puzzle-nousb16.hex similarity index 100% rename from boards/dongle/puzzle-nousb16.hex rename to boards/dongle/deprecated_hex/puzzle-nousb16.hex diff --git a/boards/dongle/puzzle-nousb21.hex b/boards/dongle/deprecated_hex/puzzle-nousb21.hex similarity index 100% rename from boards/dongle/puzzle-nousb21.hex rename to boards/dongle/deprecated_hex/puzzle-nousb21.hex diff --git a/boards/dongle/puzzle-nousb26.hex b/boards/dongle/deprecated_hex/puzzle-nousb26.hex similarity index 100% rename from boards/dongle/puzzle-nousb26.hex rename to boards/dongle/deprecated_hex/puzzle-nousb26.hex diff --git a/boards/dongle/puzzle.hex b/boards/dongle/deprecated_hex/puzzle.hex similarity index 100% rename from boards/dongle/puzzle.hex rename to boards/dongle/deprecated_hex/puzzle.hex diff --git a/boards/dongle/loopback b/boards/dongle/loopback new file mode 100755 index 0000000..30f7ff6 Binary files /dev/null and b/boards/dongle/loopback differ diff --git a/boards/dongle/loopback-nousb.rs b/boards/dongle/loopback-nousb.rs deleted file mode 100644 index 2283049..0000000 --- a/boards/dongle/loopback-nousb.rs +++ /dev/null @@ -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) -} diff --git a/boards/dongle/loopback-nousb11 b/boards/dongle/loopback-nousb11 new file mode 100755 index 0000000..ab0a0b1 Binary files /dev/null and b/boards/dongle/loopback-nousb11 differ diff --git a/boards/dongle/loopback-nousb16 b/boards/dongle/loopback-nousb16 new file mode 100755 index 0000000..d38f561 Binary files /dev/null and b/boards/dongle/loopback-nousb16 differ diff --git a/boards/dongle/loopback-nousb21 b/boards/dongle/loopback-nousb21 new file mode 100755 index 0000000..41c0f9f Binary files /dev/null and b/boards/dongle/loopback-nousb21 differ diff --git a/boards/dongle/loopback-nousb26 b/boards/dongle/loopback-nousb26 new file mode 100755 index 0000000..5b53798 Binary files /dev/null and b/boards/dongle/loopback-nousb26 differ diff --git a/boards/dongle/loopback.rs b/boards/dongle/loopback.rs deleted file mode 100644 index 65a886c..0000000 --- a/boards/dongle/loopback.rs +++ /dev/null @@ -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::::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::::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) -} diff --git a/boards/dongle/puzzle b/boards/dongle/puzzle new file mode 100755 index 0000000..f6ab71e Binary files /dev/null and b/boards/dongle/puzzle differ diff --git a/boards/dongle/puzzle-nousb.rs b/boards/dongle/puzzle-nousb.rs deleted file mode 100644 index b1962c8..0000000 --- a/boards/dongle/puzzle-nousb.rs +++ /dev/null @@ -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] = &[ - // -]; - -static TO: &[u8] = &[ - // -]; - -// store the secret rather than the plaintext -- otherwise `strings $elf` will reveal the answer -static SECRET: &[u8] = b""; - -#[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) -} diff --git a/boards/dongle/puzzle-nousb11 b/boards/dongle/puzzle-nousb11 new file mode 100755 index 0000000..1e8aa9b Binary files /dev/null and b/boards/dongle/puzzle-nousb11 differ diff --git a/boards/dongle/puzzle-nousb16 b/boards/dongle/puzzle-nousb16 new file mode 100755 index 0000000..3311ef5 Binary files /dev/null and b/boards/dongle/puzzle-nousb16 differ diff --git a/boards/dongle/puzzle-nousb21 b/boards/dongle/puzzle-nousb21 new file mode 100755 index 0000000..e14b886 Binary files /dev/null and b/boards/dongle/puzzle-nousb21 differ diff --git a/boards/dongle/puzzle-nousb26 b/boards/dongle/puzzle-nousb26 new file mode 100755 index 0000000..c88e790 Binary files /dev/null and b/boards/dongle/puzzle-nousb26 differ diff --git a/boards/dongle/puzzle.rs b/boards/dongle/puzzle.rs deleted file mode 100644 index c2b9727..0000000 --- a/boards/dongle/puzzle.rs +++ /dev/null @@ -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] = &[ - // -]; - -static TO: &[u8] = &[ - // -]; - -// store the secret rather than the plaintext -- otherwise `strings $elf` will reveal the answer -static SECRET: &[u8] = b""; - -#[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::::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::::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) -} diff --git a/embedded-workshop-book/src/DEPRECATED-nrfutil-setup.md b/embedded-workshop-book/src/DEPRECATED-nrfutil-setup.md new file mode 100644 index 0000000..b734000 --- /dev/null +++ b/embedded-workshop-book/src/DEPRECATED-nrfutil-setup.md @@ -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* \ No newline at end of file diff --git a/embedded-workshop-book/src/SUMMARY.md b/embedded-workshop-book/src/SUMMARY.md index e91bab6..be8ba33 100644 --- a/embedded-workshop-book/src/SUMMARY.md +++ b/embedded-workshop-book/src/SUMMARY.md @@ -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) diff --git a/embedded-workshop-book/src/data-stage.md b/embedded-workshop-book/src/data-stage.md index 0277231..ed4e718 100644 --- a/embedded-workshop-book/src/data-stage.md +++ b/embedded-workshop-book/src/data-stage.md @@ -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): diff --git a/embedded-workshop-book/src/dongle.md b/embedded-workshop-book/src/dongle.md index 7d96e1d..066176b 100644 --- a/embedded-workshop-book/src/dongle.md +++ b/embedded-workshop-book/src/dongle.md @@ -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: diff --git a/embedded-workshop-book/src/error-handling.md b/embedded-workshop-book/src/error-handling.md index fc7b2e6..3f7937a 100644 --- a/embedded-workshop-book/src/error-handling.md +++ b/embedded-workshop-book/src/error-handling.md @@ -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. diff --git a/embedded-workshop-book/src/from-scratch.md b/embedded-workshop-book/src/from-scratch.md index d64ce34..0bda8ac 100644 --- a/embedded-workshop-book/src/from-scratch.md +++ b/embedded-workshop-book/src/from-scratch.md @@ -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 diff --git a/embedded-workshop-book/src/get-descriptor-config.md b/embedded-workshop-book/src/get-descriptor-config.md index d20c453..fd718b9 100644 --- a/embedded-workshop-book/src/get-descriptor-config.md +++ b/embedded-workshop-book/src/get-descriptor-config.md @@ -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 diff --git a/embedded-workshop-book/src/hardware.md b/embedded-workshop-book/src/hardware.md index 0088ca2..cb077a0 100644 --- a/embedded-workshop-book/src/hardware.md +++ b/embedded-workshop-book/src/hardware.md @@ -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). diff --git a/embedded-workshop-book/src/hardware/labelled.jpg b/embedded-workshop-book/src/hardware/labelled.jpg deleted file mode 100644 index d494b41..0000000 Binary files a/embedded-workshop-book/src/hardware/labelled.jpg and /dev/null differ diff --git a/embedded-workshop-book/src/hardware/nrf52840_dk_board.jpg b/embedded-workshop-book/src/hardware/nrf52840_dk_board.jpg new file mode 100644 index 0000000..56824be Binary files /dev/null and b/embedded-workshop-book/src/hardware/nrf52840_dk_board.jpg differ diff --git a/embedded-workshop-book/src/img/puzzle_illustration.jpg b/embedded-workshop-book/src/img/puzzle_illustration.jpg new file mode 100644 index 0000000..1735e37 Binary files /dev/null and b/embedded-workshop-book/src/img/puzzle_illustration.jpg differ diff --git a/embedded-workshop-book/src/installation.md b/embedded-workshop-book/src/installation.md index 47fb3f5..9546fdc 100644 --- a/embedded-workshop-book/src/installation.md +++ b/embedded-workshop-book/src/installation.md @@ -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* diff --git a/embedded-workshop-book/src/interfaces.md b/embedded-workshop-book/src/interfaces.md index 2d639fb..56f942d 100644 --- a/embedded-workshop-book/src/interfaces.md +++ b/embedded-workshop-book/src/interfaces.md @@ -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.">

-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. diff --git a/embedded-workshop-book/src/messages.md b/embedded-workshop-book/src/messages.md index c0a765d..c6df898 100644 --- a/embedded-workshop-book/src/messages.md +++ b/embedded-workshop-book/src/messages.md @@ -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)`. \ No newline at end of file +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)`. \ No newline at end of file diff --git a/embedded-workshop-book/src/radio-puzzle-help.md b/embedded-workshop-book/src/radio-puzzle-help.md index f94eb7f..f529514 100644 --- a/embedded-workshop-book/src/radio-puzzle-help.md +++ b/embedded-workshop-book/src/radio-puzzle-help.md @@ -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)); diff --git a/embedded-workshop-book/src/radio-puzzle.md b/embedded-workshop-book/src/radio-puzzle.md index 352ce26..1d1b377 100644 --- a/embedded-workshop-book/src/radio-puzzle.md +++ b/embedded-workshop-book/src/radio-puzzle.md @@ -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. diff --git a/embedded-workshop-book/src/rtic-hello.md b/embedded-workshop-book/src/rtic-hello.md index ec14358..26d2a7a 100644 --- a/embedded-workshop-book/src/rtic-hello.md +++ b/embedded-workshop-book/src/rtic-hello.md @@ -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() -> ! { diff --git a/embedded-workshop-book/src/setup-stage.md b/embedded-workshop-book/src/setup-stage.md index a301a30..684e522 100644 --- a/embedded-workshop-book/src/setup-stage.md +++ b/embedded-workshop-book/src/setup-stage.md @@ -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 diff --git a/embedded-workshop-book/src/time.md b/embedded-workshop-book/src/time.md index 7992495..91b6cc5 100644 --- a/embedded-workshop-book/src/time.md +++ b/embedded-workshop-book/src/time.md @@ -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. diff --git a/embedded-workshop-book/src/tooling-check.md b/embedded-workshop-book/src/tooling-check.md index 82a69e3..11a4db0 100644 --- a/embedded-workshop-book/src/tooling-check.md +++ b/embedded-workshop-book/src/tooling-check.md @@ -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. diff --git a/embedded-workshop-book/src/troubleshoot-dongle-flash.md b/embedded-workshop-book/src/troubleshoot-dongle-flash.md deleted file mode 100644 index 0f624e6..0000000 --- a/embedded-workshop-book/src/troubleshoot-dongle-flash.md +++ /dev/null @@ -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 \ No newline at end of file diff --git a/embedded-workshop-book/src/troubleshoot-usb-dongle.md b/embedded-workshop-book/src/troubleshoot-usb-dongle.md index d35116f..62ad8a3 100644 --- a/embedded-workshop-book/src/troubleshoot-usb-dongle.md +++ b/embedded-workshop-book/src/troubleshoot-usb-dongle.md @@ -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 diff --git a/embedded-workshop-book/src/usb-events.md b/embedded-workshop-book/src/usb-events.md index 38c1d92..5761bb1 100644 --- a/embedded-workshop-book/src/usb-events.md +++ b/embedded-workshop-book/src/usb-events.md @@ -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. diff --git a/embedded-workshop-book/src/using-hal.md b/embedded-workshop-book/src/using-hal.md index f4f1758..1537497 100644 --- a/embedded-workshop-book/src/using-hal.md +++ b/embedded-workshop-book/src/using-hal.md @@ -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 \ No newline at end of file diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 2bd3968..89342e1 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -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" \ No newline at end of file +xmas-elf = "0.7.0" diff --git a/xtask/src/tasks.rs b/xtask/src/tasks.rs index 5fd6c1b..4183024 100644 --- a/xtask/src/tasks.rs +++ b/xtask/src/tasks.rs @@ -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)",