FreeRTOS meets Rust
The Rust ecosystem has made some cool progress on the embedded development front since my last article. Let’s take a look how we can build a much more ergonomical and safe API to one of the more popular real time operating systems - FreeRTOS - with a full testing and continous integration setup.
To get you going, a snippet from a unit test that demonstrates the usage of queues and compute tasks.
// A shared queue
let queue = Arc::new(Queue::new(10).unwrap());
// Task that consumes integers from the shared queue. Returns the
// summed value when a new integer hasn't been posted for 100 ms.
let sum_task = {
let queue = queue.clone();
Task::new().name("sum").compute(move || {
let mut sum = 0;
loop {
if let Ok(val) = queue.receive(Duration::ms(100)) {
sum += val;
} else {
break;
}
}
sum
}).unwrap()
};
// Send the integers to the shared queue
for i in 1..11 {
queue.send(i, Duration::ms(15)).unwrap();
}
// Wait for the compute task to finish the calculation
let sum = sum_task.into_result(Duration::Infinite).unwrap();
// Check the result
assert_eq!(55, sum);
Talking to FreeRTOS
Most of FreeRTOS’ offical API calls are actually macros that abstract various internal function, so we can’t just directly invoke most of the function calls. So we need an inter-op shim written in C that helps us connect Rust and FreeRTOS.
Cross compilation
Previously, we had to manually prepare the whole Rust library ecosystem for non-standard cross compilations targets. Rust’s package manager Cargo is thankfully easily extensible and the xargo helper command does the whole job of obtaining the correct sources and preparing an alternate set of Rust libraries (core
, alloc
and collections
) for us. The final output is a set of object files that can be linked together with the target platform’s linker.
Base firmware
My target board of choice is STM32F4Discovery. ST’s hardware configuration and base firmware generator STM32CubeMX greatly helps with setting all the correct clocks, IO ports, timer and other various peripherals. Using a project converter, we’re able to use GNU ARM GCC to compile the CubeMX generated project.
The final firmware boots up to the main()
written in C, setups the hardware, then hands over the execution to Rust.
Unit tests and continous integration
We’ll make a small compromise by using an emulator instead of the real hardware for continous integration, but it’s an excusable cost cutting for a hobby, open source project. The GNU ARM Eclipse QEMU provides basic emulation of our development board.
The emulator doesn’t implement many peripherals at the moment. For testing interrupts, we emulate a timer-triggered interrupt by spawning a separate FreeRTOS task and triggering the interrupt programatically. Also the FPU registers aren’t emulated fully, so a hack in FreeRTOS’ scheduler was required to omit the FPU registers while switching the context.
But it all makes it worthwhile when Travis serves you a green tick mark.
Standard API parity
Rust’s standard library provides the same primitives for mutual exclusion, threads and queues, so it would make sense to adopt its namespace and semantics to provide easier usage of other Rust libraries. However, I felt that semantics and requirements for embedded are slightly different so a separate namespace made sense. For embedded application code unit testing on the desktop, an alternative implementation that uses Rust’s standard library could come in handy.
Discussion
Setting up the development environment is still a mess. Keeping the code generators, open source IDE like Eclipse, Makefiles and Rust in sync takes a lot of mental gymnastics.
Reimplementing FreeRTOS-like functionality in Rust and assembler is a big undertaking, but the ecosystem will probably provide it in time. The one thing that the community seems to have trouble keeping up with is the chip makers and their drivers for peripherals. So I believe that linking to manufacturer’s HAL libraries, with C shims in between - as C developers really like their macros - will be the norm for the foreseeable future.
Github project: FreeRTOS.rs Cargo crate: freertos_rs Documentation: freertos_rs