下一代嵌入式应用框架--Embassy


一、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 等)

五、快速上手

  1. 安装 probe-rs:参考 https://probe.rs
  2. 进入示例目录 :cd examples/nrf52840
  3. 检查配置 :确认 Cargo.toml 和 .cargo/config.toml 里的芯片名称正确
  4. 运行示例 :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)。

🚀 如何上手

  1. 确认硬件支持:去 Embassy 的 GitHub 仓库或官方文档查看你的芯片是否有对应的 HAL 支持。
  2. 参考官方示例 :Embassy 仓库的 examples/ 目录下有各芯片的完整项目模板,这是最好的起点。
  3. 使用项目生成工具 :可以尝试 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 包装成一个真正的裸机入口。它自动完成:

    1. 设置中断向量表(vector_table)
    2. 初始化 .bss / .data 段(清零未初始化内存、拷贝已初始化数据)
    3. 初始化 RP2040 的时钟和堆
    4. 创建并运行 embassy-executor 的执行器(Executor)
    5. 调用你的 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 或硬件定时器