### Generate New Project using Cortex-M Quickstart Template Source: https://github.com/ferrous-systems/embedded-trainings-2020/blob/main/embedded-workshop-book/src/from-scratch.md Initializes a new embedded Rust project using the `cortex-m-quickstart` template via the `cargo-generate` tool. This provides a boilerplate setup for ARM Cortex-M microcontrollers. ```console $ cargo generate --git https://github.com/rust-embedded/cortex-m-quickstart ``` -------------------------------- ### Install probe-run using Cargo Source: https://github.com/ferrous-systems/embedded-trainings-2020/blob/main/embedded-workshop-book/src/installation.md Installs the probe-run tool, a utility for debugging embedded systems, using the Cargo package manager. Ensure Cargo is installed and configured correctly. ```console $ cargo install probe-run (..) Installed package `probe-run v0.3.5` (..) ``` -------------------------------- ### Install ELF Analysis Tools (Cargo and LLVM) Source: https://github.com/ferrous-systems/embedded-trainings-2020/blob/main/embedded-workshop-book/src/installation.md Installs `cargo-binutils` for analyzing ELF files and adds the `llvm-tools-preview` component to the Rust toolchain. These tools are useful for inspecting compiled code. ```console $ cargo install cargo-binutils ``` ```console $ rustup +stable component add llvm-tools-preview ``` -------------------------------- ### Install flip-link using Cargo Source: https://github.com/ferrous-systems/embedded-trainings-2020/blob/main/embedded-workshop-book/src/installation.md Installs the flip-link tool, used for managing firmware links, via the Cargo package manager. This command requires a working Cargo installation. ```console $ cargo install flip-link (..) Installed package `flip-link v0.1.6` (..) ``` -------------------------------- ### Clone Embedded Trainings Repository (Console) Source: https://github.com/ferrous-systems/embedded-trainings-2020/blob/main/embedded-workshop-book/src/installation.md Clones the embedded-trainings-2020 Git repository and navigates into the newly created directory. This is the first step to obtain all workshop materials. ```console $ git clone https://github.com/ferrous-systems/embedded-trainings-2020.git $ cd embedded-trainings-2020 ``` -------------------------------- ### Install nrfdfu using Cargo Source: https://github.com/ferrous-systems/embedded-trainings-2020/blob/main/embedded-workshop-book/src/installation.md Installs the nrfdfu tool, a utility for Nordic Semiconductor's DFU (Device Firmware Update) process, using Cargo. This requires Cargo to be set up on your system. ```console $ cargo install nrfdfu (..) Installed package `nrfdfu v0.1.3` (..) ``` -------------------------------- ### Probe-run Configuration Example Source: https://github.com/ferrous-systems/embedded-trainings-2020/blob/main/embedded-workshop-book/src/running-from-vsc.md An example TOML configuration snippet for `.cargo/config.toml` that specifies the `probe-run` runner for a specific target. This allows customization of `probe-run` flags, such as specifying the target chip. ```toml [target.thumbv7em-none-eabihf] runner = "probe-run --chip nRF52840_xxAA" # <- add/remove/modify flags here # .. ``` -------------------------------- ### Install and Use cargo-bloat for Binary Size Analysis Source: https://github.com/ferrous-systems/embedded-trainings-2020/blob/main/embedded-workshop-book/src/tooltips.md Demonstrates how to install `cargo-bloat` using cargo and then use it to analyze the binary size of a Rust program, breaking down the size contribution of different functions within the `.text` section. This helps identify areas for optimization. ```console $ cargo install cargo-bloat (..) Installed package `cargo-bloat v0.10.0` (..) $ cd beginner/apps $ cargo bloat --bin hello File .text Size Crate Name 0.7% 13.5% 1.3KiB std ::fmt 0.5% 9.6% 928B hello hello::__cortex_m_rt_main 0.4% 8.4% 804B std core::str::slice_error_fail 0.4% 8.0% 768B std core::fmt::Formatter::pad 0.3% 6.4% 614B std core::fmt::num::::fmt (..) 5.1% 100.0% 9.4KiB .text section size, the file size is 184.5KiB ``` -------------------------------- ### Install USB Development Packages (Debian Linux) Source: https://github.com/ferrous-systems/embedded-trainings-2020/blob/main/embedded-workshop-book/src/installation.md Installs necessary packages for USB development on Debian-based Linux distributions, specifically `libudev-dev` and `libusb-1.0-0-dev`. These are required for certain tools used in the workshop. ```console $ sudo apt-get install libudev-dev libusb-1.0-0-dev ``` -------------------------------- ### List Supported Rust Compilation Targets Source: https://github.com/ferrous-systems/embedded-trainings-2020/blob/main/embedded-workshop-book/src/from-scratch.md Lists all compilation targets supported by `rustup`. This is crucial for selecting a target compatible with your microcontroller's architecture. Bare metal targets often use 'none' for the OS field and may include 'hf' for hardfloat ABI. ```console $ rustup target list (..) thumbv6m-none-eabi thumbv7em-none-eabi thumbv7em-none-eabihf thumbv7m-none-eabi thumbv8m.base-none-eabi thumbv8m.main-none-eabi thumbv8m.main-none-eabihf ``` -------------------------------- ### Manage USB Endpoint 0 IN Data Transfers (Rust) Source: https://context7.com/ferrous-systems/embedded-trainings-2020/llms.txt This code manages IN data transfers on control endpoint 0 using the Ep0In struct. It utilizes the start() method to initiate a transfer and end() to complete it after a UsbEp0DataDone event. The example shows how to construct and send a USB device descriptor. Dependencies include 'dk' and 'defmt'. ```rust // Inside an RTIC task handler fn handle_get_descriptor(usbd: &USBD, ep0in: &mut Ep0In, length: u16) { // USB device descriptor (18 bytes) let desc: [u8; 18] = [ 18, // bLength 1, // bDescriptorType (Device) 0x00, 0x02, // bcdUSB (USB 2.0) 0, // bDeviceClass 0, // bDeviceSubClass 0, // bDeviceProtocol 64, // bMaxPacketSize0 0x20, 0x20, // idVendor 0x17, 0x07, // idProduct 0x00, 0x01, // bcdDevice 0, // iManufacturer 0, // iProduct 0, // iSerialNumber 1, // bNumConfigurations ]; // Truncate response to requested length (max 64 bytes per transfer) let resp_len = core::cmp::min(length as usize, desc.len()); // Start DMA transfer to host ep0in.start(&desc[..resp_len], usbd); } fn handle_data_done(usbd: &USBD, ep0in: &mut Ep0In) { // Called when UsbEp0DataDone event is received // Complete the transfer and free the endpoint ep0in.end(usbd); defmt::println!("Data transfer complete"); } ``` -------------------------------- ### Read USBD registers and parse SETUP data in Rust Source: https://github.com/ferrous-systems/embedded-trainings-2020/blob/main/embedded-workshop-book/src/setup-stage.md Modifies `usb-2.rs` to read `USBD` registers and parse SETUP data upon receiving an `EP0SETUP` event. This involves accessing register bits for `bmRequestType`, `wLength`, `wIndex`, and `wValue`. The code needs to correctly assemble these fields from the register values. It assumes the existence of a `USBD` peripheral and an event handling mechanism. ```rust use nrf52840_hal::target::usbd; use crate::usb::{Request, ParseError}; // Assuming an event handler structure like this: fn handle_event(usbd: &usbd::USBD, event: UsbEvent) { match event { UsbEvent::UsbEp0Setup => { let bmrequesttype = usbd.bmrequesttype.read().bits() as u8; let wValue_low = usbd.wvalue.read().bits() as u8; let wValue_high = usbd.wvalue.read().bits().wrapping_shr(8) as u8; let wValue = u16::from_le_bytes([wValue_low, wValue_high]); let wIndex_low = usbd.windex.read().bits() as u8; let wIndex_high = usbd.windex.read().bits().wrapping_shr(8) as u8; let wIndex = u16::from_le_bytes([wIndex_low, wIndex_high]); let wLength_low = usbd.wlength.read().bits() as u8; let wLength_high = usbd.wlength.read().bits().wrapping_shr(8) as u8; let wLength = u16::from_le_bytes([wLength_low, wLength_high]); let bRequest = usbd.brequest.read().bits() as u8; let setup_data = [ bmrequesttype, bRequest, wValue_low, wValue_high, wIndex_low, wIndex_high, wLength_low, wLength_high, ]; match Request::parse(&setup_data) { Ok(request) => { // Process the parsed request match request { Request::GetDescriptorDevice { wLength } => { println!("GET_DESCRIPTOR Device [length={}] ", wLength); // TODO: Further handle GET_DESCRIPTOR Device request } Request::SetAddress(addr) => { // Handle SetAddress request (e.g., do nothing on Mac) println!("Received SetAddress: {}", addr); } _ => { // Handle other request types or unsupported requests } } } Err(ParseError::InvalidLength) => { eprintln!("Error: Invalid SETUP data length."); } Err(ParseError::UnsupportedRequest) => { eprintln!("Error: Unsupported USB request."); } } } // Handle other UsbEvent variants _ => {} } } // Dummy definitions for compilation #[derive(Debug)] enum UsbEvent { UsbReset, UsbEp0Setup } #[derive(Debug)] enum Request { SetAddress(u16), GetDescriptorDevice { wLength: u16 }, } #[derive(Debug)] enum ParseError { InvalidLength, UnsupportedRequest } impl Request { fn parse(data: &[u8]) -> Result { // Implementation from the previous snippet would go here if data.len() < 8 { return Err(ParseError::InvalidLength); } let bmRequestType = data[0]; let bRequest = data[1]; let wValue = u16::from_le_bytes([data[2], data[3]]); let wIndex = u16::from_le_bytes([data[4], data[5]]); let wLength = u16::from_le_bytes([data[6], data[7]]); if bmRequestType == 0 && bRequest == 5 { return Ok(Request::SetAddress(wValue)); } if bmRequestType == 0b10000000 && bRequest == 6 { let descriptor_type = (wValue >> 8) as u8; let descriptor_index = (wValue & 0xFF) as u8; if descriptor_type == 1 && descriptor_index == 0 && wIndex == 0 { return Ok(Request::GetDescriptorDevice { wLength }); } } Err(ParseError::UnsupportedRequest) } } ``` -------------------------------- ### Configure Build Target in Cargo Configuration Source: https://github.com/ferrous-systems/embedded-trainings-2020/blob/main/embedded-workshop-book/src/from-scratch.md Sets the default compilation target for the Cargo build system. This file specifies which architecture and ABI the project should be compiled for, essential for cross-compilation to embedded devices. ```toml [build] target = "thumbv7em-none-eabi" ``` -------------------------------- ### Parse USB SETUP Packet Fields from USBD Registers (Rust) Source: https://context7.com/ferrous-systems/embedded-trainings-2020/llms.txt This snippet demonstrates parsing USB SETUP packet fields from USBD registers using the 'usbd' module and the 'usb' crate's Request::parse() function. It handles different USB requests like GetDescriptor and SetAddress, and includes error handling for unsupported requests. Dependencies include 'rtic', 'dk', and 'usb'. ```rust #![no_main] #![no_std] use firmware as _; #[rtic::app(device = dk, peripherals = false)] mod app { use dk::{ peripheral::USBD, usbd::{self, Ep0In, Event}, }; use usb::{Descriptor, Request}; #[local] struct MyLocalResources { usbd: USBD, ep0in: Ep0In, } #[shared] struct MySharedResources {} #[init] fn init(_cx: init::Context) -> (MySharedResources, MyLocalResources, init::Monotonics) { let board = dk::init().unwrap(); usbd::init(board.power, &board.usbd); ( MySharedResources {}, MyLocalResources { usbd: board.usbd, ep0in: board.ep0in, }, init::Monotonics(), ) } #[task(binds = USBD, local = [usbd, ep0in])] fn main(cx: main::Context) { let usbd = cx.local.usbd; let ep0in = cx.local.ep0in; while let Some(event) = usbd::next_event(usbd) { match event { Event::UsbEp0Setup => { // Read SETUP packet fields from USBD registers let bmrequesttype = usbd::bmrequesttype(usbd); let brequest = usbd::brequest(usbd); let wlength = usbd::wlength(usbd); let windex = usbd::windex(usbd); let wvalue = usbd::wvalue(usbd); defmt::println!( "SETUP: bmrequesttype={}, brequest={}, wvalue={}, windex={}, wlength={}", bmrequesttype, brequest, wvalue, windex, wlength ); // Parse into typed Request enum match Request::parse(bmrequesttype, brequest, wvalue, windex, wlength) { Ok(Request::GetDescriptor { descriptor, length }) if descriptor == Descriptor::Device => { defmt::println!("GET_DESCRIPTOR Device [length={}]", length); // Send device descriptor response via ep0in.start() } Ok(Request::SetAddress { address }) => { defmt::println!("SET_ADDRESS {:?}", address); // Handle address assignment } Err(_) => { defmt::error!("Unknown or unsupported request"); usbd::ep0stall(usbd); // Stall endpoint } _ => {} } } _ => {} } } } } ``` -------------------------------- ### RTIC Initialization and Idle Function Execution (Rust) Source: https://github.com/ferrous-systems/embedded-trainings-2020/blob/main/embedded-workshop-book/src/rtic-hello.md This snippet shows the generated Rust code for the `main` function in an RTIC application. It highlights how interrupts are disabled during the `init` phase and re-enabled before the `idle` function executes, demonstrating RTIC's real-time concurrency model. ```rust unsafe extern "C" fn main() -> ! { rtic::export::interrupt::disable(); let mut core: rtic::export::Peripherals = rtic::export::Peripherals::steal().into(); #[inline(never)] fn __rtic_init_resources(f: F) where F: FnOnce(), { f(); } __rtic_init_resources(|| { let (shared_resources, local_resources, mut monotonics) = init(init::Context::new(core.into())); rtic::export::interrupt::enable(); }); idle(idle::Context::new(&rtic::export::Priority::new(0))) } ``` -------------------------------- ### Open Project Folder in VS Code Source: https://github.com/ferrous-systems/embedded-trainings-2020/blob/main/embedded-workshop-book/src/parts-embedded-program.md This command opens the specified project folder in Visual Studio Code. It is a convenient way to start working on a project directly from the terminal. ```shell code beginner/apps ``` -------------------------------- ### Configure udev Rules for USB Access (Linux) Source: https://github.com/ferrous-systems/embedded-trainings-2020/blob/main/embedded-workshop-book/src/installation.md Sets up udev rules to allow non-root user access to specific USB devices, including the nRF52840 Dongle and Development Kit. This involves creating a configuration file and reloading the udev rules. ```console $ cat /etc/udev/rules.d/50-oxidize-global.rules # udev rules to allow access to USB devices as a non-root user # nRF52840 Dongle in bootloader mode ATTRS{idVendor}=="1915", ATTRS{idProduct}=="521f", TAG+="uaccess" # nRF52840 Dongle applications ATTRS{idVendor}=="2020", TAG+="uaccess" # nRF52840 Development Kit ATTRS{idVendor}=="1366", ENV{ID_MM_DEVICE_IGNORE}=="1", TAG+="uaccess" ``` ```console $ sudo udevadm control --reload-rules ``` -------------------------------- ### RTIC Application Framework Example in Rust Source: https://context7.com/ferrous-systems/embedded-trainings-2020/llms.txt Illustrates the Real-Time Interrupt-driven Concurrency (RTIC) framework for embedded applications. It shows the structure for defining local and shared resources, initialization tasks, and an idle task. Relies on the `rtic` crate and `dk` device crate. ```rust #![no_main] #![no_std] use firmware as _; // imports panic handler and logger #[rtic::app(device = dk, peripherals = false)] mod app { use cortex_m::asm; // Task-local resources (not shared between tasks) #[local] struct MyLocalResources {} // Shared resources (can be accessed by multiple tasks) #[shared] struct MySharedResources {} // Initialization runs once at startup before interrupts are enabled #[init] fn init(_cx: init::Context) -> (MySharedResources, MyLocalResources, init::Monotonics) { dk::init().unwrap(); defmt::println!("Hello from init!"); ( MySharedResources {}, MyLocalResources {}, init::Monotonics(), ) } // Idle task runs when no other tasks are executing #[idle] fn idle(_cx: idle::Context) -> ! { defmt::println!("Entering idle task"); loop { asm::bkpt(); // Wait for debugger or interrupt } } } ``` -------------------------------- ### Track Uptime on nRF52840 using RTC0 in Rust Source: https://context7.com/ferrous-systems/embedded-trainings-2020/llms.txt Provides a function `dk::uptime()` that returns a `Duration` representing the time elapsed since board initialization. This is achieved using the RTC0 peripheral with 30 microsecond resolution. The example logs the start time and elapsed time during iterations. ```rust #![no_main] #![no_std] use core::time::Duration; use cortex_m_rt::entry; use apps as _; #[entry] fn main() -> ! { dk::init().unwrap(); // Get elapsed time since dk::init() was called let start = dk::uptime(); defmt::println!("Program started at {:?}", start); // Perform some work... for i in 0..5 { defmt::println!("Iteration {} at {:?}", i, dk::uptime()); } let elapsed = dk::uptime(); defmt::println!("Total runtime: {:?}", elapsed); dk::exit() } ``` -------------------------------- ### Open `dk` Crate Documentation Source: https://github.com/ferrous-systems/embedded-trainings-2020/blob/main/embedded-workshop-book/src/using-hal.md Opens the documentation for the `dk` crate in the default web browser. This command is useful for exploring the API and understanding the available abstractions for the development kit's hardware. ```bash $ cargo doc -p dk --open ``` -------------------------------- ### Rust: Creating Byte Arrays for Messages Source: https://github.com/ferrous-systems/embedded-trainings-2020/blob/main/embedded-workshop-book/src/messages.md Demonstrates three equivalent ways to create a byte array representing the string 'Hello' in Rust. These methods are useful for sending messages where byte-level representation is required. ```rust let msg: &[u8; 5] = &[72, 101, 108, 108, 111]; let msg: &[u8; 5] = &[b'H', b'e', b'l', b'l', b'o']; let msg: &[u8; 5] = b"Hello"; ``` -------------------------------- ### Run `led` Program with Trace Logging Source: https://github.com/ferrous-systems/embedded-trainings-2020/blob/main/embedded-workshop-book/src/using-hal.md Runs the `led` program with `DEFMT_LOG` environment variable set to `trace`. This enables detailed logging from the `dk` Hardware Abstraction Layer, providing insights into the configuration of I/O pins and LED behavior. ```bash $ DEFMT_LOG=trace cargo run --bin led ``` -------------------------------- ### Add Rust Cross-Compilation Target Source: https://github.com/ferrous-systems/embedded-trainings-2020/blob/main/embedded-workshop-book/src/installation.md Adds the `thumbv7em-none-eabihf` target to the Rust toolchain, enabling cross-compilation for ARM Cortex-M microcontrollers. This is essential for embedded development. ```console $ rustup +stable target add thumbv7em-none-eabihf ``` -------------------------------- ### Configure Development Kit Radio Channel in Rust Source: https://github.com/ferrous-systems/embedded-trainings-2020/blob/main/embedded-workshop-book/src/troubleshoot-usb-dongle.md This Rust code snippet demonstrates how to set the radio channel on a Development Kit. It is crucial to match this channel with the one selected for the `loopback-nousb*` program flashed on the Dongle to ensure successful communication. ```rust /* make sure to pass the channel number of the loopback-nousb* program you picked */ radio.set_channel(Channel::_21); ``` -------------------------------- ### Implement EP0SETUP Function with Early Error Return in Rust Source: https://github.com/ferrous-systems/embedded-trainings-2020/blob/main/embedded-workshop-book/src/error-handling.md This Rust function `ep0setup` parses a USB request and returns a `Result`. It uses the `?` operator for early return if parsing fails. The function is designed to return `Err` for invalid requests, allowing for immediate handling. ```rust fn ep0setup(/* parameters */) -> Result<(), ()> { let req = Request::parse(/* arguments */)?; // ^ early returns an `Err` if it occurs // TODO respond to the `req`; return `Err` if the request was invalid in this state Ok(()) } ``` -------------------------------- ### Define Memory Layout for Embedded Target Source: https://github.com/ferrous-systems/embedded-trainings-2020/blob/main/embedded-workshop-book/src/from-scratch.md Specifies the memory regions (FLASH and RAM) for the target microcontroller. This linker script configuration is vital for the compiler to correctly place code and data in the device's memory. ```linker_script MEMORY { /* NOTE 1 K = 1 KiBi = 1024 bytes */ FLASH : ORIGIN = 0x00000000, LENGTH = 1M RAM : ORIGIN = 0x20000000, LENGTH = 256K } ``` -------------------------------- ### Rust Array Out-of-Bounds Panic Example Source: https://github.com/ferrous-systems/embedded-trainings-2020/blob/main/embedded-workshop-book/src/panicking.md This Rust code demonstrates an array indexing operation that goes beyond the array's bounds, resulting in a panic. The output shows the specific error message and the call stack leading to the panic. ```rust fn main() { let arr = [1, 2, 3]; // This will panic because index 3 is out of bounds for an array of length 3 let _ = arr[3]; } ``` -------------------------------- ### Initialize Board Peripherals with `dk` Crate Source: https://github.com/ferrous-systems/embedded-trainings-2020/blob/main/embedded-workshop-book/src/using-hal.md Initializes the development kit board and its peripherals using the `dk` crate. This function provides access to the board's hardware features, such as LEDs, through a simplified API. It returns a `Board` structure containing handles to the initialized peripherals. ```rust let board = dk::init().unwrap(); ``` -------------------------------- ### Prevent log buffer overflow with delays in Rust Source: https://github.com/ferrous-systems/embedded-trainings-2020/blob/main/embedded-workshop-book/src/radio-puzzle-help.md Illustrates a method to prevent log buffer overflow in embedded systems by introducing a delay between sending data and logging. This ensures that logs can be processed before the buffer is overwritten, preventing data loss. The example uses `core::time::Duration` for the delay. ```rust use core::time::Duration; #[entry] fn main() -> ! { let mut timer = board.timer; for plainletter in 0..=127 { /* ... send letter to dongle ... */ defmt::println!("got response"); /* ... store output ... */ timer.wait(Duration::from_millis(20)); } } ``` -------------------------------- ### Handle EP0SETUP Event with Error Checking in Rust Source: https://github.com/ferrous-systems/embedded-trainings-2020/blob/main/embedded-workshop-book/src/error-handling.md This Rust code snippet shows how to handle the `UsbEp0Setup` event. It calls a separate function `ep0setup` which returns a `Result`. If `ep0setup` returns an error, a warning is logged, indicating an unsupported or invalid request. ```rust fn on_event(/* parameters */) { match event { /* ... */ Event::UsbEp0Setup => { if ep0setup(/* arguments */).is_err() { // unsupported or invalid request: // TODO add code to stall the endpoint defmt::warn!("EP0IN: unexpected request; stalling the endpoint"); } } } } ``` -------------------------------- ### Flash Rust Program to Microcontroller using cargo-flash Source: https://github.com/ferrous-systems/embedded-trainings-2020/blob/main/embedded-workshop-book/src/tooltips.md Shows how to use `cargo-flash` to build and flash a Rust program onto a specified microcontroller. It also covers flashing pre-built ELF files, allowing for distribution of firmware binaries. ```console # flash the `hello` program on an nRF52840 microcontroller $ cargo flash --chip nRF52840_xxAA --bin hello Flashing target/thumbv7em-none-eabihf/debug/blinky Erasing sectors ✔ [00:00:00] [####] 20.00KB/ 20.00KB @ 21.24KB/s (eta 0s ) Programming pages ✔ [00:00:01] [####] 20.00KB/ 20.00KB @ 6.38KB/s (eta 0s ) Finished in 1.995s # you $ cargo build --bin app --release $ # distribute target/thumb*/release/app to your users $ # your users $ cargo flash --chip nRF52840_xxAA --elf ./app ```