Learning Rust for Embedded Systems
embeddedrelated.com
embeddedrelated.com
Each HAL implements different things, or the same thing in different ways. I’m in the middle of switching from the stm32f1 i started on to the more capable stm32f4 and it’s been a painful switch. I assume this would also suck in other languages, but it seems fixable in rust. There’s also widely varied support for other MCUs
Lack of emulation. You can emulate an arm cpu in qemu, but there’s no tools for mocking out hardware - which means testing has to happen on device (or you need to fragment your application into qemu-testable pieces)
Shared memory / global initialization is overly complex. If you have a resource (say a gpio pin) that you want to set up in main but use in an interrupt and nowhere else, you have to use like five layers of abstraction to “safely” do that.
after trying to switch to Basic, then Embedded Lua, then MicroPython, then TinyGo... I keep coming back to putting together microcontroller code in C/++, every time
if even one language can break the barrier, there's hope :)
They are generated programmatically from core description files that are only available to STM. They are designed sloppily because a lot of the implementation work is automated. To really progress in this area FOSS developers are going to need to get ahold of these interface specifications.
> Lack of emulation. You can emulate an arm cpu in qemu, but there’s no tools for mocking out hardware - which means testing has to happen on device (or you need to fragment your application into qemu-testable pieces).
This should be OK and I almost prefer it. Get a JTAG or SWD adapter, learn how to use GDB and the self test hardware, and you'll be fine. Modern MCUs are designed for test and can be used in circuit just fine. You can control any part of the chip via the trace macrocell.
> Shared memory / global initialization is overly complex.
Related to my first point.
It's intended for async usage, but take a look at embassy-rs (not yet on crates right now) The embassy contributors put a ton of work into unifying the pac for different versions of the peripherals and called it metapac
I'd really like to write a test that e.g. confirms that if PB13 goes high for longer than 10ms, and event is triggered. Yes, I can run that code on my MCU - but actually testing it requires pressing buttons. With an emulator, I could easily write code to mock out the behavior of external hardware and confirm that my code is handling that input correctly.
> Related to my first point
I'm actually talking about a constraint of Rust - not the HAL or PAC.
Per https://docs.rust-embedded.org/book/concurrency/index.html the canonical way to use a GPIO from an interrupt is something like:
static G_LED: Mutex<RefCell<Option<LEDPIN>>> = Mutex::new(RefCell::new(None));
fn main() {
// get the gpio and pass off to mutex
let mut led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh);
// Move the pin into our global storage
cortex_m::interrupt::free(|cs| *G_LED.borrow(cs).borrow_mut() = Some(led));
}
fn interrupt() {
static mut LED: Option<LEDPIN> = None;
// load the led from the global mutex
let led = LED.get_or_insert_with(|| {
cortex_m::interrupt::free(|cs| {
// Move LED pin here, leaving a None in its place
G_LED.borrow(cs).replace(None).unwrap()
})
});
// actually use the led gpio here
}
If we were genuinely using G_LED from main and from the interrupt, the mutex song-and-dance makes sense, since we want to be careful about how we share use of this resource. But since we just want to set it up in main and use it in the interrupt it's a LOT of confusing overhead/abstraction.To do full mockups you usually need an IO board that can drive pins, but you can also have the trace macrocell jump to code that sets an IO pin's internal driver high and triggers the input side of the IO port.
The other part is the stm32-metapac (not specific to async) that generates the PAC for any stm32 chip.
Read more about embassy here: https://github.com/embassy-rs/embassy
0. Deadlock: https://play.rust-lang.org/?version=stable&mode=debug&editio...
1. Race condition: https://play.rust-lang.org/?version=stable&mode=debug&editio...
Perhaps you were thinking about data races, which are unsafe and therefore not allowed by Safe Rust? The Rustonomicon (https://doc.rust-lang.org/nomicon/races.html) defines data races as:
- two or more threads concurrently accessing a location of memory
- one or more of them is a write
- one or more of them is unsynchronized
Possibly you knew all of this and just got carried away in your enthusiasm for Rust (very much shared!), but lest your readers think Rust is actual magic instead of merely amazing, it might be nice to clarify the article.