外部中断
在上节中, 我们在按键状态的示例中,在循环中定时读取引脚状态,为了尽可能及时读取引脚的状态,需要不断去查询,因此需要占用大量的 CPU 时间。那么如何提高效率呢?
举个例子,你在家里需要一直去通过猫眼检查是否有人在你家门口,一旦有客人来了,你能快速快门接客,但是大部分时间你去查看猫眼时候并没人,因此就会耽误你做其他事情。 那么,如果你在门上安装一个门铃,当客人来了后按下门铃,你再去看门,这样就无需反复去看猫眼,你就能从查询的任务中解放出来,去做其他重要的事,更重要的是,其他的事也不会阻碍你去响应门铃的行为,也就是说,实时性也变高了。
外部中断就是一个这样的逻辑,当引脚的电平变化或电平边沿动作时,CPU 将会收到外部中断,然后进入到中断服务函数。
这一过程与 C 嵌入式的流程完全一致!一旦开启中断使能,只要相应的通道中断没有屏蔽,在匹配到变化的信号后,CPU 自动跳转到 Rust 的中断服务函数。
示例
#![no_std] #![no_main] use embedded_hal::digital::v2::InputPin; use hal::exti::ExtiInput; use hal::gpio::{PinPullUpDown, PinSpeed}; use hal::mode::Async; use py32f030_hal as hal; use {defmt_rtt as _, panic_probe as _}; use embassy_executor::Spawner; use embassy_time::Timer; #[embassy_executor::task] async fn run(key: ExtiInput<'static, Async>) { loop { defmt::info!("wating for key push..."); key.wait_for_low().await; defmt::info!("key pushed {}, and wating for key release", key.is_high()); key.wait_for_high().await; defmt::info!("key released"); } } #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = hal::init(Default::default()); let gpioa = p.GPIOF.split(); defmt::info!("Example: embassy exti!"); let key: ExtiInput<_> = ExtiInput::new(gpioa.PF4_BOOT0, PinPullUpDown::No, PinSpeed::Low); _spawner.spawn(run(key)).unwrap(); let mut cnt: u32 = 0; loop { defmt::info!("high {} ", cnt); cnt += 1; Timer::after_secs(5).await; } }
在以上代码中,使用的是异步的方式查询引脚状态,写法与传统的阻塞式编程方式一样,但实际内部则根据中断来唤醒接口。底层驱动如下:
#![allow(unused)] fn main() { use super::types::*; use crate::clock::peripheral::PeripheralInterrupt; use crate::exti::hal::sealed::Instance; use crate::gpio::{AnyPin, GpioPort}; use crate::pac::interrupt; use embassy_sync::waitqueue::AtomicWaker; use core::{future::Future, marker::PhantomData, task::Poll}; const EXIT_GPIO_COUNT: usize = 17; #[allow(clippy::declare_interior_mutable_const)] const ATOMIC_WAKE_CONST: AtomicWaker = AtomicWaker::new(); static EXIT_GPIO_WAKERS: [AtomicWaker; EXIT_GPIO_COUNT] = [ATOMIC_WAKE_CONST; EXIT_GPIO_COUNT]; impl Instance for Exti {} pub(crate) struct Exti; pub struct ExtiInputFuture<'a> { line: Line, edge: Edge, life: PhantomData<&'a mut AnyPin>, } impl<'a> ExtiInputFuture<'a> { pub fn new(port: GpioPort, pin: usize, edge: Edge) -> Self { let line: Line = pin.into(); // line 选择 Exti::exit_channle_select(line, port.into()); critical_section::with(|_| { // 设置上升沿触发条件 Exti::line_ring_edge(line, edge.is_rising()); // 设置下降沿的触发条件 Exti::line_falling_edge(line, edge.is_falling()); // clear pending bit Exti::clear_pending(line); Exti::line_pend_enable(line, true); }); Self { line, edge, life: PhantomData, } } } impl<'d> Future for ExtiInputFuture<'d> { type Output = (); fn poll( self: core::pin::Pin<&mut Self>, cx: &mut core::task::Context<'_>, ) -> core::task::Poll<Self::Output> { if !Exti::is_line_pend_enable(self.line) { Poll::Ready(()) } else { EXIT_GPIO_WAKERS[self.line as usize].register(cx.waker()); self.line.enable_interrupt(); Poll::Pending } } } impl<'d> Drop for ExtiInputFuture<'d> { fn drop(&mut self) { critical_section::with(|_| { if self.edge.is_rising() { Exti::line_ring_edge(self.line, false); } else if self.edge.is_falling() { Exti::line_falling_edge(self.line, false); } // Exit::line_falling_edge(self.line, false); // Exit::line_pend_enable(self.line, false); }) } } #[interrupt] fn EXTI0_1() { critical_section::with(|_cs| unsafe { on_gpio_line_irq(0x03) }) } #[interrupt] fn EXTI2_3() { critical_section::with(|_cs| unsafe { on_gpio_line_irq(0xc0) }) } #[interrupt] fn EXTI4_15() { critical_section::with(|_cs| unsafe { on_gpio_line_irq(0xfff0) }) } unsafe fn on_gpio_line_irq(mask: u32) { let flag = Exti::block().pr.read().bits() & mask; for line in BitIter(flag) { Exti::line_pend_enable(Line::from(line as usize), false); Exti::clear_pending(Line::from(line as usize)); EXIT_GPIO_WAKERS[line as usize].wake(); } } }
在以上代码中,有分别有3个外部中断的服务函数:EXTI0_1
,EXTI2_3
,EXTI4_15
。
当相应的通道触发中断后,CPU 执行中断服务函数,设置唤醒标志,退出后调度器会自动唤醒上层任务继续执行。
详细驱动代码请查看:src/exti/mod.rs
,src/exti/future.rs
,src/exti/hal.rs