Crate svd2rust [−] [src]
Generate Rust register maps (struct
s) from SVD files
Changelog
Installation
$ cargo install svd2rust
Usage
- Get the start/base address of each peripheral's 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; const TSC: usize = 0x40024000; const CRC: usize = 0x40023000; const Flash: usize = 0x40022000; const RCC: usize = 0x40021000;
- Generate a register map for a single peripheral.
$ svd2rust -i STM32F30x.svd rcc | head //! Reset and clock control /// Register block pub struct Rcc { /// 0x00 - Clock control register pub cr: Cr, /// 0x04 - Clock configuration register (RCC_CFGR) pub cfgr: Cfgr, /// 0x08 - Clock interrupt register (RCC_CIR) pub cir: Cir, /// 0x0c - APB2 peripheral reset register (RCC_APB2RSTR)
API
svd2rust
generates the following API for each peripheral:
Register block
A register block "definition" as a struct
. Example below:
/// Register block #[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 the microcontroller has. There are two alternatives:
static
variables. 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; }
here 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 below:
// Base addresses of the register blocks. These are private. const I2C1: usize = 0x40005400; const I2C2: usize = 0x40005800; // NOTE(unsafe) hands out aliased `&mut-` references pub unsafe fn i2c1() -> &'static mut 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-only registers only expose the
read
method. - write-only registers only expose the
write
method. - read-write registers expose all the methods:
read
,modify
andwrite
.
This is signature of each of these methods:
(using I2C
's CR2
register as an example)
impl Cr2 { /// Modifies the contents of the register pub fn modify<F>(&mut self, f: F) where for<'w> F: FnOnce(&R, &'w mut W) -> &'w mut W { .. } /// Reads the contents of the register pub fn read(&self) -> R { .. } /// Writes to the register pub fn write<F>(&mut self, f: F) where F: FnOnce(&mut W) -> &mut W, { .. } }
The read
method "reads" the register using a single, volatile LDR
instruction and returns a proxy R
struct that allows access to only the
readable bits (i.e. not to the reserved or write-only bits) of the CR2
register:
/// Value read from the register impl R { /// Bit 0 - Slave address bit 0 (master mode) pub fn sadd0(&self) -> Sadd0R { .. } /// Bits 1:7 - Slave address bit 7:1 (master mode) pub fn sadd1(&self) -> Sadd1R { .. } (..) }
Usage looks like this:
// is the SADD0 bit of the CR2 register set? if i2c1.c2r.read().sadd0().bits() == 1 { // yes } else { // no }
On the other hand, the write
method writes some value to the register
using a single, volatile STR
instruction. This method involves a W
struct that only allows constructing valid states of the CR2
register.
The only constructor that W
provides is reset_value
which returns the
value of the CR2
register after a reset. The rest of W
methods are
"builder-like" and can be used to modify the writable bitfields 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) -> _Sadd1W { .. } /// Bit 0 - Slave address bit 0 (master mode) pub fn sadd0(&mut self) -> _Sadd0 { .. } }
The write
method takes a closure with signature (&mut W) -> &mut W
. If
the "identity closure", |w| w
, is passed then the write
method will set
the CR2
register to its reset value. Otherwise, the closure specifies how
the reset value will be modified before it's written to CR2
.
Usage looks like this:
// Starting from the reset value, `0x0000_0000`, change the bitfields SADD0 // and SADD1 to `1` and `0b0011110` respectively and write that to the // register CR2. i2c1.cr2.write(|w| unsafe { w.sadd0().bits(1).sadd1().bits(0b0011110) }); // NOTE ^ unsafe because you could be writing a reserved bit pattern into // the register. The SVD doesn't provide enough information to check that's // not the case. // NOTE The argument to `bits` will be *masked* before writing it to the // bitfield. This makes it impossible to write, for example, `6` to a 2-bit // field; instead, `6 & 3` (i.e. `2`) will be written to the bitfield.
Finally, the modify
method performs a single read-modify-write
operation that involves one read (LDR
) to the register, modifying the
value and then a single write (STR
) of the modified value to the
register. This method accepts a closure that specifies how the CR2 register
will be modified (the w
argument) and also provides access to the state of
the register before it's modified (the r
argument).
Usage looks like this:
// Set the START bit to 1 while KEEPING the state of the other bits intact i2c1.cr2.modify(|_, w| unsafe { w.start().bits(1) }); // TOGGLE the STOP bit, all the other bits will remain untouched i2c1.cr2.modify(|r, w| w.stop().bits(r.stop().bits() ^ 1));
enumeratedValues
If your SVD uses the <enumeratedValues>
feature, then the API will be
extended to provide even more type safety. This extension is backward
compatible with the original version so you could "upgrade" your SVD by
adding, yourself, <enumeratedValues>
to it and then use svd2rust
to
re-generate a better API that doesn't break the existing code that uses
that API.
The new read
API returns an enum that you can match:
match gpioa.dir.read().pin0() { gpio::dir::DirR::Input => { .. }, gpio::dir::DirR::Output => { .. }, }
or test for equality
if gpioa.dir.read().pin0() == gpio::dir::DirR::Input { .. }
It also provides convenience methods to check for a specific variant without having to import the enum:
if gpioa.dir.read().pin0().is_input() { .. } if gpioa.dir.read().pin0().is_output() { .. }
The original bits
method is available as well:
if gpioa.dir.read().pin0().bits() == 0 { .. }
And the new write
API provides similar additions as well: variant
lets
you pick the value to write from an enum
eration of the possible ones:
// enum DirW { Input, Output } gpioa.dir.write(|w| w.pin0().variant(gpio::dir::DirW::Output));
There are convenience methods to pick one of the variants without having to import the enum:
gpioa.dir.write(|w| w.pin0().output());
The bits
method is still available but will become safe if it's impossible
to write a reserved bit pattern into the register
// safe because there are only two options: `0` or `1` gpioa.dir.write(|w| w.pin0().bits(1));