diff --git a/down-the-stack-book/src/the_pac.md b/down-the-stack-book/src/the_pac.md index 9605193..ed08e94 100644 --- a/down-the-stack-book/src/the_pac.md +++ b/down-the-stack-book/src/the_pac.md @@ -4,8 +4,7 @@ ## Introduction -This crate sits at the bottom of the 'stack'. It provides access to the -memory-mapped peripherals in your MCU. +This crate sits at the bottom of the 'stack'. It provides access to the 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*. * Each field is at least 1 bit in length. @@ -30,24 +29,19 @@ memory-mapped peripherals in your MCU. ## C Code! -Embedded Code in C often uses shifts and bitwise-AND to make up registers from -fields. +Embedded Code in C often uses shifts and bitwise-AND to make up registers from fields. -```c,no_run +```c #define UARTE_INTEN_CTS_SHIFT (0) #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_MASK (0x00000001) -// The other eight fields are skipped for brevity +// The other nine fields are skipped for brevity uint32_t cts = 0; -uint32_t ncts = 1; uint32_t rxrdy = 1; 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); *((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 -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 { uint32_t tasks_startrx; // @ 0x000 uint32_t tasks_stoprx; // @ 0x004 @@ -67,27 +74,37 @@ typedef volatile struct uart0_reg_t { uint32_t inten; // @ 0x300 uint32_t _padding[79]; 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 -const UARTE0_INTEN: *mut u32 = 0x4000_2300 as *mut u32; -unsafe { UARTE0_INTEN.write_volatile(0x0000_0003); } +```rust +pub struct RegisterBlock { + pub tasks_startrx: VolatileCell, // @ 0x000 + pub tasks_stoprx: VolatileCell, // @ 0x004 + // ... + pub inten: VolatileCell, // @ 0x300 + _reserved12: [u32; 79], + pub baudrate: VolatileCell, // @ 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 -A CMSIS-SVD (or just SVD) file is an XML description of all the peripherals, -registers and fields on an MCU. +A CMSIS-SVD (or just SVD) file is an XML description of all the peripherals, registers and fields on an MCU. We can use `svd2rust` to turn this into a Peripheral Access Crate. @@ -100,12 +117,6 @@ graph LR ## 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 graph TB Peripherals --> uarte1[.UARTE1: UARTE1] @@ -116,6 +127,12 @@ graph TB uarte2 --> uart2_inten[.inten: INTEN] ``` +* 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) @@ -129,14 +146,14 @@ graph TB --- -## An example +## Using a PAC -```rust,no_run +```rust // nrf52840 is the PAC 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(); -// This register has multiple fields +// Modifying multiple fields in one go p.UARTE1.inten.modify(|_r, w| { w.cts().enabled(); w.ncts().enabled(); @@ -157,15 +174,11 @@ p.UARTE1.inten.modify(|_r, w| { ## Let's take it in turns -* I, the callee, need to set some stuff up -* You, the caller, need to do a bit of work -* I, the callee, need to clean everything up +- I, the callee, need to set some stuff up +- You, the caller, need to do a bit of work +- I, the callee, need to clean everything up -We can use a closure to insert the caller-provided code in the middle of our -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! +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! --- @@ -190,5 +203,4 @@ Docs can be generated from the source code. See -Note that `uarte0` is a *module* and `UARTE0` could mean either a `struct` type, -or a field on the `Peripherals` struct. +Note that `uarte0` is a *module* and `UARTE0` could mean either a `struct` type, or a field on the `Peripherals` struct.