Async in Embedded Rust
When I first started this book, I wrote most examples using rp-hal only. In this revision, I focus mainly on async programming with Embassy. Let's understand why async is so valuable in embedded systems.
Imagine You're Cooking Dinner
You are making dinner and you put water on to boil. Instead of standing there watching, you chop vegetables. You glance at the pot occasionally, and when you see bubbles, you're ready for the next step. While the main dish cooks, you prepare a side dish in another pan. You even check a text message on your phone. You're one person constantly moving between tasks, checking what needs attention, doing work whenever something is ready, and never just standing idle waiting.

Async is like multitasking while cooking
That's async programming. You're the executor, constantly deciding what needs attention. Each cooking task is an async operation. The stove does its heating without you watching it - that's the non-blocking wait. You don't freeze in place staring at boiling water. You go do other productive work and come back when it's ready.
Different Approaches
In embedded systems, your microcontroller spends a lot of time waiting. It waits for a button press, for a timer to expire, or for an LED to finish blinking. Without async, you have two main approaches.
Blocking
Your program literally stops and waits. If you're waiting for a button press, your code sits in a loop checking the button state. During this time, your microcontroller can't do anything else. All processor power is wasted asking "is it ready yet?" over and over.
Interrupts
When hardware events happen, interrupt handlers run. This is better because your main code can keep running, but interrupt-based code quickly becomes complex and error-prone. You need to carefully manage shared state between your main code and interrupt handlers.
Async
Async programming gives you the best of both worlds. Your code looks clean and sequential, like blocking code, but it doesn't actually block. When you await something, your code says "I need to wait for this, but feel free to do other work in the meantime."
How Async Works in Rust
When you write an async function in Rust, you use the async keyword before fn. Inside that function, you can use the await keyword on operations that might take time.
async fn blink_led(mut led: Output<'static>) {
loop {
led.set_high();
Timer::after_millis(500).await;
led.set_low();
Timer::after_millis(500).await;
}
}The important part is the .await. When you write Timer::after_millis(500).await, you're telling the runtime "I need to wait 500 milliseconds, but I don't need the CPU during that time." The runtime can then go run other tasks.
Embassy
Embassy is one of the popular async runtimes that makes all of this work in embedded Rust. It provides the executor that manages your tasks and handles hardware interrupts.
The Executor
When you use #[embassy_executor::main], Embassy automatically sets everything up - it runs your tasks, puts the CPU to sleep when everything is waiting, and wakes it up when hardware events occur. The executor is the coordinator that decides which task to poll when.
Embassy for Pico 2 W
For the Pico 2 W, Embassy is especially important because the cyw43 WiFi driver is built on async. All WiFi operations like connecting to networks, sending HTTP requests, and managing the wireless chip are async operations that work seamlessly with Embassy's executor.
#[embassy_executor::main]
async fn main(spawner: Spawner) {
let p = embassy_rp::init(Default::default());
// Multiple tasks running concurrently
spawner.spawn(wifi_task(runner)).unwrap();
spawner.spawn(blink_task(control)).unwrap();
spawner.spawn(button_task(button)).unwrap();
// All three tasks run "at the same time"
// sharing the single CPU efficiently
}Embassy is basically like Tokio for embedded systems. If you're familiar with async Rust on desktop/server, the concepts are the same - just optimized for resource-constrained microcontrollers.
RTIC Alternative
RTIC (Real-Time Interrupt-driven Concurrency) is another popular framework for embedded Rust. Unlike Embassy, which provides an async runtime, RTIC focuses on execution and scheduling with fixed priorities and compile-time resource checking. RTIC is excellent for hard real-time systems where precise timing matters.
In this book, we mainly use Embassy for its async capabilities and WiFi support.