embassy book

Embassy基础应用程序

你已经运行了一些示例,接下来呢?让我们通过一个针对nRF52 DK的简单Embassy应用程序来更好地理解它。

主程序

完整示例可以在这里找到。

> 注意:如果你使用VS Code和rust-analyzer来查看和编辑示例,你可能需要对.vscode/settings.json进行一些修改,以告诉它我们正在使用哪个项目。请按照该文件中的注释说明进行操作,以确保rust-analyzer正常工作。

裸机编程

你首先会注意到文件顶部的两个属性。这些属性告诉编译器程序没有标准库访问权限,也没有main函数(因为它不是由操作系统运行的)。

#![no_std]
#![no_main]

错误处理

接下来是一些关于如何处理panic和故障的声明。在开发过程中,一个好的做法是依赖defmt-rttpanic-probe来将诊断信息打印到终端:

use {defmt_rtt as _, panic_probe as _}; // 全局日志记录器

任务声明

在一些导入声明之后,应该声明应用程序要运行的任务:

#[embassy_executor::task]
async fn blinker(mut led: Output<'static>, interval: Duration) {
    loop {
        led.set_high();
        Timer::after(interval).await;
        led.set_low();
        Timer::after(interval).await;
    }
}

Embassy任务必须声明为async,并且不能带有泛型参数。在这个例子中,我们传入了要闪烁的LED和闪烁间隔。

> 注意:这个任务中没有忙等待。它使用Embassy定时器来让出执行权,允许微控制器在闪烁之间进入睡眠状态。

主函数

Embassy应用程序的主入口点使用#[embassy_executor::main]宏定义。入口点接收一个Spawner参数,可以用它来生成其他任务。

然后我们使用默认配置初始化HAL,这会给我们一个Peripherals结构体,我们可以用它来访问MCU的各种外设。在这个例子中,我们要将其中一个引脚配置为驱动LED的GPIO输出:

#[embassy_executor::main]
async fn main(spawner: Spawner) {
    let p = embassy_nrf::init(Default::default());

    let led = Output::new(p.P0_13, Level::Low, OutputDrive::Standard);
    unwrap!(spawner.spawn(blinker(led, Duration::from_millis(300))));
}

blinker任务被生成并且main返回后会发生什么?实际上,主入口点就像其他任务一样,只是你只能有一个,而且它需要一些特定的类型参数。魔法在于#[embassy_executor::main]宏。这个宏做了以下工作:

  1. 创建一个Embassy执行器
  2. 为入口点定义一个主任务
  3. 运行执行器并生成主任务

还有一种不使用宏来运行执行器的方法,在这种情况下,你需要自己创建Executor实例。

Cargo.toml

项目定义需要包含embassy依赖:

embassy-executor = { version = "0.7.0", path = "../../../embassy-executor", features = ["defmt", "arch-cortex-m", "executor-thread"] }
embassy-time = { version = "0.4.0", path = "../../../embassy-time", features = ["defmt"] }
embassy-nrf = { version = "0.3.1", path = "../../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote"] }

根据你的微控制器,你可能需要将embassy-nrf替换为其他库(例如STM32使用embassy-stm32。记得同时更新特性标志)。

在这个特定的例子中,选择了nrf52840芯片,并使用RTC1外设作为时间驱动程序。