embassy 源码解析
embassy 是 Rust 嵌入式的 async 框架:没有堆,没有线程,任务在编译期静态分配;任务等外设时挂起,中断把它叫醒;没有任务要跑时,CPU 执行 WFE 睡眠。仓库根目录的 README 这样介绍它:「Tasks get transformed at compile time into state machines that get run cooperatively. It requires no dynamic memory allocation and runs on a single stack, so no per-task stack size tuning is required.」这几句话分别落在哪段代码上,和轮询主循环、RTOS、RTIC、tokio 有什么不同,见 embassy 解决什么问题。
锁定的版本
embassy 是一个仓库里几十个 crate,每个 crate 各自发版、各自打 tag(<crate>-v<版本>)。本卷按一个新项目从 crates.io 实际解析到的版本锁定,核心 crate 都是 2026-03-20 这一轮发布的:
| crate | 版本 | tag | tag 指向 |
|---|---|---|---|
embassy-executor / embassy-executor-macros | 0.10.0 / 0.8.0 | embassy-executor-v0.10.0 / embassy-executor-macros-v0.8.0 | 84444a19e(2026-03-20) |
embassy-executor-timer-queue | 0.1.0 | 没有 tag,按发布 commit a416aac9c | 2025-08-25 |
embassy-time / embassy-time-driver | 0.5.1 / 0.2.2 | embassy-time-v0.5.1 / embassy-time-driver-v0.2.2 | 84444a19e |
embassy-time-queue-utils | 0.3.2 | embassy-time-queue-utils-v0.3.2 | 58979cace(2026-04-21) |
embassy-sync | 0.8.0 | embassy-sync-v0.8.0 | 84444a19e |
embassy-futures | 0.1.2 | embassy-futures-v0.1.2 | 3e8d8fec1(2025-08-27) |
embassy-hal-internal / embassy-embedded-hal | 0.5.0 / 0.6.0 | 各自的 tag | 84444a19e |
embassy-stm32 | 0.6.0 | embassy-stm32-v0.6.0 | 84444a19e;芯片数据来自 stm32-metapac 21.0.0 |
embassy-net | 0.9.1 | embassy-net-v0.9.1 | 9328e65e0(2026-04-16);底层 smoltcp 0.13.1(tag v0.13.1) |
embassy-net-driver / embassy-net-driver-channel | 0.2.0 / 0.4.0 | 各自的 tag | 35ffdf214(2023-10-18)/ 84444a19e |
embassy-usb / embassy-usb-driver | 0.6.0 / 0.2.2 | 各自的 tag | 84444a19e / 664d4ead3(2026-05-28) |
源码链接一律指向文件所属 crate 的 tag,例如 embassy-time 的文件链到 embassy-time-v0.5.1,哪怕是在执行器的页面上。crates.io 上的包和 tag 是同一份源码,只有两个 Cargo.toml 各差一行,其中一行让新项目里出现两个版本的 embassy-hal-internal,见源码地图。仓库从 2020-09-22 开始,到 84444a19e 共 13720 个提交;之后 main 又走了七个月,executor、time、sync 三个目录有 159 个提交,还没有发版,本卷不跟。
讲什么
- 核心:
embassy-executor(任务存储、运行队列、spawner、各平台的执行器、宏、0.10.0 新增的优先级 / EDF 调度),embassy-time一家(Instant/Duration、驱动接口、定时器队列、Timer/Ticker),embassy-sync(原始互斥、等待队列、channel、signal / watch / pubsub、async mutex),embassy-futures。 - HAL:只讲 embassy-stm32 一个:外设单例、中断绑定、
stm32-metapac与build.rs生成的代码、init与时钟、GPIO 与 EXTI、USART 与 DMA。embassy-hal-internal和embassy-embedded-hal在相应的页里讲。embassy-nrf、embassy-rp 只在设计取舍页对照。 - 网络和 USB:
embassy-net(smoltcp 之上的 async 栈)、embassy-usb(设备栈和类)。 - 不讲:bootloader(embassy-boot)、各 WiFi / 以太网芯片驱动(cyw43、esp-hosted、wiznet 等)、stm32-wpan、其他 HAL、USB host。
三条主线
全卷沿三条主线展开,每页开头标出自己在哪条线上:
- 定时器主线(一次
Timer::after(100ms).await):任务第一次被 poll,Timer把到期时刻和 waker 交给时间驱动 → 驱动把任务挂进定时器队列、设好硬件闹钟 → 执行器无事可做,WFE睡眠 → 闹钟中断 → 队列里到期的任务被唤醒,进运行队列 → 再次 poll,Ready。 - 中断主线(一次
ExtiInput::wait_for_rising_edge().await):bind_interrupts!把 EXTI 中断向量接到 embassy-stm32 的处理函数 → 驱动配置 EXTI 线、登记 waker、打开中断 → 任务挂起 → 中断到来,处理函数屏蔽这条线、唤醒任务 → 任务被 poll,看到线已被屏蔽,返回Ready。 - 跨任务主线(一次
Channel::send/receive):两个任务经embassy-sync的Channel通信,RawMutex保护内部状态,满 / 空时登记 waker,对端操作后唤醒。
每条线的每一步落在哪个函数、哪一页讲,见怎么读这一卷。
flowchart TB intro[00 导读] --> overview[01 全景] overview --> exec[02 执行器] exec --> time[03 时间] time --> sync[04 同步] sync --> hal["05 HAL stm32"] hal --> net[06 网络] hal --> usb[07 USB] net --> design[08 设计取舍] usb --> design design --> pit[09 常见坑] pit --> site[10 在本站中] site --> hands[11 动手]
实验环境
本机没有开发板。关于行为的说法(一次唤醒经过哪些函数、各步多少条指令、报什么错)都配一个小程序和它的真实输出,用三种办法之一:
- std 上跑:
platform-std执行器加embassy-time的std时间驱动,平台是 M2 Max、macOS 27、rustc 1.99.0。 - QEMU 仿真 Cortex-M4:QEMU 11.1.2 的
b-l475e-iot01a(STM32L475VG)。QEMU 没有给 PWR 建模,embassy_stm32::init会在rcc/bd.rs的while !cr.read().dbp() {}死等,所以实验用的 embassy-stm32 注释掉了这一行,其余和 0.6.0 相同;QEMU 也没有 TIM,时间驱动换成一个手写的 1 kHz SysTick 驱动。凡是 QEMU 上的结果都会写明这两点。指令数用-icount得到,可重复;它不是真硬件上的周期数。 - 只交叉编译:
thumbv7em-none-eabihf等目标,看代码体积、.bss里的任务池、build.rs生成的代码。
三种办法分别能回答什么、不能回答什么,见怎么读这一卷。
各部分
- 00 导读:锁定的版本和 tag 规则;三条主线每一步落在哪;三种实验办法与 QEMU 的限制;各 crate 的 feature,哪些互斥;crate 依赖图(
cargo tree核实)和规模。 - 01 全景:裸机上的几种并发写法和 embassy 的位置;一次
Timer::after、一次中断唤醒在 QEMU 上逐步打点走一遍。 - 02 执行器:任务存储、状态与运行队列、Spawner、各平台的执行器、宏、调度策略。
- 03 时间:
Instant与Duration、时间驱动、定时器队列、Timer/Ticker与超时。 - 04 同步:原始互斥、等待队列、
Channel、Signal/Watch/PubSub、asyncMutex与Semaphore、embassy-futures。 - 05 HAL:embassy-stm32:外设单例、中断绑定、芯片数据与代码生成、
init与时钟、GPIO 与 EXTI、USART 与 DMA。 - 06 网络:
Stack与Runner、驱动接口、TCP 与 UDP 套接字。 - 07 USB:设备栈、类。
- 08 设计取舍:静态任务、中断唤醒、链接期符号做插件、
RawMutex类型参数、代码生成覆盖上千款芯片,和 tokio、RTIC 对照。 - 09 常见坑:报错原文做标题,每个都先跑出最小复现。
- 10 在本站中:把本站两个 tokio 后台定时循环搬到 embassy 的 std 执行器上对照。
- 11 动手:不依赖 embassy,约 300 行写一个静态任务池加 SysTick 定时器的 mini 执行器。
适合谁读
- 写过裸机 C 或 RTOS、想知道 async 在没有线程的芯片上怎么跑起来的读者:从导读开始,再读 01 全景 的三页。
- 读过 tokio 卷、想看同一套
Future/Waker换到没有操作系统的环境里变成什么样的读者:先读 embassy 解决什么问题 和一次 Timer::after,最后读 08 设计取舍。 - 需要的前置知识:Rust 的
Future、Poll、Waker;Cargo feature;知道中断、中断向量表、外设寄存器是什么。