mirror of
https://github.com/ferrous-systems/embedded-trainings-2020.git
synced 2024-05-05 10:09:08 +00:00
Merge 9ec556be39
into f24b6aa2fb
This commit is contained in:
commit
6e96af964f
22
down-the-stack/README.md
Normal file
22
down-the-stack/README.md
Normal file
|
@ -0,0 +1,22 @@
|
|||
# Down the Stack
|
||||
|
||||
This folder contains all relevant exercise templates, demo code and solutions for the exercises in the `Down the Stack` session.
|
||||
|
||||
## `apps` package
|
||||
* `button.rs` - demo code for the BSC exercise
|
||||
* `hello.rs` - for testing as described in the BSC exercise
|
||||
* `uarte_print.rs` - demo code for the BSC exercise
|
||||
* `uarte_enable.rs` - exercise template for PAC exercise
|
||||
|
||||
## `dk_bsc` package
|
||||
* `lib.rs` - exercise template for the BSC exercise
|
||||
* `lib_solution.rs` - solution code to the BSC exercise
|
||||
|
||||
## `dk_pac` package
|
||||
* `Cargo.toml` - a `Cargo.toml` file for the to be generated PAC
|
||||
* `nrf52.svd` - an SVD file for generating the PAC
|
||||
|
||||
You will generate the remaining files during the training
|
||||
|
||||
## `solutions` package
|
||||
* `uarte_enable.rs` - contains the solution for the PAC exercise
|
19
down-the-stack/apps/.cargo/config.toml
Normal file
19
down-the-stack/apps/.cargo/config.toml
Normal file
|
@ -0,0 +1,19 @@
|
|||
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
|
||||
# (..)
|
||||
rustflags = [
|
||||
"-C", "linker=flip-link", # adds stack overflow protection
|
||||
"-C", "link-arg=-Tdefmt.x", # defmt support
|
||||
# (..)
|
||||
]
|
||||
|
||||
[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
|
49
down-the-stack/apps/Cargo.toml
Normal file
49
down-the-stack/apps/Cargo.toml
Normal file
|
@ -0,0 +1,49 @@
|
|||
[package]
|
||||
authors = ["Tanks Transfeld <tanks.transfeld@ferrous-systems.com>"]
|
||||
edition = "2018"
|
||||
license = "MIT OR Apache-2.0"
|
||||
name = "apps"
|
||||
version = "0.0.0"
|
||||
|
||||
[dependencies]
|
||||
cortex-m = {version = "0.7.6", features = ["critical-section-single-core"]}
|
||||
cortex-m-rt = "0.7.2"
|
||||
dk_bsc = { path = "../dk_bsc" }
|
||||
heapless = "0.7.16"
|
||||
panic-probe = { version = "0.3.0", features = ["print-defmt"] }
|
||||
defmt = "0.3.2"
|
||||
defmt-rtt = "0.3.2"
|
||||
|
||||
# optimize code in both profiles
|
||||
[profile.dev]
|
||||
codegen-units = 1
|
||||
debug = 2
|
||||
debug-assertions = true # !
|
||||
incremental = false
|
||||
lto = "fat"
|
||||
opt-level = 'z' # !
|
||||
overflow-checks = false
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
debug = 1
|
||||
debug-assertions = false
|
||||
incremental = false
|
||||
lto = "fat"
|
||||
opt-level = 3
|
||||
overflow-checks = false
|
||||
|
||||
[features]
|
||||
|
||||
|
||||
default = [
|
||||
"other-feature"
|
||||
]
|
||||
other-feature = []
|
||||
# do NOT modify these features
|
||||
defmt-default = []
|
||||
defmt-trace = []
|
||||
defmt-debug = []
|
||||
defmt-info = []
|
||||
defmt-warn = []
|
||||
defmt-error = []
|
31
down-the-stack/apps/src/bin/button.rs
Normal file
31
down-the-stack/apps/src/bin/button.rs
Normal file
|
@ -0,0 +1,31 @@
|
|||
#![no_main]
|
||||
#![no_std]
|
||||
|
||||
|
||||
use cortex_m_rt::entry;
|
||||
|
||||
// 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 mut led = board.leds;
|
||||
let button_1 = board.buttons.b_1;
|
||||
|
||||
loop {
|
||||
if button_1.is_pushed() {
|
||||
led.led_1.on();
|
||||
} else {
|
||||
led.led_1.off();
|
||||
}
|
||||
}
|
||||
|
||||
// this program does not `exit`; use Ctrl+C to terminate it
|
||||
}
|
28
down-the-stack/apps/src/bin/hello.rs
Normal file
28
down-the-stack/apps/src/bin/hello.rs
Normal file
|
@ -0,0 +1,28 @@
|
|||
// 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_bsc::init().unwrap();
|
||||
|
||||
defmt::println!("Hello, world!"); // :wave:
|
||||
|
||||
loop {
|
||||
// breakpoint: halts the program's execution
|
||||
asm::bkpt();
|
||||
}
|
||||
}
|
28
down-the-stack/apps/src/bin/uarte_enable.rs
Normal file
28
down-the-stack/apps/src/bin/uarte_enable.rs
Normal file
|
@ -0,0 +1,28 @@
|
|||
#![no_main]
|
||||
#![no_std]
|
||||
|
||||
use cortex_m::asm;
|
||||
use cortex_m_rt::entry;
|
||||
|
||||
// ^^^^ import the PAC here
|
||||
|
||||
// this imports `down-the-stack/apps/lib.rs` to retrieve our global logger + panicking-behavior
|
||||
use apps as _;
|
||||
use defmt;
|
||||
use defmt_rtt as _; // global logger
|
||||
|
||||
#[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]`
|
||||
|
||||
// Your code goes here...
|
||||
|
||||
|
||||
// this program does not `exit`; use Ctrl+C to terminate it
|
||||
loop {
|
||||
asm::nop();
|
||||
}
|
||||
}
|
||||
|
||||
// The helper function goes here...
|
28
down-the-stack/apps/src/bin/uarte_print.rs
Normal file
28
down-the-stack/apps/src/bin/uarte_print.rs
Normal file
|
@ -0,0 +1,28 @@
|
|||
#![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
|
||||
// to `defmt-trace` by changing the `default = []` entry in `[features]`
|
||||
|
||||
let board = dk_bsc::init().unwrap();
|
||||
let mut uarte = board.uarte;
|
||||
|
||||
let tx_buffer = "Hello, World!\n";
|
||||
uarte.write_str(tx_buffer).unwrap();
|
||||
|
||||
// this program does not `exit`; use Ctrl+C to terminate it
|
||||
loop {
|
||||
asm::nop();
|
||||
}
|
||||
}
|
10
down-the-stack/apps/src/lib.rs
Normal file
10
down-the-stack/apps/src/lib.rs
Normal file
|
@ -0,0 +1,10 @@
|
|||
#![no_std]
|
||||
// ⚠️ ⚠️ ⚠️ Don't change this file! ⚠️ ⚠️ ⚠️
|
||||
use panic_probe as _;
|
||||
|
||||
// same panicking *behavior* as `panic-probe` but doesn't print a panic message
|
||||
// this prevents the panic message being printed *twice* when `defmt::panic` is invoked
|
||||
#[defmt::panic_handler]
|
||||
fn panic() -> ! {
|
||||
cortex_m::asm::udf()
|
||||
}
|
24
down-the-stack/dk_bsc/Cargo.toml
Normal file
24
down-the-stack/dk_bsc/Cargo.toml
Normal file
|
@ -0,0 +1,24 @@
|
|||
[package]
|
||||
authors = ["Jorge Aparicio <jorge.aparicio@ferrous-systems.com>", "Tanks Transfeld <tanks.transfeld@ferrous-systems.com"]
|
||||
edition = "2018"
|
||||
license = "MIT OR Apache-2.0"
|
||||
name = "dk_bsc"
|
||||
version = "0.0.0"
|
||||
|
||||
[dependencies]
|
||||
cortex-m = {version = "0.7.6", features = ["critical-section-single-core"]}
|
||||
cortex-m-rt = "0.7.2"
|
||||
embedded-hal = "0.2.7"
|
||||
nrf52840-hal = "0.14.0"
|
||||
panic-probe = { version = "0.3.0", features = ["print-defmt"] }
|
||||
defmt = "0.3.2"
|
||||
defmt-rtt = "0.3.2"
|
||||
|
||||
[features]
|
||||
advanced = []
|
||||
beginner = []
|
||||
|
||||
default = [
|
||||
"other-feature"
|
||||
]
|
||||
other-feature = []
|
7
down-the-stack/dk_bsc/README.md
Normal file
7
down-the-stack/dk_bsc/README.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
# `dk`
|
||||
|
||||
Board Support Crate for the nRF52840 Development Kit (DK)
|
||||
|
||||
## Getting familiar with the hardware
|
||||
|
||||
TODO
|
12
down-the-stack/dk_bsc/build.rs
Normal file
12
down-the-stack/dk_bsc/build.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
use std::{env, error::Error, fs, path::PathBuf};
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let out_dir = PathBuf::from(env::var("OUT_DIR")?);
|
||||
|
||||
// put memory layout (linker script) in the linker search path
|
||||
fs::copy("memory.x", out_dir.join("memory.x"))?;
|
||||
|
||||
println!("cargo:rustc-link-search={}", out_dir.display());
|
||||
|
||||
Ok(())
|
||||
}
|
5
down-the-stack/dk_bsc/memory.x
Normal file
5
down-the-stack/dk_bsc/memory.x
Normal file
|
@ -0,0 +1,5 @@
|
|||
MEMORY
|
||||
{
|
||||
FLASH : ORIGIN = 0x00000000, LENGTH = 1024K
|
||||
RAM : ORIGIN = 0x20000000, LENGTH = 256K
|
||||
}
|
259
down-the-stack/dk_bsc/src/lib.rs
Normal file
259
down-the-stack/dk_bsc/src/lib.rs
Normal file
|
@ -0,0 +1,259 @@
|
|||
//! Board Support Crate (BSC) for the nRF52840 Development Kit
|
||||
|
||||
#![deny(missing_docs)]
|
||||
#![no_std]
|
||||
|
||||
use core::{
|
||||
ops,
|
||||
sync::atomic::{self, Ordering},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use cortex_m::asm;
|
||||
use embedded_hal::digital::v2::{OutputPin as _, StatefulOutputPin};
|
||||
|
||||
use nrf52840_hal as hal;
|
||||
use hal::{
|
||||
gpio::{p0, Level, Output, Pin, Port, PushPull},
|
||||
timer::OneShot,
|
||||
};
|
||||
|
||||
use defmt;
|
||||
use defmt_rtt as _; // global logger
|
||||
|
||||
/// Components on the boarde
|
||||
// Add structs for the peripheral you want to implement. First for the buttons, later UARTE
|
||||
pub struct Board {
|
||||
/// LEDs
|
||||
pub leds: Leds,
|
||||
|
||||
/// Timer
|
||||
pub timer: Timer,
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// All LEDs on the board
|
||||
pub struct Leds {
|
||||
/// LED1: pin P0.13, green LED
|
||||
pub led_1: Led,
|
||||
/// LED2: pin P0.14, green LED
|
||||
pub led_2: Led,
|
||||
/// LED3: pin P0.15, green LED
|
||||
pub led_3: Led,
|
||||
/// LED4: pin P0.16, green LED
|
||||
pub led_4: Led,
|
||||
}
|
||||
|
||||
/// A single LED
|
||||
pub struct Led {
|
||||
inner: Pin<Output<PushPull>>,
|
||||
}
|
||||
|
||||
impl Led {
|
||||
/// Turns on the LED
|
||||
pub fn on(&mut self) {
|
||||
defmt::trace!(
|
||||
"setting P{}.{} low (LED on)",
|
||||
port_as_char(&self.inner.port()),
|
||||
self.inner.pin()
|
||||
);
|
||||
|
||||
// NOTE this operations returns a `Result` but never returns the `Err` variant
|
||||
let _ = self.inner.set_low();
|
||||
}
|
||||
|
||||
/// Turns off the LED
|
||||
pub fn off(&mut self) {
|
||||
defmt::trace!(
|
||||
"setting P{}.{} high (LED off)",
|
||||
port_as_char(&self.inner.port()),
|
||||
self.inner.pin()
|
||||
);
|
||||
|
||||
// NOTE this operations returns a `Result` but never returns the `Err` variant
|
||||
let _ = self.inner.set_high();
|
||||
}
|
||||
|
||||
/// Returns `true` if the LED is in the OFF state
|
||||
pub fn is_off(&self) -> bool {
|
||||
self.inner.is_set_high() == Ok(true)
|
||||
}
|
||||
|
||||
/// Returns `true` if the LED is in the ON state
|
||||
pub fn is_on(&self) -> bool {
|
||||
!self.is_off()
|
||||
}
|
||||
|
||||
/// Toggles the state (on/off) of the LED
|
||||
pub fn toggle(&mut self) {
|
||||
if self.is_off() {
|
||||
self.on();
|
||||
} else {
|
||||
self.off()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// All buttons on the board
|
||||
// todo! Add a struct that represents all buttons of the board.
|
||||
// ...
|
||||
|
||||
/// A single button
|
||||
// todo! Add a struct that represents a single button.
|
||||
// ...
|
||||
|
||||
|
||||
// Add an impl block for the Button struct
|
||||
// todo! Add a method that returns true, if the button is pushed.
|
||||
// ...
|
||||
|
||||
|
||||
|
||||
/// A timer for creating blocking delays
|
||||
pub struct Timer {
|
||||
inner: hal::Timer<hal::pac::TIMER0, OneShot>,
|
||||
}
|
||||
|
||||
|
||||
impl Timer {
|
||||
/// Blocks program execution for at least the specified `duration`
|
||||
pub fn wait(&mut self, duration: Duration) {
|
||||
defmt::trace!("blocking for {:?} ...", duration);
|
||||
|
||||
// 1 cycle = 1 microsecond because the underlying HAL driver
|
||||
// always sets the timer to 1 MHz.
|
||||
const NANOS_IN_ONE_MICRO: u32 = 1_000;
|
||||
let subsec_micros = duration.subsec_nanos() / NANOS_IN_ONE_MICRO;
|
||||
if subsec_micros != 0 {
|
||||
self.inner.delay(subsec_micros);
|
||||
}
|
||||
|
||||
const MICROS_IN_ONE_SEC: u32 = 1_000_000;
|
||||
// maximum number of seconds that fit in a single `delay` call without overflowing the `u32`
|
||||
// argument
|
||||
const MAX_SECS: u32 = u32::MAX / MICROS_IN_ONE_SEC;
|
||||
let mut secs = duration.as_secs();
|
||||
while secs != 0 {
|
||||
let cycles = if secs > MAX_SECS as u64 {
|
||||
secs -= MAX_SECS as u64;
|
||||
MAX_SECS * MICROS_IN_ONE_SEC
|
||||
} else {
|
||||
let cycles = secs as u32 * MICROS_IN_ONE_SEC;
|
||||
secs = 0;
|
||||
cycles
|
||||
};
|
||||
|
||||
self.inner.delay(cycles)
|
||||
}
|
||||
|
||||
defmt::trace!("... DONE");
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Deref for Timer {
|
||||
type Target = hal::Timer<hal::pac::TIMER0, OneShot>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::DerefMut for Timer {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
/// Uarte peripheral
|
||||
// todo! Add a struct that represents the Uarte
|
||||
// ...
|
||||
|
||||
|
||||
// todo! Implement the fmt::Write Trait for Uarte
|
||||
// ...
|
||||
|
||||
/// Initializes the board
|
||||
///
|
||||
/// This return an `Err`or if called more than once
|
||||
pub fn init() -> Result<Board, ()> {
|
||||
if let Some(periph) = hal::pac::Peripherals::take() {
|
||||
|
||||
let pins = p0::Parts::new(periph.P0);
|
||||
|
||||
// NOTE LEDs turn on when the pin output level is low
|
||||
let led_1 = pins.p0_13.degrade().into_push_pull_output(Level::High);
|
||||
let led_2 = pins.p0_14.degrade().into_push_pull_output(Level::High);
|
||||
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);
|
||||
|
||||
|
||||
// Buttons
|
||||
// todo! Assign the pins of the buttons
|
||||
// ...
|
||||
|
||||
|
||||
|
||||
defmt::debug!("I/O pins have been configured for digital output");
|
||||
|
||||
let timer = hal::Timer::new(periph.TIMER0);
|
||||
|
||||
|
||||
// Uarte
|
||||
// todo! Assign the pins of the UARTE peripheral
|
||||
// ...
|
||||
|
||||
// todo! Instantiate the UARTE peripheral
|
||||
// ...
|
||||
|
||||
|
||||
|
||||
Ok(Board {
|
||||
leds: Leds {
|
||||
led_1: Led { inner: led_1 },
|
||||
led_2: Led { inner: led_2 },
|
||||
led_3: Led { inner: led_3 },
|
||||
led_4: Led { inner: led_4 },
|
||||
},
|
||||
|
||||
// todo! Create an instance of the struct that contains all the single buttons.
|
||||
// ...
|
||||
|
||||
timer: Timer { inner: timer },
|
||||
|
||||
// todo! Create an instance of the UARTE struct
|
||||
// ...
|
||||
})
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Exits the application when the program is executed through the `probe-run` Cargo runner
|
||||
pub fn exit() -> ! {
|
||||
unsafe {
|
||||
// turn off the USB D+ pull-up before pausing the device with a breakpoint
|
||||
// this disconnects the nRF device from the USB host so the USB host won't attempt further
|
||||
// USB communication (and see an unresponsive device). probe-run will also reset the nRF's
|
||||
// USBD peripheral when it sees the device in a halted state which has the same effect as
|
||||
// this line but that can take a while and the USB host may issue a power cycle of the USB
|
||||
// port / hub / root in the meantime, which can bring down the probe and break probe-run
|
||||
const USBD_USBPULLUP: *mut u32 = 0x4002_7504 as *mut u32;
|
||||
USBD_USBPULLUP.write_volatile(0)
|
||||
}
|
||||
defmt::println!("`dk::exit()` called; exiting ...");
|
||||
// force any pending memory operation to complete before the BKPT instruction that follows
|
||||
atomic::compiler_fence(Ordering::SeqCst);
|
||||
loop {
|
||||
asm::bkpt()
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
fn port_as_char(port: &Port) -> char {
|
||||
match port {
|
||||
Port::Port0 => '0',
|
||||
Port::Port1 => '1',
|
||||
}
|
||||
}
|
303
down-the-stack/dk_bsc/src/lib_solution.rs
Normal file
303
down-the-stack/dk_bsc/src/lib_solution.rs
Normal file
|
@ -0,0 +1,303 @@
|
|||
//! Board Support Crate (BSC) for the nRF52840 Development Kit
|
||||
|
||||
#![deny(missing_docs)]
|
||||
#![no_std]
|
||||
|
||||
use core::{
|
||||
ops,
|
||||
fmt,
|
||||
sync::atomic::{self, Ordering},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use cortex_m::asm;
|
||||
use embedded_hal::digital::v2::{OutputPin as _, StatefulOutputPin};
|
||||
use nrf52840_hal as hal;
|
||||
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,
|
||||
uarte,
|
||||
};
|
||||
|
||||
use defmt;
|
||||
use defmt_rtt as _; // global logger
|
||||
|
||||
/// Components on the board
|
||||
pub struct Board {
|
||||
/// LEDs
|
||||
pub leds: Leds,
|
||||
// 🔽 --- Button Exercise --- 🔽
|
||||
/// Buttons
|
||||
pub buttons: Buttons,
|
||||
// 🔼 --- Button Exercise --- 🔼
|
||||
/// Timer
|
||||
pub timer: Timer,
|
||||
// 🔽 --- UARTE Exercise --- 🔽
|
||||
/// uarte interface
|
||||
pub uarte: Uarte,
|
||||
// 🔼 --- UARTE Exercise --- 🔼
|
||||
}
|
||||
|
||||
/// All LEDs on the board
|
||||
pub struct Leds {
|
||||
/// LED1: pin P0.13, green LED
|
||||
pub led_1: Led,
|
||||
/// LED2: pin P0.14, green LED
|
||||
pub led_2: Led,
|
||||
/// LED3: pin P0.15, green LED
|
||||
pub led_3: Led,
|
||||
/// LED4: pin P0.16, green LED
|
||||
pub led_4: Led,
|
||||
}
|
||||
|
||||
/// A single LED
|
||||
pub struct Led {
|
||||
inner: Pin<Output<PushPull>>,
|
||||
}
|
||||
|
||||
impl Led {
|
||||
/// Turns on the LED
|
||||
pub fn on(&mut self) {
|
||||
defmt::trace!(
|
||||
"setting P{}.{} low (LED on)",
|
||||
port_as_char(&self.inner.port()),
|
||||
self.inner.pin()
|
||||
);
|
||||
|
||||
// NOTE this operations returns a `Result` but never returns the `Err` variant
|
||||
let _ = self.inner.set_low();
|
||||
}
|
||||
|
||||
/// Turns off the LED
|
||||
pub fn off(&mut self) {
|
||||
defmt::trace!(
|
||||
"setting P{}.{} high (LED off)",
|
||||
port_as_char(&self.inner.port()),
|
||||
self.inner.pin()
|
||||
);
|
||||
|
||||
// NOTE this operations returns a `Result` but never returns the `Err` variant
|
||||
let _ = self.inner.set_high();
|
||||
}
|
||||
|
||||
/// Returns `true` if the LED is in the OFF state
|
||||
pub fn is_off(&self) -> bool {
|
||||
self.inner.is_set_high() == Ok(true)
|
||||
}
|
||||
|
||||
/// Returns `true` if the LED is in the ON state
|
||||
pub fn is_on(&self) -> bool {
|
||||
!self.is_off()
|
||||
}
|
||||
|
||||
/// Toggles the state (on/off) of the LED
|
||||
pub fn toggle(&mut self) {
|
||||
if self.is_off() {
|
||||
self.on();
|
||||
} else {
|
||||
self.off()
|
||||
}
|
||||
}
|
||||
}
|
||||
// 🔽 --- Button Exercise --- 🔽
|
||||
/// 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>>,
|
||||
}
|
||||
|
||||
impl Button {
|
||||
/// returns true if button is pushed
|
||||
pub fn is_pushed(&self) -> bool {
|
||||
self.inner.is_low() == Ok(true)
|
||||
}
|
||||
}
|
||||
// 🔼 --- Button Exercise --- 🔼
|
||||
|
||||
/// A timer for creating blocking delays
|
||||
pub struct Timer {
|
||||
inner: hal::Timer<hal::pac::TIMER0, OneShot>,
|
||||
}
|
||||
|
||||
|
||||
impl Timer {
|
||||
/// Blocks program execution for at least the specified `duration`
|
||||
pub fn wait(&mut self, duration: Duration) {
|
||||
defmt::trace!("blocking for {:?} ...", duration);
|
||||
|
||||
// 1 cycle = 1 microsecond because the underlying HAL driver
|
||||
// always sets the timer to 1 MHz.
|
||||
const NANOS_IN_ONE_MICRO: u32 = 1_000;
|
||||
let subsec_micros = duration.subsec_nanos() / NANOS_IN_ONE_MICRO;
|
||||
if subsec_micros != 0 {
|
||||
self.inner.delay(subsec_micros);
|
||||
}
|
||||
|
||||
const MICROS_IN_ONE_SEC: u32 = 1_000_000;
|
||||
// maximum number of seconds that fit in a single `delay` call without overflowing the `u32`
|
||||
// argument
|
||||
const MAX_SECS: u32 = u32::MAX / MICROS_IN_ONE_SEC;
|
||||
let mut secs = duration.as_secs();
|
||||
while secs != 0 {
|
||||
let cycles = if secs > MAX_SECS as u64 {
|
||||
secs -= MAX_SECS as u64;
|
||||
MAX_SECS * MICROS_IN_ONE_SEC
|
||||
} else {
|
||||
let cycles = secs as u32 * MICROS_IN_ONE_SEC;
|
||||
secs = 0;
|
||||
cycles
|
||||
};
|
||||
|
||||
self.inner.delay(cycles)
|
||||
}
|
||||
|
||||
defmt::trace!("... DONE");
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Deref for Timer {
|
||||
type Target = hal::Timer<hal::pac::TIMER0, OneShot>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::DerefMut for Timer {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
// 🔽 --- UARTE Exercise --- 🔽
|
||||
/// Uarte peripheral
|
||||
pub struct Uarte {
|
||||
inner: hal::Uarte<hal::pac::UARTE1>,
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
// 🔼 --- UARTE Exercise --- 🔼
|
||||
|
||||
/// Initializes the board
|
||||
///
|
||||
/// This return an `Err`or if called more than once
|
||||
pub fn init() -> Result<Board, ()> {
|
||||
if let Some(periph) = hal::pac::Peripherals::take() {
|
||||
|
||||
let pins = p0::Parts::new(periph.P0);
|
||||
|
||||
// NOTE LEDs turn on when the pin output level is low
|
||||
let led_1 = pins.p0_13.degrade().into_push_pull_output(Level::High);
|
||||
let led_2 = pins.p0_14.degrade().into_push_pull_output(Level::High);
|
||||
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);
|
||||
|
||||
// 🔽 --- 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();
|
||||
// 🔼 --- Button Exercise --- 🔼
|
||||
|
||||
defmt::debug!("I/O pins have been configured for digital output");
|
||||
|
||||
let timer = hal::Timer::new(periph.TIMER0);
|
||||
|
||||
// 🔽 --- UARTE Exercise --- 🔽
|
||||
// Uarte
|
||||
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: None,
|
||||
rts: None,
|
||||
};
|
||||
|
||||
|
||||
let uarte = hal::uarte::Uarte::new(periph.UARTE1, pins, Parity::EXCLUDED, Baudrate::BAUD115200);
|
||||
// 🔼 --- UARTE Exercise --- 🔼
|
||||
|
||||
Ok(Board {
|
||||
leds: Leds {
|
||||
led_1: Led { inner: led_1 },
|
||||
led_2: Led { inner: led_2 },
|
||||
led_3: Led { inner: led_3 },
|
||||
led_4: Led { inner: led_4 },
|
||||
},
|
||||
|
||||
// 🔽 --- 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},
|
||||
},
|
||||
// 🔼 --- Button Exercise --- 🔼
|
||||
|
||||
timer: Timer { inner: timer },
|
||||
|
||||
// 🔽 --- UARTE Exercise --- 🔽
|
||||
uarte: Uarte { inner: uarte },
|
||||
// 🔼 --- UARTE Exercise --- 🔼
|
||||
})
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Exits the application when the program is executed through the `probe-run` Cargo runner
|
||||
pub fn exit() -> ! {
|
||||
unsafe {
|
||||
// turn off the USB D+ pull-up before pausing the device with a breakpoint
|
||||
// this disconnects the nRF device from the USB host so the USB host won't attempt further
|
||||
// USB communication (and see an unresponsive device). probe-run will also reset the nRF's
|
||||
// USBD peripheral when it sees the device in a halted state which has the same effect as
|
||||
// this line but that can take a while and the USB host may issue a power cycle of the USB
|
||||
// port / hub / root in the meantime, which can bring down the probe and break probe-run
|
||||
const USBD_USBPULLUP: *mut u32 = 0x4002_7504 as *mut u32;
|
||||
USBD_USBPULLUP.write_volatile(0)
|
||||
}
|
||||
defmt::println!("`dk::exit()` called; exiting ...");
|
||||
// force any pending memory operation to complete before the BKPT instruction that follows
|
||||
atomic::compiler_fence(Ordering::SeqCst);
|
||||
loop {
|
||||
asm::bkpt()
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
fn port_as_char(port: &Port) -> char {
|
||||
match port {
|
||||
Port::Port0 => '0',
|
||||
Port::Port1 => '1',
|
||||
}
|
||||
}
|
22
down-the-stack/dk_pac/Cargo.toml
Normal file
22
down-the-stack/dk_pac/Cargo.toml
Normal file
|
@ -0,0 +1,22 @@
|
|||
[package]
|
||||
authors = ["Tanks Transfeld <tanks.transfeld@ferrous-systems.com"]
|
||||
edition = "2018"
|
||||
license = "MIT OR Apache-2.0"
|
||||
name = "dk_pac"
|
||||
version = "0.0.0"
|
||||
|
||||
[dependencies]
|
||||
critical-section = { version = "1.0", optional = true }
|
||||
cortex-m = {version = "0.7.6", features = ["critical-section-single-core"]}
|
||||
cortex-m-rt = "0.7.2"
|
||||
vcell = "0.1.2"
|
||||
panic-probe = { version = "0.3.0", features = ["print-defmt"] }
|
||||
defmt = "0.3.2"
|
||||
defmt-rtt = "0.4"
|
||||
|
||||
[features]
|
||||
rt = ["cortex-m-rt/device"]
|
||||
default = [
|
||||
"rt"
|
||||
]
|
||||
other-feature = []
|
47016
down-the-stack/dk_pac/nrf52.svd
Normal file
47016
down-the-stack/dk_pac/nrf52.svd
Normal file
File diff suppressed because it is too large
Load diff
19
down-the-stack/solutions/.cargo/config.toml
Normal file
19
down-the-stack/solutions/.cargo/config.toml
Normal file
|
@ -0,0 +1,19 @@
|
|||
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
|
||||
# (..)
|
||||
rustflags = [
|
||||
"-C", "linker=flip-link", # adds stack overflow protection
|
||||
"-C", "link-arg=-Tdefmt.x", # defmt support
|
||||
# (..)
|
||||
]
|
||||
|
||||
[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
|
50
down-the-stack/solutions/Cargo.toml
Normal file
50
down-the-stack/solutions/Cargo.toml
Normal file
|
@ -0,0 +1,50 @@
|
|||
[package]
|
||||
authors = ["Tanks Transfeld <tanks.transfeld@ferrous-systems.com>"]
|
||||
edition = "2018"
|
||||
license = "MIT OR Apache-2.0"
|
||||
name = "solutions"
|
||||
version = "0.0.0"
|
||||
|
||||
[dependencies]
|
||||
cortex-m = {version = "0.7.6", features = ["critical-section-single-core"]}
|
||||
cortex-m-rt = "0.7.2"
|
||||
dk_bsc = { path = "../dk_bsc" }
|
||||
dk_pac = { path = "../dk_pac", features = ["critical-section"]}
|
||||
heapless = "0.7.16"
|
||||
panic-probe = { version = "0.3.0", features = ["print-defmt"] }
|
||||
defmt = "0.3.2"
|
||||
defmt-rtt = "0.3.2"
|
||||
|
||||
# optimize code in both profiles
|
||||
[profile.dev]
|
||||
codegen-units = 1
|
||||
debug = 2
|
||||
debug-assertions = true # !
|
||||
incremental = false
|
||||
lto = "fat"
|
||||
opt-level = 'z' # !
|
||||
overflow-checks = false
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
debug = 1
|
||||
debug-assertions = false
|
||||
incremental = false
|
||||
lto = "fat"
|
||||
opt-level = 3
|
||||
overflow-checks = false
|
||||
|
||||
[features]
|
||||
|
||||
|
||||
default = [
|
||||
"other-feature"
|
||||
]
|
||||
other-feature = []
|
||||
# do NOT modify these features
|
||||
defmt-default = []
|
||||
defmt-trace = []
|
||||
defmt-debug = []
|
||||
defmt-info = []
|
||||
defmt-warn = []
|
||||
defmt-error = []
|
4
down-the-stack/solutions/README.md
Normal file
4
down-the-stack/solutions/README.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
# Solutions
|
||||
|
||||
This program does not build if the pac is not generated!
|
||||
The file is only for reference.
|
48
down-the-stack/solutions/src/bin/uarte_enable.rs
Normal file
48
down-the-stack/solutions/src/bin/uarte_enable.rs
Normal file
|
@ -0,0 +1,48 @@
|
|||
#![no_main]
|
||||
#![no_std]
|
||||
|
||||
use cortex_m::asm;
|
||||
use cortex_m_rt::entry;
|
||||
use dk_pac::UARTE0;
|
||||
|
||||
// this imports `down-the-stack/apps/lib.rs` to retrieve our global logger + panicking-behavior
|
||||
use solutions as _;
|
||||
use defmt;
|
||||
use defmt_rtt as _; // global logger
|
||||
|
||||
#[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]`
|
||||
|
||||
// takes ownership of the nRF52840-DK peripherals
|
||||
let periph = dk_pac::Peripherals::take().unwrap();
|
||||
let uarte = periph.UARTE0;
|
||||
|
||||
is_uarte_enabled(&uarte);
|
||||
|
||||
// enable the UART0 peripheral the safe way
|
||||
uarte.enable.write(|w| w.enable().enabled());
|
||||
|
||||
is_uarte_enabled(&uarte);
|
||||
|
||||
// disable the UART0 peripheral by writing 0 directly into the register -- the unsafe way
|
||||
unsafe {
|
||||
uarte.enable.write(|w| w.bits(0x00u32));
|
||||
}
|
||||
|
||||
is_uarte_enabled(&uarte);
|
||||
|
||||
// this program does not `exit`; use Ctrl+C to terminate it
|
||||
loop {
|
||||
asm::nop();
|
||||
}
|
||||
}
|
||||
|
||||
fn is_uarte_enabled(uarte: &UARTE0) {
|
||||
if uarte.enable.read().enable().is_enabled() {
|
||||
defmt::println!("Uarte0 is enabled");
|
||||
} else {
|
||||
defmt::println!("Uarte0 is disabled");
|
||||
}
|
||||
}
|
11
down-the-stack/solutions/src/lib.rs
Normal file
11
down-the-stack/solutions/src/lib.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
#![no_std]
|
||||
|
||||
// ⚠️ ⚠️ ⚠️ Don't change this file! ⚠️ ⚠️ ⚠️ so it's exactly like apps/src/lib.rs
|
||||
use panic_probe as _;
|
||||
|
||||
// same panicking *behavior* as `panic-probe` but doesn't print a panic message
|
||||
// this prevents the panic message being printed *twice* when `defmt::panic` is invoked
|
||||
#[defmt::panic_handler]
|
||||
fn panic() -> ! {
|
||||
cortex_m::asm::udf()
|
||||
}
|
|
@ -24,6 +24,14 @@
|
|||
- [Collision avoidance](./collision-avoidance.md)
|
||||
- [Interrupt handling](./interrupt-handling.md)
|
||||
- [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)
|
||||
- [Generating and Using a PAC](./pac-exercise.md)
|
||||
- [Exercise: Generating a PAC ](./generating-pac.md)
|
||||
- [Reading PAC Documentation ](./pac-docs.md)
|
||||
- [Exercise: Enabling the UARTE0 Peripheral ](./enabling-uarte.md)
|
||||
- [Advanced Workbook](./advanced-workbook.md)
|
||||
- [Code Organization](./code-organisation.md)
|
||||
- [Listing USB Devices](./listing-usb-devices.md)
|
||||
|
|
67
embedded-workshop-book/src/bsc-exercise.md
Normal file
67
embedded-workshop-book/src/bsc-exercise.md
Normal file
|
@ -0,0 +1,67 @@
|
|||
# BSC Exercise
|
||||
|
||||
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.
|
||||
|
||||
## 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!
|
||||
|
||||
## Prerequisites
|
||||
* installation of `pyserial` or other serial terminal that you are familiar with.
|
||||
* `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.
|
||||
|
||||
|
||||
|
146
embedded-workshop-book/src/button-implementation.md
Normal file
146
embedded-workshop-book/src/button-implementation.md
Normal 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
|
||||
```
|
10
embedded-workshop-book/src/down-the-stack.md
Normal file
10
embedded-workshop-book/src/down-the-stack.md
Normal 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.
|
103
embedded-workshop-book/src/enabling-uarte.md
Normal file
103
embedded-workshop-book/src/enabling-uarte.md
Normal file
|
@ -0,0 +1,103 @@
|
|||
# Enabling the UARTE0 peripheral
|
||||
|
||||
Write a simple program which uses the PAC to enable the UARTE0. See how writing arbitrary values to the ENABLE field in the ENABLE register is unsafe, because only values 0 or 8 should be used.
|
||||
|
||||
## In this exercise you will learn how to:
|
||||
* to safely write into a register
|
||||
* how to write raw bits into a register
|
||||
* how to read a register
|
||||
|
||||
## Prerequisites
|
||||
* basic use of closures
|
||||
* usage of the svd2rust's read/write/modify API
|
||||
|
||||
## Tasks
|
||||
* Find out which values can be written into the `enable` register.
|
||||
* Take ownership of the board's peripherals
|
||||
* Write a helper function that reads `UARTE0`'s `enable` register and prints the enable status.
|
||||
* Enable the UARTE0 peripheral using a safe method.
|
||||
* Disable the UARTE0 peripheral by writing raw bits in it (unsafe).
|
||||
|
||||
Final terminal output:
|
||||
|
||||
```terminal
|
||||
Uarte0 is disabled.
|
||||
Uarte0 is ensabled.
|
||||
Uarte0 is disabled.
|
||||
```
|
||||
|
||||
Find the starter code in `down-the-stack/apps/src/bin/uarte_enable.rs`
|
||||
Find the full solution in `down-the-stack/solutions/src/bin/uarte_enable.rs`
|
||||
|
||||
|
||||
## Step-by-Step Solution
|
||||
|
||||
### Step 1: Find the values that can be written in the `enable` register:
|
||||
|
||||
0: disabled
|
||||
8: enabled
|
||||
|
||||
### Step 2: Import the PAC
|
||||
|
||||
In the Cargo configuration file for the `apps` package, `down-the-stack/apps/Cargo.toml`, add:
|
||||
|
||||
```
|
||||
dk_pac = { path = "../dk_pac", features = ["critical-section"]}
|
||||
```
|
||||
In the main source file for the `uarte_enable` binary, `apps/bin/uarte_enable.rs`, add:
|
||||
|
||||
```rust
|
||||
use dk_pac::UARTE0;
|
||||
```
|
||||
|
||||
### Step 3: Take ownership of the peripherals with `take()`
|
||||
|
||||
Take ownership of the peripherals with `take()`. Be aware, the take returns an `Option<T>` so that needs to be taken care of. Bind the `UARTE0` peripheral to its own variable.
|
||||
|
||||
```rust
|
||||
let periph = dk_pac::Peripherals::take().unwrap();
|
||||
let uarte = periph.UARTE0;
|
||||
```
|
||||
|
||||
### Step 4: Write a helper function to get the status of the register.
|
||||
|
||||
The helper function either reads the raw bits of the enable register or makes use of the specific method available.
|
||||
|
||||
The function prints "Uarte0 is enabled" or "Uarte0 is disabled" depending on the case. Add a function call to `fn main()`.
|
||||
|
||||
Run the code. The terminal output should read: "Uarte0 is disabled".
|
||||
|
||||
```rust
|
||||
fn is_uarte_enabled(uarte: &UARTE0) {
|
||||
if uarte.enable.read().enable().is_enabled() {
|
||||
defmt::println!("Uarte0 is enabled");
|
||||
} else {
|
||||
defmt::println!("Uarte0 is disabled");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 5: Enable the peripheral safely.
|
||||
|
||||
Enable the peripheral safely by passing `w.enable().enabled()` in the closure of a call to `write()` on the `enable` field of our UARTE object. Call the helper function after this new line and run your code.
|
||||
|
||||
It should print:
|
||||
|
||||
```terminal
|
||||
Uarte0 is disabled.
|
||||
Uarte0 is ensabled.
|
||||
```
|
||||
|
||||
```rust
|
||||
uarte.enable.write(|w| w.enable().enabled());
|
||||
```
|
||||
|
||||
### Step 6: Disable the peripheral unsafely by writing raw bits into the register.
|
||||
|
||||
Write 0 into the register to disable it by passing the closure `|w| w.bits(0u32)` to the `write()` method. This action is unsafe, so it needs to be in an unsafe block. Call the helper function once more and run your code. Compare your output with the expected output on top of this page.
|
||||
|
||||
```rust
|
||||
unsafe {
|
||||
uarte.enable.write(|w| w.bits(0x00 as u32));
|
||||
}
|
||||
```
|
79
embedded-workshop-book/src/generating-pac.md
Normal file
79
embedded-workshop-book/src/generating-pac.md
Normal file
|
@ -0,0 +1,79 @@
|
|||
# Generating the PAC
|
||||
|
||||
Generate your own PAC from an SVD file.
|
||||
|
||||
## In this exercise you will learn how to
|
||||
* generate a PAC from an SVD file.
|
||||
* format the generated code.
|
||||
* split the single PAC file into one file per module.
|
||||
|
||||
## Prerequisites
|
||||
* usage of cargo install
|
||||
* generating docs
|
||||
|
||||
## Tasks
|
||||
* Install `svd2rust` and `form` via cargo.
|
||||
* Run `svd2rust` on `nrf52.svd` using the `cortex-m` target.
|
||||
* Split the file into its modules using `form`.
|
||||
* Format the generated file to make it readable.
|
||||
* Check the documentation.
|
||||
|
||||
## Step-by-Step Solution
|
||||
|
||||
|
||||
✅ Install the necessary tools using the following commands:
|
||||
|
||||
```terminal
|
||||
cargo install svd2rust
|
||||
cargo install form
|
||||
```
|
||||
|
||||
✅ Go `down-the-stack/dk-pac`. The folder contains the SVD file `nrf52.svd`. We also provide a `Cargo.toml` file, as it will not be generated by svd2rust.
|
||||
|
||||
✅ In the terminal, go to the SVD file's location. Run `svd2rust` with the SVD file to generate a PAC using the `cortex-m` target.
|
||||
|
||||
```
|
||||
svd2rust --target cortex-m -i nrf52.svd
|
||||
```
|
||||
If you check the folder `down-the-stack/dk-pac` now, you see three new files:
|
||||
* lib.rs - the file that contains the generated code for the pac
|
||||
* device.x - linker script that weakly aliases all the interrupt handlers to the default exception handler (DefaultHandler).
|
||||
* build.rs - build script that places device.x somewhere the linker can find.
|
||||
|
||||
✅ Make an `/src` and move the generated `lib.rs` into it.
|
||||
|
||||
✅ Open the generated `lib.rs` with an editor.
|
||||
Notice how it's barely correctly formatted.
|
||||
|
||||
✅ Look at the PAC docs with the following command:
|
||||
|
||||
```terminal
|
||||
cargo doc --open
|
||||
```
|
||||
|
||||
✅ Format the crate using `cargo fmt`.
|
||||
This does not change to the docs, but `lib.rs` is a bit more readable.
|
||||
|
||||
✅ Use form to process the `lib.rs` to split it into modules, so that each module in in it's own file.
|
||||
|
||||
```terminal
|
||||
form -i src/lib.rs -o src/
|
||||
```
|
||||
|
||||
✅ Re-run `cargo fmt`.
|
||||
|
||||
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### `form`
|
||||
|
||||
In case the `form` command above causes problems, try this instead:
|
||||
|
||||
1. Move `lib.rs` out of `/src`.
|
||||
|
||||
2. Run the following command:
|
||||
|
||||
```terminal
|
||||
form -i ./lib.rs -o ./src
|
||||
```
|
|
@ -157,3 +157,6 @@ $ cargo install nrfdfu
|
|||
(..)
|
||||
Installed package `nrfdfu v0.1.3` (..)
|
||||
```
|
||||
## pySerial for Down The Stack
|
||||
|
||||
If you don't have a serial terminal you're familiar with, we recommend using `pySerial`. Follow the installation instructions [here](https://pythonhosted.org/pyserial/pyserial.html#installation). This may require an update of your python installation. This is needed for the second day of the 3 day embedded training.
|
47
embedded-workshop-book/src/pac-docs.md
Normal file
47
embedded-workshop-book/src/pac-docs.md
Normal file
|
@ -0,0 +1,47 @@
|
|||
# Reading PAC Documentation
|
||||
(This should be covered or at least preceded by a lecture that includes basic use of closures and the read/write/modify API)
|
||||
|
||||
Generate and open the PAC's docs using the following command:
|
||||
|
||||
```
|
||||
cargo doc --open
|
||||
```
|
||||
|
||||
In the `Structs` section look for the `struct Peripherals`. Taking ownership of it will be the first step later on. Note that only the method `steal()` is documented. It is an unsafe method, and to be avoided. `Peripherals` has a field named `UARTE0`.
|
||||
|
||||
In the `modules` section, look for the `uarte0` module. It is divided into submodules. `enable` is the register we are concerned about. Clicking on it shows the associated type definitions.
|
||||
|
||||
* `W` - the writer proxy for the ENABLE register, with the following methods:
|
||||
* `enable()` returns the field ENABLE writer `ENABLE_W`.
|
||||
* `unsafe bits()` writes raw bits into the register.
|
||||
* `R` - the reader proxy for the ENABLE register, with the following methods:
|
||||
* `enable()` returns the field ENABLE reader `ENABLE_R`.
|
||||
* `bits()` reads raw bits from the register.
|
||||
|
||||
The types `ENABLE_R` and `ENABLE_W` have methods that you can use if you don't want to deal with raw bits. Check them out!
|
||||
|
||||
Usage: If you want to write or read something from the uarte register and you want to avoid dealing with raw bits, you first have to call a method that gives you access to the respective reader or writer, and then call the method that does what you want.
|
||||
|
||||
Example:
|
||||
|
||||
```rust
|
||||
// this reads the enable register, and returns true if the register is disabled.
|
||||
uarte.enable.read().is_disabled()
|
||||
```
|
||||
|
||||
Note the difference between the struct field `UARTE0` in `Peripherals` and the module `uarte0`.
|
||||
|
||||
## Finding corresponding sections in the PAC
|
||||
|
||||
* `dk_pac/src/lib.rs` defines the singleton peripherals as part of a larger `struct Peripherals`. There are two methods for this struct: `take()` and `steal()`. `take()` assures, that only one instance of this can exist. Hence, it's safe. Note that `take()` is only available with the `critical-section` feature enabled.
|
||||
|
||||
To generate documentation that includes `take()`, pass the `critical-section` feature flag when generating the docs:
|
||||
|
||||
```
|
||||
cargo doc --open --features critical-section
|
||||
```
|
||||
|
||||
* `dk_pac/src/uarte0.rs` defines a struct that contains all the registers of the `UARTE0` peripheral. The `enable` field represents the register of the same name.
|
||||
|
||||
* `dk_pac/src/uarte0/enable.rs` defines the types associated with this register that you already saw in the docs.
|
||||
|
5
embedded-workshop-book/src/pac-exercise.md
Normal file
5
embedded-workshop-book/src/pac-exercise.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# Generating and Using a PAC
|
||||
|
||||
In this module you will learn how to generate a PAC (Peripheral Access Crate) from an SVD file, read its documentation and write a small program that enables the UARTE0 peripheral.
|
||||
|
||||
|
204
embedded-workshop-book/src/uarte-implementation.md
Normal file
204
embedded-workshop-book/src/uarte-implementation.md
Normal file
|
@ -0,0 +1,204 @@
|
|||
# Write the Uarte implementation
|
||||
## Step-by-Step Solution
|
||||
|
||||
### Step 1: Check Documentation.
|
||||
|
||||
The UART protocol requires up to four pins, two are mandatory, additional two are optional. they are usually labelled:
|
||||
* RXD
|
||||
* TXD
|
||||
* CTS (optional)
|
||||
* RTS (optional)
|
||||
|
||||
We will only use the mandatory ones.
|
||||
|
||||
✅ 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`: Their type is `None` in this case.
|
||||
|
||||
✅ 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::EXCLUDED`
|
||||
* 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: None,
|
||||
rts: None,
|
||||
};
|
||||
|
||||
|
||||
let uarte = hal::uarte::Uarte::new(periph.UARTE1, pins, Parity::EXCLUDED, 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
|
||||
|
||||
✅ Use the following command to find the address of the nRF52840-DK on your computer.
|
||||
|
||||
Linux:
|
||||
```
|
||||
$ ls /dev/ttyACM*
|
||||
```
|
||||
|
||||
MacOS:
|
||||
|
||||
```
|
||||
ls /dev/tty.usbmodem*
|
||||
```
|
||||
|
||||
Windows:
|
||||
a USB Serial Device (COM port) in the Device Manager under the Ports section.
|
||||
|
||||
✅ Run the following command to run `miniterm` with the nRF52840-DK with 115200 baud. This includes the default settings of no parity, 8 data bits, 1 stop bit
|
||||
|
||||
```
|
||||
python3 -m serial.tools.miniterm <address> 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 `miniterm` runs, "Hello, World" should appear.
|
||||
|
Loading…
Reference in a new issue