mirror of
https://github.com/ferrous-systems/embedded-trainings-2020.git
synced 2024-05-18 16:28:22 +00:00
33778655e4
Has notes on PACs.
4 KiB
4 KiB
The Peripheral Access Crate
Introduction
This crate sits at the bottom of the 'stack'. It provides access to the memory-mapped peripherals in your MCU.
Memory Mapped Peripherals
- e.g. a UART peripheral
- Has registers, represented by a memory address
- Registers are usually consecutive in memory (not always)
- Peripherals can have instances (same layout of registers, different start address)
- UART0, UART1, etc
Datasheets
- Registers are comprised of one or more fields.
- Each field is at least 1 bit in length.
- Sometimes fields can only take from a limited set of values
- This is all in your datasheet!
C Code!
Embedded Code in C often uses shifts and bitwise-AND to make up registers from fields.
#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
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;
Adding structure
The various registers for a peripheral are often grouped into a struct
typedef volatile struct uart0_reg_t {
uint32_t tasks_startrx; // @ 0x000
uint32_t tasks_stoprx; // @ 0x004
// ...
uint32_t inten; // @ 0x300
uint32_t _padding[79];
uint32_t baudrate; // @ 0x500
} uart0_reg_t;
Rust Code
You could do this in Rust if you wanted...
const UARTE0_INTEN: *mut u32 = 0x4000_2300 as *mut u32;
unsafe { UARTE0_INTEN.write_volatile(0x0000_0003); }
But it seems like a lot of reading PDFs and re-typing everything?
CMSIS-SVD Files
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.
graph LR
svd[(SVD XML)] --> svd2rust[<tt>svd2rust</tt>] --> rust[(Rust Source)]
The svd2rust
generated API
- The crate has a top-level
struct Peripherals
with members for each Peripheral - Each Peripheral gets a
struct
, likeUARTE0
,SPI1
, etc. - Each Peripheral
struct
has members for each Register - Each Register gets a
struct
, likeBAUDRATE
,INTEN
, etc. - Each Register
struct
hasread()
,write()
andmodify()
methods
graph TB
Peripherals --> uarte1[.UARTE1: <b>UARTE1</b>]
uarte1 --> uart1_baudrate[.baudrate: <b>BAUDRATE</b>]
uarte1 --> uart1_inten[.inten: <b>INTEN</b>]
Peripherals --> uarte2[.UARTE2: <b>UARTE2</b>]
uarte2 --> uart2_baudrate[.baudrate: <b>BAUDRATE</b>]
uarte2 --> uart2_inten[.inten: <b>INTEN</b>]
The svd2rust
generated API (2)
- The
read()
method returns a special proxy object, with methods for each Field - The
write()
method takes a closure, which is given a special 'proxy' object, with methods for each Field- All the Field changes are batched together and written in one go
- Any un-written Fields are set to a default value
- The
modify()
method gives you both- Any un-written Fields are left alone
An example
// nrf52840 is the PAC
let p = nrf52840::Peripherals::take().unwrap();
// This register has only one field
let current_baud_rate = p.UARTE1.baudrate.read().baudrate();
// This register has multiple fields
p.UARTE1.inten.write(|w| {
w.cts().enabled();
w.ncts().enabled();
w.rxrdy().enabled();
w
});
Documentation
Docs can be generated from the source code.
Note that uarte0
is a module and UARTE0
could mean either a struct
type,
or a field on the Peripherals
struct.