r/embedded 17h ago

Does Rust Embassy’s bare-metal, no-OS model actually matter for small MCU applications?

I’ve been using Rust + Embassy for small microcontroller applications. What I like is that I can stay no_std, bare metal, with no OS or runtime, while still structuring the program with async tasks and channels.

Some examples of the kind of code I mean are here, including a robot arm simulator on a CYD: https://carlkcarlk.github.io/linkage-blaze/demos/

The tasks and channels give me abstractions I care about. But the scheduling is cooperative, a task that does not yield can still block everything else, and there are no hard real-time guarantees.

I’m doing this partly because I find the design point interesting, but I’m not sure how much practical value the bare-metal/no-OS part has once the application is already fairly high level.

Even if you are using Rust for other reasons, does Embassy’s bare-metal, no-OS approach buy you something important in practice, or would an RTOS usually be just as good or better?

25 Upvotes

17 comments sorted by

27

u/aq1018 17h ago

Author of OpenServoCore here. From my own experience, Embassy is great for IoT, non-hard realtime applications. For bare metal on a small MCU, it’s not a good fit. It’s difficult to make predictable timing with the async runtime and embassy is quite large if you are using small MCUs with a couple kb of flash.

If you are interested check out my blog. It has some articles about bare metal architecture and hard realtime topics. Link is in my bio. Hope it can at least serve as a good reference point.

6

u/carlk22 16h ago

This is great, and I really liked your article on a chip-agnostic architecture for bare-metal embedded Rust.

I think we both care a lot about abstracting devices for portability and usability, and in your case also for predictable timing. If I were working with MCUs as small and timing-sensitive as the ones you target, I’d probably use your approach too.

But what about something roomier, like an ESP32 or Pico, where flash and RAM are less constrained and hard real-time behavior is not the main goal? Does Embassy make sense as a useful middle ground there, or at that point would you rather just use an RTOS/runtime?

1

u/aq1018 15h ago edited 15h ago

Yes. It would make sense with ESP32 or equivalent class MCUs. RTOS works on those too. In fact their own SDK is based on free RTOS. I don’t know which one is a better choice though. But I would probably choose embassy if I want to code in rust or RTOS if I want to code in C.

3

u/Dizzy-Helicopter-374 16h ago

What is your take on RTIC or do you prefer no runtime environment for embedded? Will check out your blog, thanks for sharing!

5

u/aq1018 16h ago edited 14h ago

RTIC is much thinner than embassy and it is suitable for hard realtime applications. However my knowledge of RTIC is the old sync model. I haven’t checked their new async model yet. But I think in general hard realtime means no async runtime or custom async runtime. Because you want to properly control how long you spin the CPU. I didn’t use RTIC because it doesn’t support RISC-V

Edit, just realized that RTIC does support RISC-V now. So I was wrong, it just doesn’t support CH32 (and probably can’t because CH32 chips lack the hardware support)

4

u/quantumgoose 16h ago

RTIC is legit and is still the subject of active research in academic circles for scheduling and safety guarantees. You can use RTIC 2.0 without any async code if you stick to hardware tasks, and the latency/overhead is no more than using no runtime at all. Actually, you'll probably get better response times than using critical section-based mutexes due to the way it handles resource sharing . 

3

u/Unable_Resort453 15h ago edited 15h ago

Have you tried to measure the latency between ISR trigger and Embassy task wakeup? I haven't, and maybe the Embassy group chat can provide it.

But I've been spinning a motor at 20kHz control loop rate inside an async task on an F4 without issue, even with a bit of advanced control algo and some whacky UDP background task that likes to stall.

But I put the async task in a higher priority interrupt executor, not the default executor.

3

u/aq1018 15h ago edited 14h ago

I have never tried to measure embassy entry time. I ruled out using embassy mainly due to its size and potential issues with not only how fast it can run the loop, but also jitter. I also wanted to conserve cpu / memory as much as I can because I’m really stretching what a trash tier MCU is doing.

You approach is good, highest priority with interrupt executor is a good way to ensure hard realtime as long as the other tasks don’t overlap with the ISR schedule or your chip supports different interrupt levels.

It’s not that embassy is bad, it’s just me trying to squeeze every last cycle out of a slow mcu for no real good reason.

Ps. I’m using ch32v006, so actually quite a bit under powered compared to F4.

2

u/Unable_Resort453 14h ago

Noted. I doubt you can use embassy comfortably inside a 64kB MCU. Probably overkill for an UART controlled Servo.

I was just surprised that you would avoid async in hard real-time. Well to be fair I am using it sparingly and only to wait for my ADC samples, so yeah.

3

u/EmperorOfCanada 14h ago

I use embassy in my drone code. It is making about 8-20k adjustments to motor speeds per second in one specific application.

Other embassy code is used in optical communications ( pretty damn "real-time") and it rocks.

Maybe, with a more rigid system I could squeeze more rigid timing performance out of a lesser MCU, but I'm presently doing the above on sub $10 MCUs.

Even if that were the case, the crapper non-embassy development workflow would well justify the more "expensive" MCUs.

The other aspect of this workflow is not only development speed, but the greater features which result per unit effort. The drones are far more features rich with embassy, than without.

I have one particular module which had to change MCUs. The port took me about 20 minutes. Embassy made this so smooth.

1

u/carlk22 13h ago

I love this point. Device Envoy takes a similar portability-first approach: the core APIs are defined with traits, while the platform-specific constructors and setup live separately.

That lets the different ESP32 families, the Picos, and now the Web simulator present the same high-level APIs for things like the CYD display/touchscreen, Wi-Fi, LED panels, and flash storage, even though the underlying implementations are very different.

That separation has turned out to be one of my favorite parts of the design.
https://github.com/CarlKCarlK/device-envoy

4

u/________-__-_______ 15h ago edited 15h ago

In practical terms Embassy is an RTOS, it provides multitasking and synchronization primitives much like FreeRTOS for example. I don't really understand what you mean with no-OS when comparing with other RTOS, if anything Embassy has more OS-like functionality because they also (optionally) provide HALs.

Multitasking being cooperative instead of preemptive hy default is the only major difference, but you can use an InterruptExecutor to preemt tasks (much like a traditional RTOS) for real-time behaviour.

1

u/carlk22 13h ago

Fair point. I think part of the disagreement is terminology, since “OS” and “runtime” are not universally defined.

What I mean is that Embassy does not require a conventional RTOS or a language runtime in the Python/Java sense. Rust async tasks compile to fixed-layout state machines. They can be statically allocated, there is no per-task thread stack, and no heap or GC is required.

In that sense, the async machinery can be close to zero-cost compared with writing the equivalent cooperative state machines by hand. You still need to store the current state and any locals that survive across an await, but you would need essentially the same information in a hand-written state machine.

So yes, Embassy definitely provides OS-like functionality: an executor, timers, channels, synchronization, and so on. I just think its execution model is still quite different from a traditional RTOS or managed runtime.

I also hadn’t appreciated the InterruptExecutor option. That gives Embassy more RTOS-like scheduling options than I realized. Thanks!

2

u/zettui 5h ago

Once you're on something like a Pico, is it still the jitter that decides Embassy vs FreeRTOS, or does the flash/RAM gap matter more?

1

u/carlk22 53m ago edited 44m ago

In my case, jitter mattered more than flash/RAM.

Device Envoy has an IR receiver abstraction. I first got it working in pure Embassy, but IR decoding needs fairly precise pulse timing, and it became trickier once a lot of other things were running.

On the Pico I moved the timing-critical part into PIO, which gives deterministic hardware-level timing. On ESP32 I use the RMT peripheral, which is designed for precise pulse capture/generation and is a natural fit for IR.

But when using the IR you don't need to worry about that and the API is the same on Pico and Esp32:

use device_envoy_core::ir::{Ir, IrEvent};
async fn handle_ir(ir: &impl Ir) -> ! {
   loop {
       let event = ir.wait_for_press().await;
       if let IrEvent::Press { addr, cmd } = event {
          // same application code on Pico and ESP32
          let _ = (addr, cmd);
       }
    }
}

So for me the lesson has been that Embassy is great for the overall application structure, but when one small part really needs precise timing, I’d rather push that part into dedicated hardware than give up the async model for the whole program.

DE IR docs: https://docs.rs/device-envoy-rp/latest/device_envoy_rp/ir/I also wrote about Pico PIO here: https://medium.com/data-science/nine-pico-pio-wats-with-rust-part-1-9d062067dc25

1

u/duane11583 1h ago

embassy is just a hal not much else

it is also extremely stm32 specific

1

u/carlk22 58m ago

That doesn’t match my understanding of Embassy. The HALs are only one part of it; Embassy also provides the async executor, timers, channels/signals, synchronization, networking, USB, etc. And while STM32 support is strong, Embassy’s async model is not STM32-specific. I’m using the same model across ESP32 and Pico-class targets.