embassy book

简介

Embassy 是一个使 async/await 成为嵌入式开发一流选择的项目。

什么是 async?

在处理 I/O 时,软件必须调用阻塞程序执行的函数,直到 I/O 操作完成。当在 Linux 等操作系统中运行时,此类函数通常将控制权转移到内核,以便可以执行另一个任务(称为"线程"),或者可以将 CPU 置于睡眠状态,直到另一个任务准备就绪。

由于操作系统不能假定线程会协同工作,因此线程是相对资源密集型的,如果它们没有在分配的时间内将控制权转移回内核,则可能会被强制中断。如果可以假定任务会协同工作,或者至少不会恶意行为,那么与传统的 OS 线程相比,创建看起来几乎是免费的任务是可能的。

在其他编程语言中,这些轻量级任务被称为"协程"或"goroutines"。在 Rust 中,它们是用 async 实现的。Async-await 的工作原理是将每个异步函数转换为一个称为 future 的对象。当 future 在 I/O 上阻塞时,future 会让步,调度程序(称为执行器)可以选择要执行的其他 future。

与 RTOS 等替代方案相比,async 可以产生更好的性能和更低的功耗,因为执行器不必猜测 future 何时准备好执行。但是,程序大小可能比其他替代方案更高,这对于某些内存非常有限的特定空间受限设备来说可能是个问题。在 Embassy 支持的设备上,例如 stm32 和 nrf,内存通常足够大,可以容纳适度增加的程序大小。

什么是 Embassy?

Embassy 项目由几个可以一起使用或独立使用的 crate 组成:

执行器

embassy-executor 是一个 async/await 执行器,通常执行固定数量的任务,这些任务在启动时分配,尽管以后可以添加更多任务。执行器还可以提供一个系统计时器,您可以将其用于异步和阻塞延迟。对于小于一微秒的情况,应使用阻塞延迟,因为上下文切换的成本太高,并且执行器将无法提供准确的计时。

硬件抽象层

HAL 实现了安全的 Rust API,可让您使用 USART、UART、I2C、SPI、CAN 和 USB 等外设,而无需直接操作寄存器。

Embassy 在有意义的地方提供了异步和阻塞 API 的实现。DMA(直接内存访问)是一个适合异步的示例,而 GPIO 状态更适合阻塞 API。

Embassy 项目为选定的硬件维护 HAL,但您仍然可以将 Embassy 与其他项目中的 HAL 一起使用。

  • embassy-stm32,适用于所有 STM32 微控制器系列。
  • embassy-nrf,适用于 Nordic Semiconductor nRF52、nRF53、nRF91 系列。
  • embassy-rp,适用于 Raspberry Pi RP2040 微控制器。
  • esp-rs,适用于 Espressif Systems ESP32 系列芯片。
  • ch32-hal,适用于 WCH 32 位 RISC-V(CH32V) 系列芯片。

注意:一个常见的问题是是否可以独立使用 Embassy HAL。是的,这是可能的!HAL 中没有对执行器的依赖。您甚至可以在没有 async 的情况下使用它们,因为它们同时实现了 Embedded HAL 阻塞和异步特性。

网络

embassy-net 网络堆栈实现了广泛的网络功能,包括以太网、IP、TCP、UDP、ICMP 和 DHCP。Async drastically simplifies managing timeouts and serving multiple connections concurrently. 可以找到几个用于 WiFi 和以太网芯片的驱动程序。

蓝牙

nrf-softdevice crate 为 nRF52 微控制器提供蓝牙低功耗 4.x 和 5.x 支持。

LoRa

lora-rs 支持在各种 LoRa 无线电上进行 LoRa 组网,并与 Rust LoRaWAN 实现完全集成。它提供了四个 crate -- lora-phy、lora-modulation、lorawan-encoding 和 lorawan-device -- 以及各种开发板的基本示例。它支持 STM32WL 无线微控制器或 Semtech SX127x 收发器等。

USB

embassy-usb 实现了一个设备端 USB 堆栈。提供了 USB 串行 (CDC ACM) 和 USB HID 等常见类的实现,并且丰富的构建器 API 允许您构建自己的实现。

引导加载程序和 DFU

embassy-boot 是一个轻量级引导加载程序,支持以电源故障安全的方式进行固件应用程序升级,并具有试用引导和回滚功能。

什么是 DMA?

对于嵌入式设备中的大多数 I/O,外设并不直接支持一次传输多个字节,CAN 是一个明显的例外。相反,MCU 必须一次写入一个字节,然后等待外设准备好发送下一个字节。对于高 I/O 速率,如果 MCU 必须花费越来越多的时间来处理每个字节,这可能会带来问题。解决此问题的方法是使用直接内存访问控制器。

直接内存访问控制器 (DMA) 是 Embassy 支持的 MCU(包括 stm32 和 nrf)中存在的一种控制器。DMA 允许 MCU 设置传输(发送或接收),然后等待传输完成。使用 DMA,一旦启动,在传输完成之前无需 MCU 干预,这意味着 MCU 可以在传输进行时执行其他计算或设置其他 I/O。对于高 I/O 速率,DMA 可以将 MCU 处理 I/O 的时间减少一半以上。但是,由于 DMA 的设置更为复杂,因此在嵌入式社区中使用较少。Embassy 旨在通过将 DMA 作为首选而不是最后选择来改变这种状况。使用 Embassy,一旦 I/O 速率增加,就不需要额外的调整,因为您的应用程序已经设置为处理它们。

示例

Embassy 为所有支持的 HAL 提供了示例。您可以在 examples/ 文件夹中找到它们。

主循环示例

use embassy_executor::Spawner;
use embassy_time::Timer;
use log::*;

#[embassy_executor::task]
async fn run() {
    loop {
        info!("tick");
        Timer::after_secs(1).await;
    }
}

#[embassy_executor::main]
async fn main(spawner: Spawner) {
    env_logger::builder()
        .filter_level(log::LevelFilter::Debug)
        .format_timestamp_nanos()
        .init();

    spawner.spawn(run()).unwrap();
}

实际应用中的 Embassy

以下是使用 Embassy 的真实项目的已知示例。欢迎 添加更多

资源

有关异步 Rust 和 Embassy 的更多阅读材料:

视频: