Merge pull request #200 from ferrous-systems/svd2rust_clean

Svd2rust exercise
This commit is contained in:
Tanks Transfeld 2023-03-23 11:53:18 +01:00 committed by GitHub
commit 9ec556be39
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 47459 additions and 0 deletions

22
down-the-stack/README.md Normal file
View 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

View File

@ -35,6 +35,7 @@ overflow-checks = false
[features]
default = [
"other-feature"
]

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

View 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 = []

File diff suppressed because it is too large Load Diff

View 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

View 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 = []

View File

@ -0,0 +1,4 @@
# Solutions
This program does not build if the pac is not generated!
The file is only for reference.

View 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");
}
}

View 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()
}

View File

@ -28,6 +28,10 @@
- [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)

View 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));
}
```

View 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
```

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

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