Ramblings of an aging IT geek
← Ramblings of an aging IT geek
rust

blinking an led on an stm32 the hard, satisfying way

First impressions of embedded Rust on a Blue Pill STM32, where the borrow checker turns a peripheral into something you can only configure once.

An STM32 board on a breadboard next to a laptop

I spent the weekend getting Rust running on an STM32F103, the cheap "Blue Pill" board, and the headline is that the hello-world of embedded, blinking an LED, took me an embarrassing amount of setup and was entirely worth it.

The setup is the tax. You need the thumbv7m-none-eabi target, probe-rs to flash over an ST-Link, a memory.x describing the flash and RAM layout, and the right cortex-m-rt and HAL crates wired together. None of it is hard, exactly, but it's all the sort of thing where one wrong line in .cargo/config.toml gets you a linker error that tells you nothing. Budget an evening before you see a single photon.

What makes it click is what happens once it compiles. The stm32f1xx-hal crate models peripherals as owned types, so the GPIO port is a value you split into pins, and configuring a pin as a push-pull output consumes it and hands you back a new type that only exposes the operations that now make sense. You cannot accidentally read from a pin you've configured as an output, because that method doesn't exist on the type you're holding. The classic embedded-C bug of reconfiguring a register from two places simply doesn't compile.

let mut gpioc = dp.GPIOC.split();
let mut led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh);
led.set_low();

into_push_pull_output takes pc13 by value. There is no second copy. The borrow checker, the thing that annoys everyone for their first month of Rust, turns out to be exactly the right tool for a domain where two bits of code touching the same hardware register is a genuine hazard rather than a theoretical one. The LED blinks. More importantly, I trust that it blinks for the reason I think it does.