Crate svd2rust [−] [src]
Generate Rust register maps (structs) 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:
staticvariables. 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
readmethod. - write-only registers only expose the
writemethod. - read-write registers expose all the methods:
read,modifyandwrite.
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 enumeration 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));