diff --git a/down-the-stack/apps/src/bin/button.rs b/down-the-stack/apps/src/bin/button.rs index 7b6f2d3..3195c8d 100644 --- a/down-the-stack/apps/src/bin/button.rs +++ b/down-the-stack/apps/src/bin/button.rs @@ -1,12 +1,14 @@ #![no_main] #![no_std] -use cortex_m::asm; + use cortex_m_rt::entry; -use core::fmt::Write; + // this imports `down-the-stack/apps/lib.rs` to retrieve our global logger + panicking-behavior use apps as _; +// ⚠️ ⚠️ ⚠️ Don't change this file! ⚠️ ⚠️ ⚠️ + #[entry] fn main() -> ! { // to enable more verbose logs, go to your `Cargo.toml` and set defmt logging levels diff --git a/down-the-stack/apps/src/bin/uarte_print.rs b/down-the-stack/apps/src/bin/uarte_print.rs index a2aebfc..2df9c96 100644 --- a/down-the-stack/apps/src/bin/uarte_print.rs +++ b/down-the-stack/apps/src/bin/uarte_print.rs @@ -4,26 +4,25 @@ use cortex_m::asm; use cortex_m_rt::entry; use core::fmt::Write; + // this imports `down-the-stack/apps/lib.rs` to retrieve our global logger + panicking-behavior use apps as _; +// ⚠️ ⚠️ ⚠️ Don't change this file! ⚠️ ⚠️ ⚠️ + #[entry] fn main() -> ! { // to enable more verbose logs, go to your `Cargo.toml` and set defmt logging levels // to `defmt-trace` by changing the `default = []` entry in `[features]` let board = dk_bsc::init().unwrap(); - - let button_1 = board.buttons.b_1; let mut uarte = board.uarte; - - - let tx_buffer = "Hello\n"; + + let tx_buffer = "Hello, World!\n"; + uarte.write_str(tx_buffer).unwrap(); // this program does not `exit`; use Ctrl+C to terminate it loop { - if button_1.is_pushed() { - uarte.write_str(tx_buffer).unwrap(); - } + asm::nop(); } } diff --git a/down-the-stack/apps/src/lib.rs b/down-the-stack/apps/src/lib.rs index 3446a8d..911961c 100644 --- a/down-the-stack/apps/src/lib.rs +++ b/down-the-stack/apps/src/lib.rs @@ -1,5 +1,5 @@ #![no_std] - +// ⚠️ ⚠️ ⚠️ Don't change this file! ⚠️ ⚠️ ⚠️ use panic_probe as _; // same panicking *behavior* as `panic-probe` but doesn't print a panic message diff --git a/down-the-stack/dk_bsc/src/lib_solution.rs b/down-the-stack/dk_bsc/src/lib_solution.rs index 267f02b..9fd2940 100644 --- a/down-the-stack/dk_bsc/src/lib_solution.rs +++ b/down-the-stack/dk_bsc/src/lib_solution.rs @@ -13,14 +13,14 @@ use core::{ use cortex_m::asm; use embedded_hal::digital::v2::{OutputPin as _, StatefulOutputPin}; use nrf52840_hal as hal; -pub use hal::pac::{ - UARTE1, uarte0::{ - baudrate::BAUDRATE_A as Baudrate, config::PARITY_A as Parity}}; +pub use hal::pac::uarte0::{ + baudrate::BAUDRATE_A as Baudrate, config::PARITY_A as Parity}; use hal::{ - gpio::{p0, Level, Output, Input, PullUp, Pin, Port, PushPull}, - timer::OneShot, prelude::InputPin, +gpio::{p0, Level, Output, Input, PullUp, Pin, Port, PushPull}, +timer::OneShot, prelude::InputPin, +uarte, }; use defmt; @@ -30,16 +30,16 @@ use defmt_rtt as _; // global logger pub struct Board { /// LEDs pub leds: Leds, - // --- Exercise --- 🔽 + // 🔽 --- Button Exercise --- 🔽 /// Buttons pub buttons: Buttons, - // --- Exercise --- 🔼 + // 🔼 --- Button Exercise --- 🔼 /// Timer pub timer: Timer, - // --- Exercise --- 🔽 + // 🔽 --- UARTE Exercise --- 🔽 /// uarte interface pub uarte: Uarte, - // --- Exercise --- 🔼 + // 🔼 --- UARTE Exercise --- 🔼 } /// All LEDs on the board @@ -103,16 +103,16 @@ impl Led { } } } -// --- Exercise --- 🔽 +// 🔽 --- Button Exercise --- 🔽 /// All buttons on the board pub struct Buttons { - /// BUTTON1: pin P0.11, green LED + /// BUTTON1: pin P0.11 pub b_1: Button, - /// BUTTON2: pin P0.12, green LED + /// BUTTON2: pin P0.12 pub b_2: Button, - /// BUTTON3: pin P0.24, green LED + /// BUTTON3: pin P0.24 pub b_3: Button, - /// BUTTON4: pin P0.25, green LED + /// BUTTON4: pin P0.25 pub b_4: Button, } @@ -127,7 +127,7 @@ impl Button { self.inner.is_low() == Ok(true) } } -// --- Exercise --- 🔼 +// 🔼 --- Button Exercise --- 🔼 /// A timer for creating blocking delays pub struct Timer { @@ -184,9 +184,10 @@ impl ops::DerefMut for Timer { } } +// 🔽 --- UARTE Exercise --- 🔽 /// Uarte peripheral pub struct Uarte { - inner: hal::Uarte, + inner: hal::Uarte, } impl fmt::Write for Uarte { @@ -203,6 +204,7 @@ impl fmt::Write for Uarte { Ok(()) } } +// 🔼 --- UARTE Exercise --- 🔼 /// Initializes the board /// @@ -218,19 +220,19 @@ pub fn init() -> Result { let led_3 = pins.p0_15.degrade().into_push_pull_output(Level::High); let led_4 = pins.p0_16.degrade().into_push_pull_output(Level::High); - // --- Exercise --- 🔽 + // 🔽 --- Button Exercise --- 🔽 // Buttons let b_1 = pins.p0_11.degrade().into_pullup_input(); let b_2 = pins.p0_12.degrade().into_pullup_input(); let b_3 = pins.p0_24.degrade().into_pullup_input(); let b_4 = pins.p0_25.degrade().into_pullup_input(); - // --- Exercise --- 🔼 + // 🔼 --- Button Exercise --- 🔼 defmt::debug!("I/O pins have been configured for digital output"); let timer = hal::Timer::new(periph.TIMER0); - // --- Exercise --- 🔽 + // 🔽 --- UARTE Exercise --- 🔽 // Uarte let pins = hal::uarte::Pins { rxd: pins.p0_08.degrade().into_floating_input(), @@ -240,8 +242,8 @@ pub fn init() -> Result { }; - let uarte = hal::uarte::Uarte::new(periph.UARTE0, pins, Parity::INCLUDED, Baudrate::BAUD115200); - // --- Exercise --- 🔼 + let uarte = hal::uarte::Uarte::new(periph.UARTE1, pins, Parity::INCLUDED, Baudrate::BAUD115200); + // 🔼 --- UARTE Exercise --- 🔼 Ok(Board { leds: Leds { @@ -251,20 +253,20 @@ pub fn init() -> Result { led_4: Led { inner: led_4 }, }, - // --- Exercise --- 🔽 + // 🔽 --- Button Exercise --- 🔽 buttons: Buttons { b_1: Button { inner: b_1}, b_2: Button { inner: b_2}, b_3: Button { inner: b_3}, b_4: Button { inner: b_4}, }, - // --- Exercise --- 🔼 + // 🔼 --- Button Exercise --- 🔼 timer: Timer { inner: timer }, - // --- Exercise --- 🔽 + // 🔽 --- UARTE Exercise --- 🔽 uarte: Uarte { inner: uarte }, - // --- Exercise --- 🔼 + // 🔼 --- UARTE Exercise --- 🔼 }) } else { Err(()) diff --git a/embedded-workshop-book/src/SUMMARY.md b/embedded-workshop-book/src/SUMMARY.md index 696a806..99701c3 100644 --- a/embedded-workshop-book/src/SUMMARY.md +++ b/embedded-workshop-book/src/SUMMARY.md @@ -26,6 +26,8 @@ - [Starting a Project from Scratch](./from-scratch.md) - [Down the Stack Workbook](./down-the-stack.md) - [BSC Exercise](./bsc-exercise.md) + - [Button Implementation](./button-implementation.md) + - [UARTE Implementation](./uarte-implementation.md) - [Advanced Workbook](./advanced-workbook.md) - [Code Organization](./code-organisation.md) - [Listing USB Devices](./listing-usb-devices.md) diff --git a/embedded-workshop-book/src/bsc-exercise.md b/embedded-workshop-book/src/bsc-exercise.md index 6aa4dbe..43957d2 100644 --- a/embedded-workshop-book/src/bsc-exercise.md +++ b/embedded-workshop-book/src/bsc-exercise.md @@ -1,37 +1,69 @@ # BSC Exercise -In this exercise you will learn how to write a board support crate. -The bsc template will already contain the led and timer implementation. -The radio and USB/Power implementations will be deleted, because that just takes up unnecessary space and adds to confusion. +In this exercise you will learn how to write a *Board Support Crate* (or BSC, also known as a *Board Support Package*) by implementing support for handling button presses, and support for using the UARTE peripheral. + +The template `down-the-stack/dk_bsc/src/lib.rs` already contains the LED and Timer implementations. Add your code to the designated lines. You'll find a `//todo!` there. + +You can test after each step by running the following command out of `down-the-stack/apps` +``` +cargo run --bin hello +``` +This program will not call any of the functions you are implementing, so it does not matter if they are incomplete. It will refuse to build if there are errors present in the `lib.rs`! + +`down-the-stack/dk_bsc/src/lib_solution.rs` contains the full solution code. -## Learning goals -* implement buttons functionality -* uarte implementation -* impl blocks, associated functions, methods -* generate docs! -## Steps -### Write a button implementation -* add field in the board struct -* add struct for all buttons -* add struct for the single button +## You will learn how to +* modify the `init()` function that brings up the board's peripherals +* how to configure pins +* how to write a function that checks the state of a pin +* implement functionality on a type +* implement a Trait +* to document and generate docs for your own library! -* Read docs, section 8.7 for info about pins and pin configuration -* add button bring up to board init +## Prerequisites +* `impl` keyword +* methods and associated functions +* `pub` keyword +* usage of structs to represent registers +* Trait + +## Tasks +### Write a button implementation. This entails the following steps: +* Add `struct Buttons` with 4 fields, that represents each of the four buttons. +* Add `struct Button` that is a wrapper for the pin that a single button is connected to. +* Write a method `is_pushed` that checks if a single button is pushed. +* Initialize the pins in `fn init()`. +* Add the `struct Button` to the definition and instantiation of `struct Board`. +* Run `apps/buttons.rs` to test. +* Run `cargo doc` out of the apps folder to find all your doc comments! +### Write a UARTE implementation. This entails the following steps: +* Check the `uarte` module of the `nrf-hal` for requirements of the instantiating method. +* Add `struct Uarte` that serves as wrapper for the `UARTE1` instance. +* Initialize the UARTE1 peripheral in `fn init()` using the following settings: + * parity: included + * baudrate: 115200 baud +* Add `struct Uarte` to the definition and instantiation of `struct Board`. +* Implement the `fmt::Write` trait for `struct Uarte`. +* Connect your computer to the virtual UART port with `screen`. +* Run `apps/uarte_print.rs` to test. +## Knowledge + +### Comments +The `lib.rs` has an attribute `#![deny(missing_docs)]`. This means, that missing doc comments for structs are returned as compiler errors, to remind you to document your work properly. + +```rust +/// This is a doc comment +// This is a normal comment +``` +### Structs represent Registers + +[todo!] insert refresher from rust fundamentals +## Hardware documentation for pin configuration + +Go to [Nordic Infocenter](https://infocenter.nordicsemi.com/topic/ug_nrf52840_dk/UG/dk/intro.html) to download the User Guide. You can find all the information that is relevant to this exercise in there. -* add doc lines every where! -* add methods in impl block: - * detect button push - * debounce button function? like in knurling session, requires implementation of a second timer, just for this? -### Write Uarte implementation -* add field to the board struct -* add struct for the instance, how to figure out what the type of the inner field is -* create instance in init, add baudrate, parity etc. -* add to instantiation of board struct -* impl fmt::Write for the Uarte struct, simple write does not work because of dma -* example code with button is not a good idea for the simple button implementation. -I think this is plenty for an hour. \ No newline at end of file diff --git a/embedded-workshop-book/src/button-implementation.md b/embedded-workshop-book/src/button-implementation.md new file mode 100644 index 0000000..8c0118b --- /dev/null +++ b/embedded-workshop-book/src/button-implementation.md @@ -0,0 +1,146 @@ +# Write the Button Implementation +## Step-by-Step Solution + +### Step 1: Read the docs! + +✅ Read the [User Guide section 8.7](https://infocenter.nordicsemi.com/topic/ug_nrf52840_dk/UG/dk/hw_buttons_leds.html?cp=5_0_4_7_6) for info about pins and pin configuration related to the buttons. Note down the pins that the buttons are connected to. + +The pins need to be configured as input pins with an internal pull-up. The pins as well as the configurations are defined as types in the `nrf-hal` in the `gpio` peripheral. Add the following imports: `Input` and `PullUp`. + +### Step 2: Add the structs that represent the buttons as a group and a generic single button. + +✅ Add the struct that represents the single button. It has only one field, `inner`. The type of this button is the pin configuration: `Pin>` + +✅ Add the `struct` that represents the group of buttons has four fields, one for each button. The field name contains the number that corresponds to the button numeration on the board. The type of each field is the struct that represents the generic single button. + +✅ Add doc comments for every struct and field! + +Building this code should return a warning: field `inner` is never read. + +
+ Solution + +```rust +/// All buttons on the board +pub struct Buttons { + /// BUTTON1: pin P0.11 + pub b_1: Button, + /// BUTTON2: pin P0.12 + pub b_2: Button, + /// BUTTON3: pin P0.24 + pub b_3: Button, + /// BUTTON4: pin P0.25 + pub b_4: Button, +} + +/// A single button +pub struct Button { + inner: Pin>, +} +``` +
+ +### Step 3: Implement the button function. + +✅ Add an `impl` block for the `struct Button`. Add a method `is_pushed` that takes in the struct as `&self` and returns a bool, if the button is pushed. + +✅ Now remember, the pins the buttons are connected to are configured as active low. For buttons this means, that the pin is pulled low, when the button is pushed. + +✅ In the `nrf-hal` you can find a method to check if a single pin is low. To use it, you have to add the following line to your `nrf52840_hal` imports: `prelude::InputPin`. + + +
+ Solution + +```rust +impl Button { + /// returns true if button is pushed + pub fn is_pushed(&self) -> bool { + self.inner.is_low() == Ok(true) + } +} +``` +
+ +### Step 4: Bring up the pins! + +✅ Go to `pub fn init()`, the function that initializes the board's peripherals. + +✅ Configure each pin as degraded, pull-up input pin and bind it to a variable that makes it clear what button number it is connected to. + +Building this code brings up warnings about unused variables. + +
+ Solution + +```rust +// Buttons +let b_1 = pins.p0_11.degrade().into_pullup_input(); +let b_2 = pins.p0_12.degrade().into_pullup_input(); +let b_3 = pins.p0_24.degrade().into_pullup_input(); +let b_4 = pins.p0_25.degrade().into_pullup_input(); +``` +
+ +### Step 5: Add everything to the board struct. + +✅ In the definition of the `struct Board` add a field for the `struct Buttons`. + +✅ In the pub `fn init()` function, where `Board` is instantiated, add the button field, assigning the pins you defined earlier to the respective buttons. + +
+ Solution + +```rust +/// Components on the board +pub struct Board { + /// LEDs + pub leds: Leds, + /// Buttons + pub buttons: Buttons, + /// Timer + pub timer: Timer, +} + +// ... + +pub fn init() -> Result { + // ... + Ok(Board { + leds: Leds { + // ... + }, + buttons: Buttons { + b_1: Button { inner: b_1}, + b_2: Button { inner: b_2}, + b_3: Button { inner: b_3}, + b_4: Button { inner: b_4}, + }, + timer: Timer { inner: timer }, + }) + } else { + Err(()) + } + +} + +``` +
+ +### Step 6: Run the example! + +✅ Go to `/down-the-stack/apps`. + +✅ Run the following command: + +```shell +cargo run --bin button +``` + +### Step 7: Generate the docs! + +✅ Out of the apps folder run the following command to build the docs for this crate and to view your written documentation! + +```shell +cargo doc +``` \ No newline at end of file diff --git a/embedded-workshop-book/src/down-the-stack.md b/embedded-workshop-book/src/down-the-stack.md index e69de29..88284e9 100644 --- a/embedded-workshop-book/src/down-the-stack.md +++ b/embedded-workshop-book/src/down-the-stack.md @@ -0,0 +1,10 @@ +# Down the Stack + +In this Session you will learn more about Rust's split crate model that includes +* the Board Support Crate +* an implementation of the Hardware Abstraction Layer +* the Peripheral Access Crate + +This session consists of lectures and two blocks of exercises: +* BSC Exercise: Implementing further features in a basic BSC +* PAC Exercise: Generating a PAC from an SVD file and writing into registers using the the generated PAC. \ No newline at end of file diff --git a/embedded-workshop-book/src/uarte-implementation.md b/embedded-workshop-book/src/uarte-implementation.md new file mode 100644 index 0000000..b1f3dc7 --- /dev/null +++ b/embedded-workshop-book/src/uarte-implementation.md @@ -0,0 +1,192 @@ +# Write the Uarte implementation +## Step-by-Step Solution + +### Step 1: Check Documentation. + +The UART protocol requires four pins, they are usually labelled: +* RXD +* TXD +* CTS +* RTS + +✅ Check the [User Guide in section 7.2](https://infocenter.nordicsemi.com/topic/ug_nrf52840_dk/UG/dk/vir_com_port.html) to find to find out which pins are reserved for these and what their configuration needs to be. + +### Step 2: Explore the `nrf-hal` to find out what needs to be done. + + +The `nrf52840-hal` is a crate that exports all the `52840` flagged features from the `nrf-hal-common`. Let's take a look at the `nrf52840-hal`'s [Uarte module](https://github.com/nrf-rs/nrf-hal/blob/v0.14.1/nrf-hal-common/src/uarte.rs). + +In line 16 we see, that the nRF52840 uses the `hal::pac::UARTE1` peripheral. +In line 44 you find the `struct Uarte(T)`, the interface to a UARTE instance `T`. Besides the instance `T`, the instantiating method takes variables of the following types as arguments: `Pins`, `Parity` and `Baudrate`. + +A quick search of the document reveals where to find all of them: +* `Pins`: Line 463 +* `Parity` and `Baudrate`: Re-export on line 34 + +✅ Add the following lines as import: +``` +use hal::pac::uarte0::{ + baudrate::BAUDRATE_A as Baudrate, config::PARITY_A as Parity}; +use hal::uarte; +``` + +### Step 3: Add `struct Uarte` + +✅ Add `struct Uarte` that serves as a wrapper for underlying HAL UART driver. + +The struct has one field labelled `inner` which is of type `hal::Uarte` but with the type parameter `T` being set to `hal::pac::UARTE1`. + +The main difference between using our `Uarte` object and the underlying HAL `Uarte` object is that *ours* will be pre-configured for the correct pins and baud rate, according to the layout of our nRF52840-DK board and the Virtual COM Port interface chip on the other end of the UART link. + +
+ Solution + +```rust +pub struct Uarte { + inner: hal::Uarte, +} +``` +
+ +### Step 4: Bring up the peripheral in the `fn init()` + +✅ Take a closer look at the definition of the `uarte::Pins` struct in the `nrf-hal`. Compare the pin type configurations with the ones you have already imported in `lib.rs`. Add the ones you're missing. + +✅ Create an instance of this struct in `fn init()` with the appropriate pins and configurations. Set the output pin's level to `Level::High`. +Note, that the third and fourth pin are each wrapped in an `Option`. + +✅ Create a Uarte driver with `hal::uarte::Uarte::new(...)` and bind it to a variable called `uarte` - we will stash this in our own `Uarte` struct later. + +Creating the Uarte driver requires four arguments: +* The `UARTE1` instance can be found in the `periph` variable. +* Your instance of the `uarte::Pins` struct. +* Set parity to `Parity::INCLUDED` +* set the baud rate to `Baudrate::BAUD115200`. + +
+ Solution + +```rust + let pins = hal::uarte::Pins { + rxd: pins.p0_08.degrade().into_floating_input(), + txd: pins.p0_06.degrade().into_push_pull_output(Level::High), + cts: Some(pins.p0_07.degrade().into_floating_input()), + rts: Some(pins.p0_05.degrade().into_push_pull_output(Level::High)), + }; + + + let uarte = hal::uarte::Uarte::new(periph.UARTE1, pins, Parity::INCLUDED, Baudrate::BAUD115200); +``` +
+ +### Step 5: Board struct + +✅ Add a field for the `Uarte` struct in the `Board` struct. +add the field to the instance of the `Board` struct in `fn init()`. + +
+ Solution + +```rust + +pub struct Board { + /// LEDs + pub leds: Leds, + /// Buttons + pub buttons: Buttons, + /// Timer + pub timer: Timer, + /// uarte interface + pub uarte: Uarte, +} + +// ... + +pub fn init() -> Result { + + // ... + + Ok(Board { + leds: Leds { + // ... + }, + + buttons: Buttons { + // ... + }, + // 🔼 --- Button Exercise --- 🔼 + + timer: Timer { inner: timer }, + + uarte: Uarte { inner: uarte }, + }) + } else { + Err(()) + } +``` + +
+ +### Step 6: Implementing the `fmt::Write` trait + +We want to implement the `fmt::Write` trait so that users can call `write!` on our Uarte object + +When implementing this, we can't just write to the `Uarte` instance because a simple write of a string literal would try and read the string literal from flash memory. This does not work because the EasyDMA peripheral in the nRF52 series can only access RAM, not flash. + +Instead our implementation must ensure all the strings are copied to a stack allocated buffer and that buffer is passed to the Uarte's `write` method. + +✅ Add `use::core::fmt;` to your imports. + +✅ Create a public method `write_str`. It takes a mutable reference to self and a `&str` as argument. It returns an `fmt::Result` + +✅ Create a buffer. The type is an `array` of 16 u8, set to all 0. + +✅ To copy all data into an on-stack buffer, *iterate* over *chunks* of the string and copy them into the buffer (noting that the chunk length may be less than the requested size if you are at the end of the input string). + +
+ Solution + +```rust +impl fmt::Write for Uarte { + + fn write_str(&mut self, s: &str) -> fmt::Result { + // Copy all data into an on-stack buffer so we never try to EasyDMA from + // flash. + let mut buf: [u8; 16] = [0; 16]; + for block in s.as_bytes().chunks(16) { + buf[..block.len()].copy_from_slice(block); + self.inner.write(&buf[..block.len()]).map_err(|_| fmt::Error)?; + } + + Ok(()) + } +} +``` +
+ +### Step 7: Connect your computer to the virtual UART +[todo!] [directions for mac present, linux and windows are missing.] + +✅ Use the following command to find the address of the nRF52840-DK on your computer. + +``` +ls /dev/tty.usbmodem* +``` + +✅ Run the following command to run `screen` with the nRF52840-DK with 115200 baud. + +``` +screen
115200 +``` + +### Step 8: Run the example. + +✅ In another terminal window go into the folder `down-the-stack/apps` and use the following command. + +``` +cargo run --bin uarte_print +``` + +On your terminal window where `screen` runs, "Hello, World" should appear. + +You need to terminate `screen` manually. \ No newline at end of file