一个基础的Embassy应用
你已经运行了 examples ,那么接下来做什么呢?让我们通过一个运行在nRF52 DK上的简单的Embassy例子来更好地了解它。
主要内容
完整的示例可以在 这里 找到
如果您使用VS Code和rust-analyzer查看和编辑示例,您可能需要对 .vscode/settings.json 进行一些更改,以告知它我们正在处理哪个项目。请按照该文件中的注释说明来正确配置rust-analyzer。
|
裸机(Bare metal)
首先,您会注意到文件顶部有两个属性宏。这些属性宏告诉编译器程序无法访问标准库(std),并且没有主函数(因为它不是由操作系统运行的)。
#![no_std]
#![no_main]
处理错误
接下来是一些关于如何处理panic和错误的声明。在开发过程中,一个好的做法是用 defmt-rtt 和 panic-probe 来将诊断信息打印到终端:
use {defmt_rtt as _, panic_probe as _}; // global logger
任务声明
在一些导入语句之后,应该声明应用程序运行的任务:
#[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 timer来让出执行权,允许MCU在每个闪烁之间进入休眠状态。 |
主要部分
Embassy应用程序的主要入口点(entry point)是使用 #[embassy_executor::main] 宏定义的。入口点接收一个 Spawner ,它可以用来生成其他任务。
然后,我们使用默认配置初始化 HAL,这给了我们一个 Peripherals struct , 我们可以用它来访问 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 任务被生成(spawn)而且main函数return时,会发生什么呢? 实际上,main入口点就像任何其他任务一样,但是只能有一个,并且它需要一些特定类型的参数。魔法就在于 #[embassy_executor::main] 宏。该宏执行以下操作:
-
创建一个 Embassy Executor(执行器)
-
为入口点定义一个主任务
-
运行执行器,生成主任务
还有一种不使用宏运行执行器的方法,那就是你来自己创建 Executor 实例。
Cargo.toml
项目需要包含Embassy的依赖项:
embassy-executor = { version = "0.8.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.6.0", path = "../../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote"] }
根据您的MCU,您可能需要把`embassy-nrf`替换成其他的。(STM32: embassy-stm32 ,记住也要更新feature).
在本文这种特定的情况下,我们选择nrf52840芯片,使用RTC1外设作为time driver。