stm32ral is my alternative register access layer (“RAL”) for STM32 devices in Rust. It replaces svd2rust with something a lot more light-weight and simple, and consequently much faster to compile. However, it does not provide the same type system svd2rust does, and therefore has somewhat less type safety. On the whole I think it is an interesting experiment in what alternatives might be possible and how we might improve svd2rust, but I have also used it in earnest, for instance on FFP.
The GitHub page says it best:
What Is It?
stm32ral is an experiment into a lightweight register access layer. It provides access to every register, and provides constants which define the fields and possible field values in those registers. In that sense it is a lot like C device header files. However, it also provides a couple of simple macros that permit very easy register access, with very simple generated code which is efficient even without optimisations enabled.
The main aims are simplicity, compactness, and completeness. You get a module structure that contains a struct for each peripheral comprising just its registers in order, and you get a lot of constants for field widths, positions, and possible values. There’s not much else, so it takes little disk and builds very quickly. It covers all registers of all STM32 devices, including core Cortex-M peripherals, and aims to include full enumerated values for each field as soon as possible.
Please consider trying it out and contributing or leaving feedback!
Quick Example
use stm32ral::{read_reg, write_reg, modify_reg, reset_reg};
use stm32ral::{rcc, gpio};
// For safe access we have to first `take()` the peripheral instance.
// This only returns Some(Instance) if that instance is not already
// taken; otherwise it returns None. This ensures that no other code can be
// simultaneously accessing the peripheral, which could lead to a race
// condition. There's `release()` to return it. See below for unsafe use.
let gpioa = gpio::GPIOA::take().unwrap();
let rcc = rcc::RCC::take().unwrap();
// Field-level read/modify/write, with either named values or just literals.
// Most of your code will look like this.
modify_reg!(rcc, rcc, AHB1ENR, GPIOAEN: Enabled);
modify_reg!(gpio, gpioa, MODER, MODER1: Input, MODER2: Output, MODER3: Input);
while read_reg!(gpio, gpioa, IDR, IDR3 == High) {
let pa1 = read_reg!(gpio, gpioa, IDR, IDR1);
modify_reg!(gpio, gpioa, ODR, ODR2: pa1);
}
// You can also reset whole registers or specific fields
reset_reg!(gpio, gpioa, GPIOA, MODER, MODER13, MODER14, MODER15);
reset_reg!(gpio, gpioa, GPIOA, MODER);
// Whole-register read/modify/write.
// Rarely used but nice to have the option.
let port = read_reg!(gpio, gpioa, IDR);
write_reg!(gpio, gpioa, ODR, 0x12345678);
modify_reg!(gpio, gpioa, MODER, |r| r | (0b10 << 4));
// Or forego the macros and just use the constants yourself.
// The macros above just expand to these forms for you, bringing
// the relevant constants into scope. Nothing else is going on.
let pa1 = (gpioa.IDR.read() & gpio::IDR::IDR1::mask) >> gpio::IDR::IDR1::offset;
gpioa.ODR.write(gpio::ODR::ODR2::RW::Output << gpio::ODR::ODR2::offset);
// Once you're done with a peripheral, you can release it so it is available
// to `take()` again. You can't use `gpioa` after this line.
gpio::GPIOA::release(gpioa);
// For unsafe access, you don't need to first call `take()`, just use `GPIOA`:
unsafe { modify_reg!(gpio, GPIOA, MODER, MODER1: Output) };
// With the `nosync` feature set, this is the only way to access registers.
See the example project for a more complete example that should build out of the box.