一、Embassy 是什么?
Embassy 是下一代嵌入式应用框架。 它让你用 Rust 语言 + async/await 异步机制 ,更快地写出安全、正确、省电的嵌入式代码。
一句话理解: 传统嵌入式开发用 RTOS(实时操作系统)来管理多任务;Embassy 用 Rust 的异步机制在编译期把任务变成状态机,不需要 RTOS,更快、更小、更省电。
二、核心卖点
1. Rust + async 的优势
| 特性 | 说明 |
|---|---|
| 编译期查错 | Rust 的内存安全和线程安全在编译时就能抓出大量 Bug |
| 无运行时/无GC | 不需要垃圾回收器,适合资源受限的嵌入式环境 |
| 多任务极简 | async/await 让多任务像写同步代码一样简单 |
| 单栈运行 | 不需要为每个任务单独调栈大小,省心 |
| 比RTOS更优 | 更快、更小,不需要内核上下文切换 |
2. 硬件支持(HAL)
README 列出了 Embassy 官方维护的 HAL(硬件抽象层):
| HAL | 支持的芯片 |
|---|---|
| embassy-stm32 | 所有 STM32 系列 |
| embassy-nrf | Nordic nRF52/53/54/91 系列 |
| embassy-rp | 树莓派 RP2040 / RP23xx |
| embassy-mspm0 | 德州仪器 MSPM0 |
| embassy-mcxa | NXP MCX-A 系列 |
| esp-rs / esp-hal | 乐鑫 ESP32 系列 |
| ch32-hal | WCH 32位 RISC-V |
| mpfs-hal | Microchip PolarFire SoC |
| py32-hal | 普冉半导体 PY32 |
| ra-hal | 瑞萨 RA 系列 |
3. 其他关键功能
| 功能 | 说明 |
|---|---|
| embassy-time | 全局可用的时间类型,永不溢出,不用再折腾硬件定时器 |
| 实时就绪 | 支持多优先级执行器,高优先级任务可抢占低优先级 |
| 低功耗就绪 | 无任务时自动休眠,靠中断唤醒,不用忙等轮询 |
| embassy-net | 网络协议栈:以太网、IP、TCP、UDP、ICMP、DHCP |
| 蓝牙 | 通过 trouble、nrf-softdevice、embassy-stm32-wpan 支持 BLE 4.x/5.x |
| LoRa | 通过 lora-rs 项目提供异步 LoRa/LoRaWAN 栈 |
| USB | embassy-usb 实现设备端 USB 栈(CDC ACM、HID等) |
| Bootloader/DFU | embassy-boot 轻量级引导程序,支持断电安全的固件升级、试启动和回滚 |
三、代码示例解读(闪烁LED + 按钮)
README 里的示例展示了 Embassy 的核心用法:
rust
#[embassy_executor::task]
async fn blink(pin: ...) {
let mut led = Output::new(pin, Level::Low, ...);
loop {
led.set_high();
Timer::after_millis(150).await; // 异步等待,不阻塞其他任务
led.set_low();
Timer::after_millis(150).await;
}
}
#[embassy_executor::main]
async fn main(spawner: Spawner) {
let p = embassy_nrf::init(Default::default());
spawner.spawn(blink(p.P0_13.into()).unwrap()); // 后台并发运行
let mut button = Input::new(p.P0_11, Pull::Up);
loop {
button.wait_for_low().await; // 异步等待GPIO事件
info!("Button pressed!");
button.wait_for_high().await;
info!("Button released!");
}
}
关键点:
Timer::after_millis(150).await------ 异步等待,期间 CPU 可以休眠或运行其他任务spawner.spawn()------ 把任务丢到后台并发运行button.wait_for_low().await------ 异步等待 GPIO 事件,不占用 CPU
四、项目信息速查
| 项目 | 内容 |
|---|---|
| 名称由来 | EMB edded ASYnc(嵌入式异步)的缩写 |
| 语言 | Rust |
| 许可证 | MIT 或 Apache-2.0 双许可 |
| MSRV | 保证在最新稳定版 Rust 上编译 |
| 示例目录 | examples/ 按芯片厂商分文件夹(nrf52840、stm32xx、rp等) |
| 运行工具 | probe-rs(用于烧录和调试) |
| 编辑器支持 | Rust Analyzer(VS Code 等) |
五、快速上手
- 安装 probe-rs:参考 https://probe.rs
- 进入示例目录 :
cd examples/nrf52840 - 检查配置 :确认
Cargo.toml和.cargo/config.toml里的芯片名称正确 - 运行示例 :
cargo run --release --bin blinky
总结一句话
Embassy = 用 Rust 的 async/await 重写嵌入式开发范式------它去掉了传统 RTOS 的包袱,用编译期状态机实现多任务,更安全、更省电、更简洁。如果你在用 ESP32 做测试系统或调试工具,Embassy 提供了一个现代、高效的开发路径。
🎯 Embassy 的核心价值
Embassy 是 Rust 嵌入式开发中一个非常核心的异步框架 。它让你能在微控制器上使用 Rust 原生的 async/await 语法来实现并发多任务,而且不需要传统的 RTOS。
它最大的特点是协作式调度 和零成本抽象 。相比 FreeRTOS,Embassy 的 RAM 占用仅为后者的 15% ,上下文切换速度快 5 倍,中断延迟也低得多。这意味着在资源受限的 MCU 上,你能跑更多任务,且响应更及时。
🧩 它包含哪些组件
Embassy 不是一个单一库,而是一套模块化的 crate 集合:
embassy-executor:任务执行器,负责调度异步任务。支持 Cortex-M 架构,并在所有任务等待时自动让 CPU 休眠以省电。- 硬件抽象层 (HAL) :为不同芯片提供安全的 Rust API。官方支持 STM32 (
embassy-stm32)、Nordic nRF (embassy-nrf)、Raspberry Pi RP2040/RP2350 (embassy-rp) 等。 embassy-time:提供异步的定时器和延时功能,例如Timer::after_millis(500).await。embassy-sync:提供任务间通信工具,如Signal(保留最新值)和Channel(固定容量队列)。
💻 代码长什么样
以经典的 LED 闪烁为例,代码结构大致如下:
rust
#![no_std]
#![no_main]
use embassy_executor::Spawner;
use embassy_rp::gpio::{Level, Output};
use embassy_time::Timer;
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let p = embassy_rp::init(Default::default());
let mut led = Output::new(p.PIN_15, Level::Low);
loop {
led.set_high();
Timer::after_millis(500).await; // 异步延时,期间不阻塞 CPU
led.set_low();
Timer::after_millis(500).await;
}
}
关键点在于 Timer::after_millis().await:执行到 .await 时,任务会主动让出 CPU,让执行器去运行其他任务,而不是忙等(busy-wait)。
🚀 如何上手
- 确认硬件支持:去 Embassy 的 GitHub 仓库或官方文档查看你的芯片是否有对应的 HAL 支持。
- 参考官方示例 :Embassy 仓库的
examples/目录下有各芯片的完整项目模板,这是最好的起点。 - 使用项目生成工具 :可以尝试
cargo-embassy(STM32/NRF) 或esp-generate(ESP32) 来快速创建项目脚手架。
LED闪烁实例代码详解
rs
#![no_std]
#![no_main]
use embassy_executor::Spawner;
use embassy_rp::gpio::{Level, Output};
use embassy_time::Timer;
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let p = embassy_rp::init(Default::default());
let mut led = Output::new(p.PIN_15, Level::Low);
loop {
led.set_high();
Timer::after_millis(500).await; // 异步延时,期间不阻塞 CPU
led.set_low();
Timer::after_millis(500).await;
}
}
这段代码是 Embassy 在 RP2040(树莓派 Pico)上最经典的 LED 闪烁程序。下面我逐行拆解,帮你彻底理解每个部分的含义。
📦 1. 属性宏(顶层声明)
rust
#![no_std]
#![no_main]
#![no_std]:告诉编译器不使用标准库(std) 。MCU 资源有限,没有操作系统,标准库中的std::io、std::fs、std::thread等都无法使用。取而代之的是core库,它只提供最基础的类型和 trait。#![no_main]:告诉编译器不使用默认的main入口 。默认的main是 C 运行时(crt0)调用的,依赖操作系统。在裸机上,入口由#[embassy_executor::main]宏生成的代码接管,它负责初始化堆栈、内存、时钟等。
注意:
#是内部属性 ,作用于整个 crate;#[...](不带!)是外部属性,作用于紧随其后的项。
📚 2. 导入依赖
rust
use embassy_executor::Spawner;
use embassy_rp::gpio::{Level, Output};
use embassy_time::Timer;
| 导入项 | 来源 crate | 作用 |
|---|---|---|
Spawner |
embassy-executor |
任务孵化器,用于在运行时动态启动新任务 |
Level |
embassy_rp::gpio |
枚举,表示引脚电平 High / Low |
Output |
embassy_rp::gpio |
配置 GPIO 为推挽输出模式的类型 |
Timer |
embassy_time |
提供异步延时的定时器抽象 |
embassy_rp 是 RP2040 的硬件抽象层(HAL),封装了芯片的寄存器操作。
🎬 3. 程序入口
rust
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
这一行是整个程序最核心的魔法。
-
#[embassy_executor::main]:这是一个属性宏,它会把你的async fn main包装成一个真正的裸机入口。它自动完成:- 设置中断向量表(
vector_table) - 初始化
.bss/.data段(清零未初始化内存、拷贝已初始化数据) - 初始化 RP2040 的时钟和堆
- 创建并运行
embassy-executor的执行器(Executor) - 调用你的
main作为第一个异步任务
- 设置中断向量表(
-
async fn main:主函数是一个异步函数 。.await只能在async上下文中使用,所以入口必须是async。 -
_spawner: Spawner:执行器自动注入的Spawner实例。这里加了下划线前缀,表示暂时不用它 。如果你需要启动其他任务,就可以调用_spawner.spawn(...)。
🔧 4. 初始化外设
rust
let p = embassy_rp::init(Default::default());
embassy_rp::init():这是 RP2040 的统一初始化函数 。它接收一个配置结构体(这里用Default::default()表示使用默认配置),返回一个包含所有外设(Peripherals) 的结构体p。p.PIN_15:p中的字段代表芯片的物理引脚,PIN_15就是 GPIO 15。在树莓派 Pico 上,GPIO 15 通常连接板载 LED(Pico W 除外,它用的是无线芯片的 GPIO)。
这种设计叫 类型状态(Type State) 模式:每个外设只能被初始化一次,编译器会保证你不会重复使用同一个引脚。
💡 5. 配置 LED 引脚
rust
let mut led = Output::new(p.PIN_15, Level::Low);
Output::new(pin, initial_level):把PIN_15配置为推挽输出 ,并设置初始电平为Low(低电平,LED 熄灭)。mut:因为后面要调用led.set_high()/led.set_low()来改变状态,所以需要可变绑定。- 所有权转移 :
p.PIN_15被移动 进了Output对象。从此p.PIN_15不再可用,避免了两个地方同时操作同一个引脚的隐患。
🔁 6. 主循环
rust
loop {
led.set_high();
Timer::after_millis(500).await;
led.set_low();
Timer::after_millis(500).await;
}
loop:无限循环,程序永远不会退出(裸机上也没有"退出"的概念)。led.set_high():拉高 GPIO 15,LED 点亮。Timer::after_millis(500).await:异步延时 500 毫秒 。这是 Embassy 的精髓:- 执行到
.await时,当前任务主动让出 CPU。 - 执行器会把 CPU 切换到其他就绪任务(如果有的话)。
- 如果没有任何任务就绪,RP2040 会进入休眠模式(WFE/睡眠),大幅降低功耗。
- 500ms 后,定时器中断唤醒该任务,从
.await处继续执行。 - 对比传统
busy-wait(死循环空转),这种方式不浪费 CPU 周期。
- 执行到
led.set_low():拉低 GPIO 15,LED 熄灭。- 再延时 500ms,循环往复。
🧠 整体执行流程
上电
↓
crt0 (C 运行时) 启动
↓
#[embassy_executor::main] 生成的入口
↓
初始化内存 / 时钟 / 堆
↓
创建 Executor
↓
运行 main 任务
↓
init() 获取所有外设
↓
配置 PIN_15 为输出
↓
进入 loop:
set_high → await(500ms) → set_low → await(500ms) → ...
🔑 关键概念总结
| 概念 | 说明 |
|---|---|
no_std / no_main |
裸机编程的必要声明 |
#[embassy_executor::main] |
自动生成裸机入口 + 执行器 |
async fn main |
主任务本身是异步的 |
Spawner |
用于启动额外任务的句柄 |
embassy_rp::init() |
一次性获取所有外设 |
Output::new() |
类型状态保证引脚安全 |
.await |
非阻塞让出 CPU,实现协作式并发 |
Timer::after_millis() |
异步定时器,依赖 SysTick 或硬件定时器 |