Merge pull request #196 from ferrous-systems/add_bsc_exercise_text

Add bsc exercise text
This commit is contained in:
Tanks Transfeld 2023-03-20 17:37:08 +01:00 committed by GitHub
commit 70ce745930
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 448 additions and 63 deletions

View file

@ -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

View file

@ -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();
}
}

View file

@ -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

View file

@ -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<hal::pac::UARTE0>,
inner: hal::Uarte<hal::pac::UARTE1>,
}
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<Board, ()> {
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<Board, ()> {
};
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<Board, ()> {
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(())

View file

@ -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)

View file

@ -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.

View file

@ -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<Input<PullUp>>`
✅ 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.
<details>
<summary>Solution</summary>
```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<Input<PullUp>>,
}
```
</details>
### 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`.
<details>
<summary>Solution</summary>
```rust
impl Button {
/// returns true if button is pushed
pub fn is_pushed(&self) -> bool {
self.inner.is_low() == Ok(true)
}
}
```
</details>
### 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.
<details>
<summary>Solution</summary>
```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();
```
</details>
### 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.
<details>
<summary>Solution</summary>
```rust
/// Components on the board
pub struct Board {
/// LEDs
pub leds: Leds,
/// Buttons
pub buttons: Buttons,
/// Timer
pub timer: Timer,
}
// ...
pub fn init() -> Result<Board, ()> {
// ...
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(())
}
}
```
</details>
### 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
```

View file

@ -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.

View file

@ -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>(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.
<details>
<summary>Solution</summary>
```rust
pub struct Uarte {
inner: hal::Uarte<hal::pac::UARTE1>,
}
```
</details>
### 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`.
<details>
<summary>Solution</summary>
```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);
```
</details>
### 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()`.
<details>
<summary>Solution</summary>
```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<Board, ()> {
// ...
Ok(Board {
leds: Leds {
// ...
},
buttons: Buttons {
// ...
},
// 🔼 --- Button Exercise --- 🔼
timer: Timer { inner: timer },
uarte: Uarte { inner: uarte },
})
} else {
Err(())
}
```
</details>
### 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).
<details>
<summary>Solution</summary>
```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(())
}
}
```
</details>
### 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 <address of mc> 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.