mirror of
https://github.com/ferrous-systems/embedded-trainings-2020.git
synced 2025-01-10 08:15:36 +00:00
Clean up slides.
This commit is contained in:
parent
a4ee38540d
commit
5e6fb4ab06
1 changed files with 54 additions and 42 deletions
|
@ -4,8 +4,7 @@
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
This crate sits at the bottom of the 'stack'. It provides access to the
|
This crate sits at the bottom of the 'stack'. It provides access to the memory-mapped peripherals in your MCU.
|
||||||
memory-mapped peripherals in your MCU.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -19,7 +18,7 @@ memory-mapped peripherals in your MCU.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Datasheets
|
## Registers
|
||||||
|
|
||||||
* *Registers* are comprised of one or more *fields*.
|
* *Registers* are comprised of one or more *fields*.
|
||||||
* Each field is at least 1 bit in length.
|
* Each field is at least 1 bit in length.
|
||||||
|
@ -30,24 +29,19 @@ memory-mapped peripherals in your MCU.
|
||||||
|
|
||||||
## C Code!
|
## C Code!
|
||||||
|
|
||||||
Embedded Code in C often uses shifts and bitwise-AND to make up registers from
|
Embedded Code in C often uses shifts and bitwise-AND to make up registers from fields.
|
||||||
fields.
|
|
||||||
|
|
||||||
```c,no_run
|
```c
|
||||||
#define UARTE_INTEN_CTS_SHIFT (0)
|
#define UARTE_INTEN_CTS_SHIFT (0)
|
||||||
#define UARTE_INTEN_CTS_MASK (0x00000001)
|
#define UARTE_INTEN_CTS_MASK (0x00000001)
|
||||||
#define UARTE_INTEN_NCTS_SHIFT (1)
|
|
||||||
#define UARTE_INTEN_NCTS_MASK (0x00000001)
|
|
||||||
#define UARTE_INTEN_RXRDY_SHIFT (2)
|
#define UARTE_INTEN_RXRDY_SHIFT (2)
|
||||||
#define UARTE_INTEN_RXRDY_MASK (0x00000001)
|
#define UARTE_INTEN_RXRDY_MASK (0x00000001)
|
||||||
|
|
||||||
// The other eight fields are skipped for brevity
|
// The other nine fields are skipped for brevity
|
||||||
uint32_t cts = 0;
|
uint32_t cts = 0;
|
||||||
uint32_t ncts = 1;
|
|
||||||
uint32_t rxrdy = 1;
|
uint32_t rxrdy = 1;
|
||||||
|
|
||||||
uint32_t inten_value = ((cts & UARTE_INTEN_CTS_MASK) << UARTE_INTEN_CTS_SHIFT)
|
uint32_t inten_value = ((cts & UARTE_INTEN_CTS_MASK) << UARTE_INTEN_CTS_SHIFT)
|
||||||
| ((ncts & UARTE_INTEN_NCTS_MASK) << UARTE_INTEN_NCTS_SHIFT)
|
|
||||||
| ((rxrdy & UARTE_INTEN_RXRDY_MASK) << UARTE_INTEN_RXRDY_SHIFT);
|
| ((rxrdy & UARTE_INTEN_RXRDY_MASK) << UARTE_INTEN_RXRDY_SHIFT);
|
||||||
|
|
||||||
*((volatile uint32_t*) 0x40002300) = inten_value;
|
*((volatile uint32_t*) 0x40002300) = inten_value;
|
||||||
|
@ -55,11 +49,24 @@ uint32_t inten_value = ((cts & UARTE_INTEN_CTS_MASK) << UARTE_INTEN_CTS_SHIFT)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Rust Code
|
||||||
|
|
||||||
|
You *could* do this in Rust if you wanted...
|
||||||
|
|
||||||
|
```rust
|
||||||
|
const UARTE0_INTEN: *mut u32 = 0x4000_2300 as *mut u32;
|
||||||
|
unsafe { UARTE0_INTEN.write_volatile(0x0000_0003); }
|
||||||
|
```
|
||||||
|
|
||||||
|
But this still seems very error-prone. Nothing stops you putting the wrong value at the wrong address.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Adding structure
|
## Adding structure
|
||||||
|
|
||||||
The various registers for a peripheral are often grouped into a `struct`
|
In C, the various registers for a peripheral can also be grouped into a `struct`
|
||||||
|
|
||||||
```c,no_run
|
```c
|
||||||
typedef volatile struct uart0_reg_t {
|
typedef volatile struct uart0_reg_t {
|
||||||
uint32_t tasks_startrx; // @ 0x000
|
uint32_t tasks_startrx; // @ 0x000
|
||||||
uint32_t tasks_stoprx; // @ 0x004
|
uint32_t tasks_stoprx; // @ 0x004
|
||||||
|
@ -67,27 +74,37 @@ typedef volatile struct uart0_reg_t {
|
||||||
uint32_t inten; // @ 0x300
|
uint32_t inten; // @ 0x300
|
||||||
uint32_t _padding[79];
|
uint32_t _padding[79];
|
||||||
uint32_t baudrate; // @ 0x500
|
uint32_t baudrate; // @ 0x500
|
||||||
} uart0_reg_t;
|
} uart0_reg_t
|
||||||
|
|
||||||
|
uart0_reg_t* const p_uart = (uart0_reg_t*) 0x40002000;
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Rust Code
|
## Structures in Rust
|
||||||
|
|
||||||
You *could* do this in Rust if you wanted...
|
We can do that too (and this is how our PAC works under the hood).
|
||||||
|
|
||||||
```rust,no_run
|
```rust
|
||||||
const UARTE0_INTEN: *mut u32 = 0x4000_2300 as *mut u32;
|
pub struct RegisterBlock {
|
||||||
unsafe { UARTE0_INTEN.write_volatile(0x0000_0003); }
|
pub tasks_startrx: VolatileCell<u32>, // @ 0x000
|
||||||
|
pub tasks_stoprx: VolatileCell<u32>, // @ 0x004
|
||||||
|
// ...
|
||||||
|
pub inten: VolatileCell<u32>, // @ 0x300
|
||||||
|
_reserved12: [u32; 79],
|
||||||
|
pub baudrate: VolatileCell<u32>, // @ 0x500
|
||||||
|
}
|
||||||
|
|
||||||
|
let p_uart: &RegisterBlock = unsafe { &*(0x40002000 as *const RegisterBlock) };
|
||||||
```
|
```
|
||||||
|
|
||||||
But it seems like a lot of reading PDFs and re-typing everything?
|
We use the [`VolatileCell`](https://docs.rs/vcell/0.1.3/vcell/struct.VolatileCell.html) to ensure reads/writes on the structure fields are always performed with volatile pointer read/writes.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## CMSIS-SVD Files
|
## CMSIS-SVD Files
|
||||||
|
|
||||||
A CMSIS-SVD (or just SVD) file is an XML description of all the peripherals,
|
A CMSIS-SVD (or just SVD) file is an XML description of all the peripherals, registers and fields on an MCU.
|
||||||
registers and fields on an MCU.
|
|
||||||
|
|
||||||
We can use `svd2rust` to turn this into a Peripheral Access Crate.
|
We can use `svd2rust` to turn this into a Peripheral Access Crate.
|
||||||
|
|
||||||
|
@ -100,12 +117,6 @@ graph LR
|
||||||
|
|
||||||
## The `svd2rust` generated API
|
## The `svd2rust` generated API
|
||||||
|
|
||||||
* The crate has a top-level `struct Peripherals` with members for each *Peripheral*
|
|
||||||
* Each *Peripheral* gets a `struct`, like `UARTE0`, `SPI1`, etc.
|
|
||||||
* Each *Peripheral* `struct` has members for each *Register*
|
|
||||||
* Each *Register* gets a `struct`, like `BAUDRATE`, `INTEN`, etc.
|
|
||||||
* Each *Register* `struct` has `read()`, `write()` and `modify()` methods
|
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
graph TB
|
graph TB
|
||||||
Peripherals --> uarte1[.UARTE1: <b>UARTE1</b>]
|
Peripherals --> uarte1[.UARTE1: <b>UARTE1</b>]
|
||||||
|
@ -116,6 +127,12 @@ graph TB
|
||||||
uarte2 --> uart2_inten[.inten: <b>INTEN</b>]
|
uarte2 --> uart2_inten[.inten: <b>INTEN</b>]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
* The crate has a top-level `struct Peripherals` with members for each *Peripheral*
|
||||||
|
* Each *Peripheral* gets a `struct`, like `UARTE0`, `SPI1`, etc.
|
||||||
|
* Each *Peripheral* `struct` has members for each *Register*
|
||||||
|
* Each *Register* gets a `struct`, like `BAUDRATE`, `INTEN`, etc.
|
||||||
|
* Each *Register* `struct` has `read()`, `write()` and `modify()` methods
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## The `svd2rust` generated API (2)
|
## The `svd2rust` generated API (2)
|
||||||
|
@ -129,14 +146,14 @@ graph TB
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## An example
|
## Using a PAC
|
||||||
|
|
||||||
```rust,no_run
|
```rust
|
||||||
// nrf52840 is the PAC
|
// nrf52840 is the PAC
|
||||||
let p = nrf52840::Peripherals::take().unwrap();
|
let p = nrf52840::Peripherals::take().unwrap();
|
||||||
// This register has only one field
|
// Reading the 'baudrate' field
|
||||||
let current_baud_rate = p.UARTE1.baudrate.read().baudrate();
|
let current_baud_rate = p.UARTE1.baudrate.read().baudrate();
|
||||||
// This register has multiple fields
|
// Modifying multiple fields in one go
|
||||||
p.UARTE1.inten.modify(|_r, w| {
|
p.UARTE1.inten.modify(|_r, w| {
|
||||||
w.cts().enabled();
|
w.cts().enabled();
|
||||||
w.ncts().enabled();
|
w.ncts().enabled();
|
||||||
|
@ -157,15 +174,11 @@ p.UARTE1.inten.modify(|_r, w| {
|
||||||
|
|
||||||
## Let's take it in turns
|
## Let's take it in turns
|
||||||
|
|
||||||
* I, the callee, need to set some stuff up
|
- I, the callee, need to set some stuff up
|
||||||
* You, the caller, need to do a bit of work
|
- You, the caller, need to do a bit of work
|
||||||
* I, the callee, need to clean everything up
|
- I, the callee, need to clean everything up
|
||||||
|
|
||||||
We can use a closure to insert the caller-provided code in the middle of our
|
We can use a closure to insert the caller-provided code in the middle of our function. We see this used [all (1)](https://doc.rust-lang.org/core/iter/trait.Iterator.html#method.map) [over (2)](https://doc.rust-lang.org/core/primitive.str.html#method.matches) [the (3)](https://doc.rust-lang.org/std/thread/fn.spawn.html) Rust standard library!
|
||||||
function. We see this used
|
|
||||||
[all](https://doc.rust-lang.org/core/iter/trait.Iterator.html#method.map)
|
|
||||||
[over](https://doc.rust-lang.org/core/primitive.str.html#method.matches)
|
|
||||||
[the](https://doc.rust-lang.org/std/thread/fn.spawn.html) Rust standard library!
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -190,5 +203,4 @@ Docs can be generated from the source code.
|
||||||
|
|
||||||
See <https://docs.rs/nrf52840>
|
See <https://docs.rs/nrf52840>
|
||||||
|
|
||||||
Note that `uarte0` is a *module* and `UARTE0` could mean either a `struct` type,
|
Note that `uarte0` is a *module* and `UARTE0` could mean either a `struct` type, or a field on the `Peripherals` struct.
|
||||||
or a field on the `Peripherals` struct.
|
|
||||||
|
|
Loading…
Reference in a new issue