Embassy基础应用程序
你已经运行了一些示例,接下来呢?让我们通过一个针对nRF52 DK的简单Embassy应用程序来更好地理解它。
主程序
完整示例可以在这里找到。
> 注意:如果你使用VS Code和rust-analyzer来查看和编辑示例,你可能需要对.vscode/settings.json
进行一些修改,以告诉它我们正在使用哪个项目。请按照该文件中的注释说明进行操作,以确保rust-analyzer正常工作。
裸机编程
你首先会注意到文件顶部的两个属性。这些属性告诉编译器程序没有标准库访问权限,也没有main函数(因为它不是由操作系统运行的)。
#![no_std]
#![no_main]
错误处理
接下来是一些关于如何处理panic和故障的声明。在开发过程中,一个好的做法是依赖defmt-rtt
和panic-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]
宏。这个宏做了以下工作:
- 创建一个Embassy执行器
- 为入口点定义一个主任务
- 运行执行器并生成主任务
还有一种不使用宏来运行执行器的方法,在这种情况下,你需要自己创建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外设作为时间驱动程序。