语言
中文
English
导航菜单
← 返回 嵌入式
#embassy#async#no_std#concurrency
用 Embassy 写异步固件
传统固件是一个大 loop:轮询外设、维护状态机、手写定时。逻辑一复杂,状态机就爆炸。Embassy 把 Rust 的 async/await 带到裸机上——没有操作系统、没有堆,却能把"等一个中断"写成一次 .await。
为什么 async 适合 MCU
固件本质上全是"等待":等 DMA 完成、等定时器、等 GPIO 边沿、等 UART 收满。同步写法要么忙等浪费电,要么手搓回调状态机。async 把这些等待点变成挂起点:任务 .await 时让出 CPU,事件到了再被唤醒。
Embassy 的执行器不需要分配器:任务是编译期已知大小的状态机,静态存放。等待时核心可以进低功耗 wfi,对电池设备至关重要。
一个最小例子
#![no_std]
#![no_main]
use embassy_executor::Spawner;
use embassy_time::{Duration, Timer};
use {defmt_rtt as _, panic_probe as _};
#[embassy_executor::task]
async fn blink(mut led: Output<'static>) {
loop {
led.set_high();
Timer::after(Duration::from_millis(500)).await;
led.set_low();
Timer::after(Duration::from_millis(500)).await;
}
}
#[embassy_executor::main]
async fn main(spawner: Spawner) {
let p = embassy_rp::init(Default::default());
let led = Output::new(p.PIN_25, Level::Low);
spawner.spawn(blink(led)).unwrap();
}
Timer::after(...).await 不是忙等——执行器把这个任务挂起、安排一个硬件定时器中断,然后让核心休眠。500ms 后中断唤醒任务,从 .await 之后接着跑。同样的核心可以同时跑十几个这样的任务而互不阻塞。
多任务与共享状态
Spawner 可以 spawn 多个任务并发跑:
spawner.spawn(blink(led)).unwrap();
spawner.spawn(read_sensor(i2c)).unwrap();
spawner.spawn(handle_uart(uart)).unwrap();
任务间共享数据用 Embassy 的同步原语(都是 no_std 友好的):
Signal<T>:一个任务发、一个任务收的最新值。Channel<T, N>:定长 MPSC 队列。Mutex:async 互斥锁,持锁时仍可让出。
use embassy_sync::signal::Signal;
use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex;
static TEMP: Signal<ThreadModeRawMutex, f32> = Signal::new();
// 生产者
TEMP.signal(reading);
// 消费者
let value = TEMP.wait().await;
与 HAL 的关系
Embassy 为主流芯片提供了自带 async HAL:embassy-rp(RP2040)、embassy-stm32、embassy-nrf。它们的外设 API 直接返回 future——比如 i2c.read(addr, &mut buf).await 在 DMA 期间挂起,而不是阻塞轮询。
心智模型
把 Embassy 想成"给固件用的、零分配的 tokio":
- 写任务,用
.await标出等待点; Spawner把任务交给执行器;- 没有就绪任务时,核心睡觉省电;
- 中断把对应任务标记为就绪、唤醒执行器。
结果是:中断驱动的并发,读起来却像顺序代码——状态机的复杂度交给编译器去生成。这正是 Rust 在嵌入式领域最有说服力的地方之一。
登录
选择一种方式继续
登录即表示你同意我们的服务条款和隐私政策
搜索
输入关键词开始搜索 · 支持中英文 · 按 ⌘K 随时打开