Bootstrapping support for the STM32WLE with the Embedded Rust ecosystem

Ingmar Jager
November 23, 2020
Last updated: Mar 31, 2023

This year ST released a very interesting new SoC, the STM32WLE. It is packing both an STM32L4 and a Semtech SX1262 LoRa radio die. In one very tiny package.

The problem is, the chip is so new that there is no support or additional resources and examples out there on the internet, whatsoever. Sure (at the time of writing) there is the V1.0 manual from ST. But that’s it. No official devkits yet. No example code. No debugger support.

Still, we were eager to try out this chip. So we designed our own devkit. Which was also exciting since we had never used a 0.5mm pitch bga footprint before. You need < 0.1mm traces and microvias for the fanout. Did you know many PCB shops don’t even go that small?

We also didn’t dare to do the assembly ourselves, so we had the board made and assembled by Weller PCB. Turned out very nicely.

Normally how you test a new board is by flashing a blinky program on it and check whether the LED is blinking. But we don’t have a known working blinky binary for this microcontroller, nor do we have a method to flash that imaginary binary. That will be the challenge to tackle for this article.

Creating a no-lib blinky

I figured the first step is to have a firmware binary that should work once it is flashed. Since I don’t have any chip libraries or peripheral access crates (PACs) yet it needs to be a very low-level program. To just write that and assume it is good is too risky. That’s why I decided to write it for the STM32G070 first. It is an mcu I have already done some projects with, so it easy to test. And the register interface is quite similar to other ST parts. When that works we will adjust the register addresses for the STM32WLE and hope for the best.

And by the way, we don’t really need it to blink, just turning on the led is enough to show that the code and toolchain are working. I also lied about no libraries. Because we will use the cortex_m crate that will provide us with generic startup code that works for any cortex-m chip.

#![no_std]
#![no_main]

use panic_halt as _;
use cortex_m::asm;
use cortex_m_rt::entry;
use core::ptr;

#[entry]
fn main() -> ! {

// stm32g070 nucleo board with led on pin PA5
// We need three registers. Look up the correct addresses in the manual RM454
const RCC_IOPENR: *mut u32 = 0x40021034 as *mut u32;
const GPIOA_MODER: *mut u32 = 0x5000_0000 as *mut u32;
const GPIOA_BSRR: *mut u32 = 0x5000_0018 as *mut u32;

unsafe {
// Enable gpioa peripheral clock
ptr::write_volatile(RCC_IOPENR, 0b1 << 0);

// Set PA5 pin to output mode
// first read out current values so we only change the mode for PA5
let val = ptr::read_volatile(GPIOA_MODER);
let mask = !(0b11 << 10);
let pa5_mode = 0b01 << 10;
ptr::write_volatile(GPIOA_MODER, (val & mask) | pa5_mode);

// Turn on led
ptr::write_volatile(GPIOA_BSRR, 0b1 << 5);
}

loop {
asm::nop();
}

}

Now flash it, but how?

Now that we have a valid firmware binary (for the stm32g0) we need to test the flashing mechanism. We don’t have a working swd debug probe yet for the stm32wle. That’s why I want to try flashing over serial using the vendor bootloader. So let’s first test that with the G0 as well.

I found this slightly outdated open source command line utility stm32flash (on sourgeforge!). I hacked in support for the STM32G070 and STM32WLE by a adding these lines to the device table:

/* ID   "name"       SRAM-address-range      FLASH-address-range    PPS  PSize   Option-byte-addr-range  System-mem-addr-range   Flags */
/* G0 */
{0x460, "STM32G070", 0x20003000, 0x20009000, 0x08000000, 0x08200000, 1, p_2k , 0x1FFF7800, 0x1FFF787F, 0x1FFF0000, 0x1FFF7000, 0},
/* WLE 128k */
{0x497, "STM32WLE" , 0x20002000, 0x20009000, 0x08000000, 0x08200000, 1, p_2k , 0x1FFF7800, 0x1FFF7800, 0x1FFF0000, 0x1FFF7000, 0},

Then flashing it like this.

./stm32flash -w ~/dev/rust/nolib-blinky/target/thumbv7em-none-eabi/debug/nolib-blinky.bin  -v -g 0x0 /dev/cu.usbmodemC1CC90EE3

That worked as expected.

Rinse and repeat for the WLE

Adapt the code to use the correct register addresses and pin numbers (see repo).


#[entry]
fn main() -> ! {
// STM23WLE Led is on PA4 pin
const RCC_AHB2ENR: *mut u32 = 0x5800_004C as *mut u32;
const GPIOA_MODER: *mut u32 = 0x4800_0000 as *mut u32;
const GPIOA_BSRR: *mut u32 = 0x4800_0018 as *mut u32;

unsafe {
// Enable gpioa clock
ptr::write_volatile(RCC_AHB2ENR, 0b1 << 0);

let val = ptr::read_volatile(GPIOA_MODER);
let mask = !(0b11 << 8);
let pa4_mode = 0b01 << 8;

// Set PA4 to output
ptr::write_volatile(GPIOA_MODER, (val & mask) | pa4_mode);

// Turn on led
ptr::write_volatile(GPIOA_BSRR, 0b1 << 4);
}

loop {
for _x in 0..1_000 {
asm::nop();
}

unsafe {
ptr::write_volatile(GPIOA_BSRR, 0b1 << 20);
}

for _x in 0..5_000 {
asm::nop();
}

unsafe {
ptr::write_volatile(GPIOA_BSRR, 0b1 << 4);
}
}
}

Which worked! Nice.

But what is this? I cannot flash the board again. Turns out the uart bootloader is only available if there is no valid firmware or if the firmware sets a specific bit. Looks like it is flash-once then.

SWD support

In order to unbrick this board I need to be able to access it through SWD. Normally we use the Black Magic Probe. However, it does not support such a brand new chip yet. And I am not familiar enough with the codebase to quickly add a new target with potentially a new flashing algorithm. So we need an alternative.

Probe-rs

Recently I encountered Probe-rs. A very promising Rust tool for debugging and flashing. The cool part is that even though it did not support the stm32wl series yet, I could add it by generating the configuration from the family’s CMSIS Pack. So convenient. This configuration describes the flashing algorithm for probe-rs.

Flashing using probe-rs can be done with for example an st-link and the cargo-flash command-line utility (install with cargo install cargo-flash). Then, it is as easy as running the following command inside your rust project.

cargo flash --chip stm32wle5jbix

Yay, the board is unbricked!

Peripheral access crate

So far we can run a very low level (unsafe) firmware. But it would be nice if we didn’t have to look up all those register addresses (which the user manual does not make very easy). Therefore we need a PAC, or a chip library in c-speak.

In the rust stm32 ecosystem, we can also just generate the PAC from the vendor SVD file. So I asked on the ST forums if I could get those, and they kindly provided them. Generating a new crate is as easy as adding a pull request to the stm32-rs repository. And voila, the stm32wl crate is live.

Updating our blinky code

Now that we have this fancy register interface we can update our blinky app.

#![no_std]
#![no_main]

// pick a panicking behavior
use panic_halt as _; // you can put a breakpoint on `rust_begin_unwind` to catch panics

use cortex_m::asm;
use cortex_m_rt::entry;

use stm32wl::stm32wle5 as pac;

#[entry]
fn main() -> ! {
let dp = pac::Peripherals::take().unwrap();

let rcc = dp.RCC;
rcc.ahb2enr.modify(|_, w| w.gpioaen().set_bit());

let gpioa = dp.GPIOA;

gpioa.moder.modify(|_,w| unsafe {
w.moder4().bits(0b01)
});

gpioa.bsrr.write(|w| w.bs4().set_bit());

loop {
for _x in 0..10_000 {
asm::nop();
}
gpioa.bsrr.write(|w| w.bs4().set_bit());

for _x in 0..5_000 {
asm::nop();
}
gpioa.bsrr.write(|w| w.br4().set_bit());
}
}

You can check the project out here on github.

Conclusion

In the end I could have skipped the flashing over uart (stm32flash) step and keep everything within the embedded Rust ecosystem. This shows that Rust might already be the best tool to bootstrap support for a new microcontroller! In C, without vendor chip libraries, this would have taken me ages. I am pretty happy that it went so smoothly because I was afraid this would cost me a lot of time.

Get a free STM32WLE devkit

UPDATE 26 November 2020
We only had 4 and they’re all gone already.

Of course, I didn’t get to the most interesting parts of the STM32WLE yet: the LoRa RF part (the SX126X). Unfortunately, some of our priorities have shifted and we don’t have time right now to work on that part. We have a few spare boards, so if anyone is interested to give it a go, we’re happy to send you one! Just drop us a line at stm32wle-devkit@jitter.company.