从EXTI实现看Embassy: 异步Rust嵌入式框架

📅 2026/7/27 17:00:05
从EXTI实现看Embassy: 异步Rust嵌入式框架
从EXTI实现看Embassy: 异步Rust嵌入式框架引言为什么需要异步嵌入式框架传统嵌入式开发中处理外部中断如按键触发、传感器信号通常使用中断服务函数ISR。但ISR存在痛点只能执行少量代码、容易导致优先级反转、难以处理多个事件并发。Embassy作为Rust生态中的异步嵌入式框架通过async/await机制让中断处理像普通函数一样简单、安全、可组合。本文将从一个经典场景——外部中断EXTI出发逐步剖析Embassy如何用异步思想重构嵌入式中断处理并给出完整代码示例。## 基础概念EXTI与中断的传统实现外部中断EXTI是单片机检测外部引脚电平变化的常用外设。传统STM32的HAL库中处理EXTI需要1. 配置GPIO为中断模式2. 编写中断服务函数硬编码3. 在ISR中设置标志位主循环轮询这种模式的问题在于中断上下文与任务上下文割裂且多个中断源共享同一优先级时容易冲突。## 进阶Embassy的异步中断模型Embassy的核心思想是将中断处理转化为Future。当外部事件发生时Embassy运行时Executor会唤醒等待该事件的async任务从而在任务上下文中安全地处理数据。### 关键组件-Interrupttrait定义中断使能、清除等操作-ExtiInput封装外部中断输入提供wait_for_high()、wait_for_low()等异步方法-Executor调度器管理所有异步任务## 代码实战一基础EXTI事件监听以下代码展示如何使用Embassy监听一个按键中断每次按下时通过串口打印信息。硬件假设PA0接按键默认高电平按下拉低。rust// 使用nrf52840-dk开发板为例但概念通用#![no_std]#![no_main]use embassy_executor::Spawner;use embassy_nrf::gpio::{Input, Pull, Level, Output, Drive};use embassy_nrf::interrupt;use embassy_time::Timer;use cortex_m_rt::entry;use defmt::*;use {defmt_rtt as _, panic_probe as _};// 定义外部中断输入引脚PA0#[embassy_executor::task]async fn button_task(pin: static mut Inputstatic) { loop { // 等待引脚变为低电平按键按下 pin.wait_for_low().await; info!(Button pressed!); // 简单去抖延时异步不会阻塞其他任务 Timer::after_millis(50).await; }}#[main]async fn main(spawner: Spawner) { let p embassy_nrf::init(Default::default()); // 配置PA0为中断输入内部上拉下降沿触发 let button Input::new( p.P0_00, Pull::Up, // 使用外部中断EXTI通道0对应P0.00 interrupt::take!(EXTI0), ); // 启动按键任务 spawner.spawn(button_task(button)).unwrap(); // 主循环可以处理其他事情 loop { Timer::after_secs(1).await; info!(Main task still alive...); }}代码解析-Input::new()的第三个参数是中断源这里显式绑定EXTI0通道-wait_for_low()是一个异步方法内部通过中断唤醒不会阻塞main任务- 50ms去抖使用了Timer::after_millis它是异步的不会占用CPU## 高级用法多中断源协同与优先级管理真实场景常需同时处理多个中断如按键传感器串口。Embassy允许为每个中断分配不同优先级并通过async任务解耦。以下示例展示两个EXTI通道PA0按键和PA1传感器同时工作且传感器中断优先级更高rustuse embassy_nrf::gpio::{Input, Pull, Level};use embassy_nrf::interrupt::{self, InterruptExt};use embassy_time::Timer;use defmt::*;// 高优先级传感器任务#[embassy_executor::task]async fn sensor_task(pin: static mut Inputstatic) { loop { pin.wait_for_high().await; info!(Sensor triggered! (priority high)); // 模拟耗时处理实际不会阻塞其他高优先级中断 Timer::after_millis(10).await; }}// 低优先级按键任务#[embassy_executor::task]async fn button_task(pin: static mut Inputstatic) { loop { pin.wait_for_low().await; info!(Button pressed (priority low)); Timer::after_millis(50).await; }}#[main]async fn main(spawner: Spawner) { let p embassy_nrf::init(Default::default()); // 配置传感器中断P0.01高优先级 let sensor_int interrupt::take!(EXTI1); sensor_int.set_priority(interrupt::Priority::P2); // 设置为高优先级 let sensor_pin Input::new(p.P0_01, Pull::Down, sensor_int); // 配置按键中断P0.00低优先级 let button_int interrupt::take!(EXTI0); button_int.set_priority(interrupt::Priority::P3); // 设置为低优先级 let button_pin Input::new(p.P0_00, Pull::Up, button_int); // 同时启动两个任务 spawner.spawn(sensor_task(sensor_pin)).unwrap(); spawner.spawn(button_task(button_pin)).unwrap(); // 主循环做低优先级工作 loop { Timer::after_millis(500).await; info!(Main loop...); }}高级特性-set_priority()可动态调整中断优先级Embassy运行时保证高优先级中断先被处理- 每个中断通道独立绑定任务任务之间通过邮箱如Channel通信时Embassy自动处理数据竞争- 所有wait_for_*方法在中断触发时自动唤醒对应任务无需手动轮询## 性能与安全Embassy为何更优1.零开销抽象异步方法编译后等效于状态机无需动态分配内存2.安全并发Rust的所有权系统确保中断上下文与任务上下文不会数据竞争3.可组合性多个中断事件可通过select!宏组合例如rust select! { _ button.wait_for_low() info!(Button), _ sensor.wait_for_high() info!(Sensor), }4.功耗优化Embassy的Timer和wait_for_*均支持进入低功耗模式如WFI指令## 总结从EXTI中断的实现可以看到Embassy通过异步编程模型彻底改变了嵌入式中断处理方式-化被动为主动中断不再需要ISR中的标志位和主循环轮询而是直接表现为可等待的Future-化混乱为有序多个中断源可以像同步函数一样按优先级调度无需手动管理嵌套-化复杂为简单去抖、超时、多事件组合等常见模式用几行异步代码即可实现Embassy的意义不仅在于代码简洁更在于构建了一种可推理、可测试、可复用的嵌入式开发范式。当你下一次面对复杂的中断系统时不妨试试Embassy——它会让你重新定义“嵌入式异步”的可能性。