svd2rust
Generate Rust register maps (
struct
s) from SVD files
$ svd2rust -i STM32F30x.svd
const GPIOA: usize = 0x48000000;
const GPIOB: usize = 0x48000400;
const GPIOC: usize = 0x48000800;
const GPIOD: usize = 0x48000c00;
const GPIOE: usize = 0x48001000;
const GPIOF: usize = 0x48001400;
(..)
``` $ svd2rust -i STM32F30x.svd rcc | head
/// Reset and clock control pub struct Rcc { /// Clock control register pub cr: Cr, /// Clock configuration register (RCCCFGR) pub cfgr: Cfgr, /// Clock interrupt register (RCCCIR) pub cir: Cir, /// APB2 peripheral reset register (RCC_APB2RSTR) (..) ```
The svd2rust
generates the following API for each peripheral:
A register block "definition" as a struct
. Example below:
``` rust /// Inter-integrated circuit
pub struct I2c1 { /// 0x00 - Control register 1 pub cr1: Cr1, /// 0x04 - Control register 2 pub cr2: Cr2, /// 0x08 - Own address register 1 pub oar1: Oar1, /// 0x0c - Own address register 2 pub oar2: Oar2, /// 0x10 - Timing register pub timingr: Timingr, /// 0x14 - Status register 1 pub timeoutr: Timeoutr, /// 0x18 - Interrupt and Status register pub isr: Isr, /// 0x1c - Interrupt clear register pub icr: Icr, /// 0x20 - PEC register pub pecr: Pecr, /// 0x24 - Receive data register pub rxdr: Rxdr, /// 0x28 - Transmit data register pub txdr: Txdr, } ```
The user has to "instantiate" this definition for each peripheral instance. They have several choices:
static
s and/or static mut
s. Example below:rust
extern "C" {
// I2C1 can be accessed in read-write mode
pub static mut I2C1: I2c;
// whereas I2C2 can only be accessed in "read-only" mode
pub static I2C1: I2c;
}
Where the addresses of these register blocks must be provided by a linker script:
ld
/* layout.ld */
I2C1 = 0x40005400;
I2C2 = 0x40005800;
This has the side effect that the I2C1
and I2C2
symbols get "taken" so no other C/Rust symbol
(static
, function
, etc.) can have the same name.
static
one, below:``` rust // Addresses of the register blocks. These are private. const I2C1: usize = 0x40005400; const I2C2: usize = 0x40005800;
// NOTE(unsafe) can alias references to mutable memory pub unsafe fn i2c1() -> &'mut static I2C { unsafe { &mut *(I2C1 as *mut I2c) } }
pub fn i2c2() -> &'static I2C { unsafe { &*(I2C2 as *const I2c) } } ```
read
/ modify
/ write
Each register in the register block, e.g. the cr1
field in the I2c
struct, exposes a combination
of the read
, modify
and write
methods. Which methods exposes each register depends on whether
the register is read-only, read-write or write-only:
read
method.write
method.read
, modify
and write
.This is signature of each of these methods:
(using the CR2
register as an example)
``` rust
impl Cr2 {
pub fn modify
pub fn read(&self) -> Cr2R { .. }
pub fn write<F>(&mut self, f: F)
where F: FnOnce(&mut Cr2W) -> &mut Cr2W,
{
..
}
} ```
The read
method performs a single, volatile LDR
instruction and returns a proxy Cr2R
struct
which allows access to only the readable bits (i.e. not to the reserved bits) of the CR2
register:
``` rust impl Cr2R { /// Bit 0 - Slave address bit 0 (master mode) pub fn sadd0(&self) -> bool { .. }
/// Bits 1:7 - Slave address bit 7:1 (master mode)
pub fn sadd1(&self) -> u8 { .. }
(..)
} ```
Usage looks like this:
rust
// is the SADD0 bit of the CR2 register set?
if i2c1.c2r.read().sadd0() {
// something
} else {
// something else
}
The write
method performs a single, volatile STR
instruction to write a value to the CR2
register. This method involves the Cr2W
struct which only allows constructing valid states of the
CR2
register.
The only constructor that Cr2W
provides is reset_value
which returns the value of the CR2
register after a reset. The rest of Cr2W
methods are "builder" like and can be used to set or
reset the writable bits of the CR2
register.
``` rust impl Cr2W { /// Reset value pub fn reset_value() -> Self { Cr2W { bits: 0 } }
/// Bits 1:7 - Slave address bit 7:1 (master mode)
pub fn sadd1(&mut self, value: u8) -> &mut Self { .. }
/// Bit 0 - Slave address bit 0 (master mode)
pub fn sadd0(&mut self, value: bool) -> &mut Self { .. }
} ```
The write
method takes a closure with signature &mut Cr2W -> &mut Cr2W
. If passed the identity
closure, |w| w
, the write
method will set the CR2
register to its reset value. Otherwise, the
closure specifies how that reset value will be modified before it's written to CR2
.
Usage looks like this:
rust
// Write to CR2, its reset value but with its SADD0 and SADD1 fields set to `true` and `0b0011110`
i2c1.cr2.write(|w| w.sadd0(true).sadd1(0b0011110));
Finally, the modify
method performs a read-modify-write operation that involves at least one LDR
instruction, one STR
instruction plus extra instructions to modify the fetched value of the CR2
register. This method accepts a closure that specifies how the CR2
register will be modified.
Usage looks like this:
rust
// Toggle the STOP bit of the CR2 register and set the START bit
i2c1.cr2.modify(|r, w| w.stop(!r.stop()).start(true));
Licensed under either of
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.