本文最后更新于 2024年10月21日 中午
Embassy是一个基于Rust的异步嵌入式开发框架。
Embassy
它不仅包含了异步运行时,还提供了STM32、RP2xxx,NRF等的异步HAL实现、Bootloader、usb等。
此外,乐鑫官方的esp-rs 也是将embassy作为默认框架使用。
最近研究了embassy-stm32的部分实现,写在博客里作为记录吧。Exti最简单也有点Async味,就先写这个吧。
注意:Embassy尚未1.0,此文可能在您读的时候已经过时。
文件路径:embassy-stm32\src\exti.rs
从顶向下看。
1 2 3 4 5 6 7 8 9 10 pub struct ExtiInput <'d > { pin: Input<'d >, }
ExtiInput
类型,内部包含了一个Input类型。其实Input类型也是内部包含了一个FlexPin类型。
new函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 impl <'d > ExtiInput<'d > { pub fn new <T: GpioPin>( pin: impl Peripheral <P = T> + 'd , ch: impl Peripheral <P = T::ExtiChannel> + 'd , pull: Pull, ) -> Self { into_ref!(pin, ch); assert_eq! (pin.pin (), ch.number ()); Self { pin: Input::new (pin, pull), } } ...
new函数没啥好说的。impl Peripheral<P = T::ExtiChannel>倒是有点意思。
EXTI的定义在_generated.rs
中由codegen生成的embassy_hal_internal::peripherals_definition!
宏生成。
1 2 3 4 5 6 7 8 9 embassy_hal_internal::peripherals_definition!( ADC1, ... EXTI0, EXTI1, EXTI2, EXTI3, ... )
(_generated.rs)
这些外设信息来自芯片的CubeMX数据库。经过stm32-data 和embassy-stm32宏的层层处理,实现了完善的类型限制和不同型号间高度的代码复用。
1 2 3 4 5 6 7 foreach_pin!( ($pin_name:ident, $port_name:ident, $port_num:expr, $pin_num:expr, $exti_ch:ident) => { impl Pin for peripherals ::$pin_name { #[cfg(feature = "exti" )] type ExtiChannel = peripherals::$exti_ch; } ...
(embassy-stm32\src\gpio.rs)
EXTI的Channel比较简单,一个IO数字对应一个Channel,一个宏就解决问题了。
而相对复杂的IO复用,也是通过codegen和宏实现的,比如:
1 2 3 4 impl_adc_pin!(ADC3, PC2, 12u8 ); impl_adc_pin!(ADC3, PC3, 13u8 ); pin_trait_impl!(crate::can::RxPin, CAN1, PA11, 9u8 ); pin_trait_impl!(crate::can::TxPin, CAN1, PA12, 9u8 );
(_generated.rs)
这种情况下就限制死了alternate function,从而在编译期就能发现问题,而且通过代码提示就能获知可用的IO而不用翻手册。这就是人们希望类型系统所做到的!
wait_for_high, wait_for_rising_edge 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 pub async fn wait_for_high (&mut self ) { let fut = ExtiInputFuture::new (self .pin.pin.pin.pin (), self .pin.pin.pin.port (), true , false ); if self .is_high () { return ; } fut.await } ... pub async fn wait_for_rising_edge (&mut self ) { ExtiInputFuture::new (self .pin.pin.pin.pin (), self .pin.pin.pin.port (), true , false ).await } ...
这个self.pin.pin.pin.pin()有够吐槽的。解释起来是这样的:
ExtiInput.Input.FlexPin.PeripheralRef<AnyPin>.pin()
wait_for_high
或是wait_for_rising_edge
新建了一个ExtiInputFuture
,我们来看看:
1 2 3 4 5 #[must_use = "futures do nothing unless you `.await` or poll them" ] struct ExtiInputFuture <'a > { pin: u8 , phantom: PhantomData<&'a mut AnyPin>, }
ExtiInputFuture并不存储外设实例,而只是寸一个pin_num,这也有利于所有权的编写。实际上,STM32也只有16个Channel嘛。
new和drop 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 fn new (pin: u8 , port: u8 , rising: bool , falling: bool ) -> Self { critical_section::with (|_| { let pin = pin as usize ; exticr_regs ().exticr (pin / 4 ).modify (|w| w.set_exti (pin % 4 , port)); EXTI.rtsr (0 ).modify (|w| w.set_line (pin, rising)); EXTI.ftsr (0 ).modify (|w| w.set_line (pin, falling)); #[cfg(not(any(exti_c0, exti_g0, exti_u0, exti_l5, exti_u5, exti_h5, exti_h50)))] EXTI.pr (0 ).write (|w| w.set_line (pin, true )); #[cfg(any(exti_c0, exti_g0, exti_u0, exti_l5, exti_u5, exti_h5, exti_h50))] { EXTI.rpr (0 ).write (|w| w.set_line (pin, true )); EXTI.fpr (0 ).write (|w| w.set_line (pin, true )); } cpu_regs ().imr (0 ).modify (|w| w.set_line (pin, true )); }); Self { pin, phantom: PhantomData, } } }impl <'a > Drop for ExtiInputFuture <'a > { fn drop (&mut self ) { critical_section::with (|_| { let pin = self .pin as _; cpu_regs ().imr (0 ).modify (|w| w.set_line (pin, false )); }); } }
new函数使用了一个critical_section 。这是一个对整个线程的全局锁。
对于单核的MCU,实现方式是暂时禁用中断就行了。
对于多核,要禁用中断并使用一个spinlock。
new函数初始化Exti中断,就细不看了,最后一行设置了IMR(Interrupt mask register)寄存器,表示未屏蔽(Mask)该位。
impl Future 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 const EXTI_COUNT: usize = 16 ;const NEW_AW: AtomicWaker = AtomicWaker::new ();static EXTI_WAKERS: [AtomicWaker; EXTI_COUNT] = [NEW_AW; EXTI_COUNT]; ... ...impl <'a > Future for ExtiInputFuture <'a > { type Output = (); fn poll (self : Pin<&mut Self >, cx: &mut Context<'_ >) -> Poll<Self ::Output> { EXTI_WAKERS[self .pin as usize ].register (cx.waker ()); let imr = cpu_regs ().imr (0 ).read (); if !imr.line (self .pin as _) { Poll::Ready (()) } else { Poll::Pending } } }
在这里实现了Future trait。
poll函数将waker注册到全局的EXTI_WAKERS中,然后读上面所说的Interrupt mask register来判断该通道是否已经否被屏蔽。Async Driver每次中断后都需要重新建一个Future,所以poll
方法通过判断某位是否被屏蔽来确认是否为此通道。
提一下,AtomicWaker这个底层实现是,有Atomic的情况下用AtomicPtr实现,没有的话用Mutex实现。
中断 绑定 Embassy通过一系列宏将EXTI中断绑定到on_irq上。因为已经有IMR寄存器,所以不用再通过Vector ID确认中断的Channel ID,直接看IMR即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 macro_rules! foreach_exti_irq { ($action:ident) => { foreach_interrupt!( (EXTI0) => { $action!(EXTI0); }; (EXTI1) => { $action!(EXTI1); }; ... (EXTI0_1) => { $action!( EXTI0_1 ); }; (EXTI15_10) => { $action!(EXTI15_10); }; ... ); }; }macro_rules! impl_irq { ($e:ident) => { #[allow(non_snake_case)] #[cfg(feature = "rt" )] #[interrupt] unsafe fn $e () { on_irq () } }; }
on_irq 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 unsafe fn on_irq () { #[cfg(not(any(exti_c0, exti_g0, exti_u0, exti_l5, exti_u5, exti_h5, exti_h50)))] let bits = EXTI.pr (0 ).read ().0 ; #[cfg(any(exti_c0, exti_g0, exti_u0, exti_l5, exti_u5, exti_h5, exti_h50))] let bits = EXTI.rpr (0 ).read ().0 | EXTI.fpr (0 ).read ().0 ; let bits = bits & 0x0000FFFF ; cpu_regs ().imr (0 ).modify (|w| w.0 &= !bits); for pin in BitIter (bits) { EXTI_WAKERS[pin as usize ].wake (); } #[cfg(not(any(exti_c0, exti_g0, exti_u0, exti_l5, exti_u5, exti_h5, exti_h50)))] EXTI.pr (0 ).write_value (Lines (bits)); #[cfg(any(exti_c0, exti_g0, exti_u0, exti_l5, exti_u5, exti_h5, exti_h50))] { EXTI.rpr (0 ).write_value (Lines (bits)); EXTI.fpr (0 ).write_value (Lines (bits)); } #[cfg(feature = "low-power" )] crate::low_power::on_wakeup_irq (); }
on_irq函数首先读了PR(Pending Register)(RPR,FPR,Rising Edge Pending Register,Falling Edge Pending Register),判断出此次需要处理的ExtiChannel。
然后清掉IMR(Interrupt mask register)对应位,防止该通道再次触发。
为了处理多个Channel都触发的情况,Embassy使用了一个BitIter,就在下面。然后迭代并wake对应的waker。
BitIter 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 struct BitIter (u32 );impl Iterator for BitIter { type Item = u32 ; fn next (&mut self ) -> Option <Self ::Item> { match self .0 .trailing_zeros () { 32 => None , b => { self .0 &= !(1 << b); Some (b) } } } }
trailing_zeros方法返回二进制表示中低位连续零的数量。如果所有位都是0则返回32。
然后,self.0 &= !(1 << b)
将低位第一个非零位清零并返回该位的位置。
embedded_hal_async::digital exti.rs还位ExtiInput实现了embedded_hal(略) 和 embedded_hal_async Trait:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 impl <'d > embedded_hal_async::digital::Wait for ExtiInput <'d > { async fn wait_for_high (&mut self ) -> Result <(), Self ::Error> { self .wait_for_high ().await ; Ok (()) } async fn wait_for_low (&mut self ) -> Result <(), Self ::Error> { self .wait_for_low ().await ; Ok (()) } async fn wait_for_rising_edge (&mut self ) -> Result <(), Self ::Error> { self .wait_for_rising_edge ().await ; Ok (()) } async fn wait_for_falling_edge (&mut self ) -> Result <(), Self ::Error> { self .wait_for_falling_edge ().await ; Ok (()) } async fn wait_for_any_edge (&mut self ) -> Result <(), Self ::Error> { self .wait_for_any_edge ().await ; Ok (()) } }
啊,好水的文章啊……