embassy: Modern embedded framework, using Rust and async
38 points by marcecoll
38 points by marcecoll
I have used this, it's excellent. I love how I can split event loops.
I was really impressed by the tooling and the fact that it works the same on linux and windows. It'd been awhile since I'd done embedded dev, but last I tried, nothing was consistent and many things required windows only GUI tools. And what CLI tools did exist were quirky and unergonomic.
I’ve done a few projects in Embassy now and it’s great (at least on STM32 and ESP32). It’s also my go-to example of why it’s good that Rust doesn’t have a baked-in async runtime.
There are lots of other great things about embedded Rust too, like defmt and probe-rs. I think my only major complaint is that you can’t turn off optimization, or else the code doesn’t fit on the device and sometimes is even too slow to actually work. With all the generic stuff going on, it really needs to inline everything. Which means breakpoints and single-step can be a little quirky.
Wondering how Embassy's runtime compares with RTIC nowadays – I have used RTIC a couple years ago for some small Teensy projects, its main idea of using interrupts to drive preemptive multitasking was really neat.
(Embassy dev here)
RTIC v2 is basically an async executor, with a design similar to Embassy's:
cortex-m-rt's #[interrupt] macro.What RTIC has that Embassy doesn't is the SRP priority-based resource locks. You can define "resources" that are shared across tasks. When one task locks a resource, RTIC blocks preemption from tasks at priorities equal or lower than the max priority of all tasks that can access that resource. Tasks at higher priorities are still allowed to run since it's guaranteed they won't touch the locked resource. It's a pretty neat system that allows quite fine-grained locking while guaranteeing no deadlocks.
The closest equivalent outside of RTIC is critical_section::Mutex / embassy_sync::blocking_mutex::CriticalSectionMutex. It blocks all preemption at all priority levels. Still guarantees no deadlocks though. It's arguably a special case of the SRP locks with only one priority level, so much less fine-grained.
rtic_sync::Arbiter is equivalent to embassy_sync::Mutex. It's a "classic" async mutex that can deadlock. It's only the SRP locks that can't deadlock, but you can't hold them across an await, so even in RTIC there's cases where you need the Arbiter.
So my recommendation of how to choose is: if you need SRP locks use RTIC. If you don't, use embassy-executor.
Note that you can still use all other Embassy crates with RTIC. They work under any async executor, they're not tied to embassy-executor. So you can e.g use RTIC as executor and embassy-stm32 as HAL.
I haven’t used RTIC, but I think a rough equivalent in Embassy is the InterruptExecutor. Basically hooking an executor to a software interrupt so waking one of its tasks can preempt lower priority executors.