Generate Rust register maps (
struct
s) from SVD files
- Get the start address of each peripheral register block.
$ 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;
(..)
- Generate a register map for a single peripheral.
$ svd2rust -i STM32F30x.svd rcc | head
#[repr(C)]
/// Reset and clock control
pub struct Rcc {
/// Clock control register
pub cr: Cr,
/// Clock configuration register (RCC_CFGR)
pub cfgr: Cfgr,
/// Clock interrupt register (RCC_CIR)
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:
/// Inter-integrated circuit
#[repr(C)]
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/orstatic mut
s. Example below:
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:
/* 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.
- "constructor" functions. Example, equivalent to the
static
one, below:
// 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) }
}
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-only registers only expose the
read
method. - write-only registers only expose the
write
method. - read-write registers exposes all the methods:
read
,modify
andwrite
.
This is signature of each of these methods:
(using the CR2
register as an example)
impl Cr2 {
pub fn modify<F>(&mut self, f: F)
where for<'w> F: FnOnce(&Cr2R, &'w mut Cr2W) -> &'w mut Cr2W
{
..
}
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:
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:
// 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.
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:
// 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:
// 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
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
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.