From d12bc9785399991065e511efbea34f0138c7645e Mon Sep 17 00:00:00 2001 From: MathisDerooNXP <52401665+MathisDeroo@users.noreply.github.com> Date: Wed, 26 Nov 2025 10:05:16 -0800 Subject: Add GPIO interrupt support and embedded-hal-async trait implementation (#38) * Add GPIO interrupt support and embedded-hal-async trait implementation Signed-off-by: Mathis Deroo * Run cargo fmt * Improve GPIO driver interrupt mechanism and example - GPIO interrupt managed internally at the HAL level, - Renamed and cleaned gpio_interrupt example; now button_async.rs, - Use BitIter instead of simple for loop in the irq handler, - Fix comments and add "rt" wrappen to GPIO IRQ handler. Signed-off-by: Mathis Deroo * Modify INTERRUPT_DETECTED (AtomicBool to AtomicU32) to work with pin number and not only port number interrupt Signed-off-by: Mathis Deroo * add embedded_hal_async::digital::* traits Signed-off-by: Mathis Deroo * Update irq_handler with BitIter loop Co-authored-by: Felipe Balbi * Add suggested changes Signed-off-by: Mathis Deroo * cargo fmt Signed-off-by: Felipe Balbi * WIP: Modify Wakers from AtomicWaker to WaitMap, with pin number (per PORT) as key Signed-off-by: Mathis Deroo * Tweak maitake-sync usage * Improve docs * refactor a bit * Move all of the async+interrupt stuff into a module * Remove defmt debug traces Signed-off-by: Mathis Deroo * cargo vet * Move e-hal-async impls into the gated block * "rt", begone! --------- Signed-off-by: Mathis Deroo Signed-off-by: Felipe Balbi Co-authored-by: Felipe Balbi Co-authored-by: Felipe Balbi Co-authored-by: Felipe Balbi Co-authored-by: James Munns --- src/config.rs | 2 + src/gpio.rs | 243 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- src/interrupt.rs | 193 ++++++++++++++++++++++++++++++++++++++++++- src/lib.rs | 15 +++- 4 files changed, 450 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/config.rs b/src/config.rs index 9c0d47ecb..8d52a1d44 100644 --- a/src/config.rs +++ b/src/config.rs @@ -9,6 +9,7 @@ pub struct Config { pub time_interrupt_priority: Priority, pub rtc_interrupt_priority: Priority, pub adc_interrupt_priority: Priority, + pub gpio_interrupt_priority: Priority, pub clock_cfg: ClocksConfig, } @@ -19,6 +20,7 @@ impl Default for Config { time_interrupt_priority: Priority::from(0), rtc_interrupt_priority: Priority::from(0), adc_interrupt_priority: Priority::from(0), + gpio_interrupt_priority: Priority::from(0), clock_cfg: ClocksConfig::default(), } } diff --git a/src/gpio.rs b/src/gpio.rs index 4f79aff93..332c4c8b2 100644 --- a/src/gpio.rs +++ b/src/gpio.rs @@ -3,13 +3,96 @@ //! concrete pin type. use core::convert::Infallible; +use core::future::Future; use core::marker::PhantomData; +use core::pin::pin; use embassy_hal_internal::{Peri, PeripheralType}; +use maitake_sync::WaitMap; use paste::paste; +use crate::pac::interrupt; use crate::pac::port0::pcr0::{Dse, Inv, Mux, Pe, Ps, Sre}; +struct BitIter(u32); + +impl Iterator for BitIter { + type Item = usize; + + fn next(&mut self) -> Option { + match self.0.trailing_zeros() { + 32 => None, + b => { + self.0 &= !(1 << b); + Some(b as usize) + } + } + } +} + +const PORT_COUNT: usize = 5; + +static PORT_WAIT_MAPS: [WaitMap; PORT_COUNT] = [ + WaitMap::new(), + WaitMap::new(), + WaitMap::new(), + WaitMap::new(), + WaitMap::new(), +]; + +fn irq_handler(port_index: usize, gpio_base: *const crate::pac::gpio0::RegisterBlock) { + let gpio = unsafe { &*gpio_base }; + let isfr = gpio.isfr0().read().bits(); + + for pin in BitIter(isfr) { + // Clear all pending interrupts + gpio.isfr0().write(|w| unsafe { w.bits(1 << pin) }); + gpio.icr(pin).modify(|_, w| w.irqc().irqc0()); // Disable interrupt + + // Wake the corresponding port waker + if let Some(w) = PORT_WAIT_MAPS.get(port_index) { + w.wake(&pin, ()); + } + } +} + +#[interrupt] +fn GPIO0() { + irq_handler(0, crate::pac::Gpio0::ptr()); +} + +#[interrupt] +fn GPIO1() { + irq_handler(1, crate::pac::Gpio1::ptr()); +} + +#[interrupt] +fn GPIO2() { + irq_handler(2, crate::pac::Gpio2::ptr()); +} + +#[interrupt] +fn GPIO3() { + irq_handler(3, crate::pac::Gpio3::ptr()); +} + +#[interrupt] +fn GPIO4() { + irq_handler(4, crate::pac::Gpio4::ptr()); +} + +pub(crate) unsafe fn init() { + use embassy_hal_internal::interrupt::InterruptExt; + + crate::pac::interrupt::GPIO0.enable(); + crate::pac::interrupt::GPIO1.enable(); + crate::pac::interrupt::GPIO2.enable(); + crate::pac::interrupt::GPIO3.enable(); + crate::pac::interrupt::GPIO4.enable(); + + cortex_m::interrupt::enable(); +} + /// Logical level for GPIO pins. #[derive(Copy, Clone, Eq, PartialEq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -598,6 +681,77 @@ impl<'d> Flex<'d> { } } +/// Async methods +impl<'d> Flex<'d> { + /// Helper function that waits for a given interrupt trigger + async fn wait_for_inner(&mut self, level: crate::pac::gpio0::icr::Irqc) { + // First, ensure that we have a waker that is ready for this port+pin + let w = PORT_WAIT_MAPS[self.pin.port].wait(self.pin.pin); + let mut w = pin!(w); + // Wait for the subscription to occur, which requires polling at least once + // + // This function returns a result, but can only be an Err if: + // + // * We call `.close()` on a WaitMap, which we never do + // * We have a duplicate key, which can't happen because `wait_for_*` methods + // take an &mut ref of their unique port+pin combo + // + // So we wait for it to complete, but ignore the result. + _ = w.as_mut().subscribe().await; + + // Now that our waker is in the map, we can enable the appropriate interrupt + // + // Clear any existing pending interrupt on this pin + self.pin + .gpio() + .isfr0() + .write(|w| unsafe { w.bits(1 << self.pin.pin()) }); + self.pin.gpio().icr(self.pin.pin()).write(|w| w.isf().isf1()); + + // Pin interrupt configuration + self.pin + .gpio() + .icr(self.pin.pin()) + .modify(|_, w| w.irqc().variant(level)); + + // Finally, we can await the matching call to `.wake()` from the interrupt. + // + // Again, technically, this could return a result, but for the same reasons + // as above, this can't be an error in our case, so just wait for it to complete + _ = w.await; + } + + /// Wait until the pin is high. If it is already high, return immediately. + #[inline] + pub fn wait_for_high(&mut self) -> impl Future + use<'_, 'd> { + self.wait_for_inner(crate::pac::gpio0::icr::Irqc::Irqc12) + } + + /// Wait until the pin is low. If it is already low, return immediately. + #[inline] + pub fn wait_for_low(&mut self) -> impl Future + use<'_, 'd> { + self.wait_for_inner(crate::pac::gpio0::icr::Irqc::Irqc8) + } + + /// Wait for the pin to undergo a transition from low to high. + #[inline] + pub fn wait_for_rising_edge(&mut self) -> impl Future + use<'_, 'd> { + self.wait_for_inner(crate::pac::gpio0::icr::Irqc::Irqc9) + } + + /// Wait for the pin to undergo a transition from high to low. + #[inline] + pub fn wait_for_falling_edge(&mut self) -> impl Future + use<'_, 'd> { + self.wait_for_inner(crate::pac::gpio0::icr::Irqc::Irqc10) + } + + /// Wait for the pin to undergo any transition, i.e low to high OR high to low. + #[inline] + pub fn wait_for_any_edge(&mut self) -> impl Future + use<'_, 'd> { + self.wait_for_inner(crate::pac::gpio0::icr::Irqc::Irqc11) + } +} + /// GPIO output driver that owns a `Flex` pin. pub struct Output<'d> { flex: Flex<'d>, @@ -691,12 +845,99 @@ impl<'d> Input<'d> { self.flex } - // Get the pin level. + /// Get the pin level. pub fn get_level(&self) -> Level { self.flex.get_level() } } +/// Async methods +impl<'d> Input<'d> { + /// Wait until the pin is high. If it is already high, return immediately. + #[inline] + pub fn wait_for_high(&mut self) -> impl Future + use<'_, 'd> { + self.flex.wait_for_high() + } + + /// Wait until the pin is low. If it is already low, return immediately. + #[inline] + pub fn wait_for_low(&mut self) -> impl Future + use<'_, 'd> { + self.flex.wait_for_low() + } + + /// Wait for the pin to undergo a transition from low to high. + #[inline] + pub fn wait_for_rising_edge(&mut self) -> impl Future + use<'_, 'd> { + self.flex.wait_for_rising_edge() + } + + /// Wait for the pin to undergo a transition from high to low. + #[inline] + pub fn wait_for_falling_edge(&mut self) -> impl Future + use<'_, 'd> { + self.flex.wait_for_falling_edge() + } + + /// Wait for the pin to undergo any transition, i.e low to high OR high to low. + #[inline] + pub fn wait_for_any_edge(&mut self) -> impl Future + use<'_, 'd> { + self.flex.wait_for_any_edge() + } +} + +impl embedded_hal_async::digital::Wait for Input<'_> { + 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(()) + } +} + +impl embedded_hal_async::digital::Wait for Flex<'_> { + 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(()) + } +} + // Both embedded_hal 0.2 and 1.0 must be supported by embassy HALs. impl embedded_hal_02::digital::v2::InputPin for Flex<'_> { // GPIO operations on this block cannot fail, therefor we set the error type diff --git a/src/interrupt.rs b/src/interrupt.rs index f2f1cccac..0490e3a66 100644 --- a/src/interrupt.rs +++ b/src/interrupt.rs @@ -8,7 +8,8 @@ mod generated { embassy_hal_internal::interrupt_mod!( - OS_EVENT, LPUART0, LPI2C0, LPI2C1, LPI2C2, LPI2C3, LPUART1, LPUART2, LPUART3, LPUART4, LPUART5, RTC, ADC1, + OS_EVENT, RTC, ADC1, GPIO0, GPIO1, GPIO2, GPIO3, GPIO4, LPI2C0, LPI2C1, LPI2C2, LPI2C3, LPUART0, LPUART1, + LPUART2, LPUART3, LPUART4, LPUART5, ); } @@ -292,6 +293,196 @@ impl InterruptExt for Adc { } } +pub struct Gpio0; +pub const GPIO0: Gpio0 = Gpio0; + +impl InterruptExt for Gpio0 { + /// Clear any pending GPIO0 in NVIC. + #[inline] + fn unpend(&self) { + cortex_m::peripheral::NVIC::unpend(Interrupt::GPIO0); + } + + /// Set NVIC priority for GPIO0. + #[inline] + fn set_priority(&self, priority: Priority) { + unsafe { + let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; + nvic.set_priority(Interrupt::GPIO0, u8::from(priority)); + } + } + + /// Enable GPIO0 in NVIC. + #[inline] + unsafe fn enable(&self) { + cortex_m::peripheral::NVIC::unmask(Interrupt::GPIO0); + } + + /// Disable GPIO0 in NVIC. + #[inline] + unsafe fn disable(&self) { + cortex_m::peripheral::NVIC::mask(Interrupt::GPIO0); + } + + /// Check if GPIO0 is pending in NVIC. + #[inline] + fn is_pending(&self) -> bool { + cortex_m::peripheral::NVIC::is_pending(Interrupt::GPIO0) + } +} + +pub struct Gpio1; +pub const GPIO1: Gpio1 = Gpio1; + +impl InterruptExt for Gpio1 { + /// Clear any pending GPIO1 in NVIC. + #[inline] + fn unpend(&self) { + cortex_m::peripheral::NVIC::unpend(Interrupt::GPIO1); + } + + /// Set NVIC priority for GPIO1. + #[inline] + fn set_priority(&self, priority: Priority) { + unsafe { + let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; + nvic.set_priority(Interrupt::GPIO1, u8::from(priority)); + } + } + + /// Enable GPIO1 in NVIC. + #[inline] + unsafe fn enable(&self) { + cortex_m::peripheral::NVIC::unmask(Interrupt::GPIO1); + } + + /// Disable GPIO1 in NVIC. + #[inline] + unsafe fn disable(&self) { + cortex_m::peripheral::NVIC::mask(Interrupt::GPIO1); + } + + /// Check if GPIO1 is pending in NVIC. + #[inline] + fn is_pending(&self) -> bool { + cortex_m::peripheral::NVIC::is_pending(Interrupt::GPIO1) + } +} + +pub struct Gpio2; +pub const GPIO2: Gpio2 = Gpio2; + +impl InterruptExt for Gpio2 { + /// Clear any pending GPIO2 in NVIC. + #[inline] + fn unpend(&self) { + cortex_m::peripheral::NVIC::unpend(Interrupt::GPIO2); + } + + /// Set NVIC priority for GPIO2. + #[inline] + fn set_priority(&self, priority: Priority) { + unsafe { + let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; + nvic.set_priority(Interrupt::GPIO2, u8::from(priority)); + } + } + + /// Enable GPIO2 in NVIC. + #[inline] + unsafe fn enable(&self) { + cortex_m::peripheral::NVIC::unmask(Interrupt::GPIO2); + } + + /// Disable GPIO2 in NVIC. + #[inline] + unsafe fn disable(&self) { + cortex_m::peripheral::NVIC::mask(Interrupt::GPIO2); + } + + /// Check if GPIO2 is pending in NVIC. + #[inline] + fn is_pending(&self) -> bool { + cortex_m::peripheral::NVIC::is_pending(Interrupt::GPIO2) + } +} + +pub struct Gpio3; +pub const GPIO3: Gpio3 = Gpio3; + +impl InterruptExt for Gpio3 { + /// Clear any pending GPIO3 in NVIC. + #[inline] + fn unpend(&self) { + cortex_m::peripheral::NVIC::unpend(Interrupt::GPIO3); + } + + /// Set NVIC priority for GPIO3. + #[inline] + fn set_priority(&self, priority: Priority) { + unsafe { + let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; + nvic.set_priority(Interrupt::GPIO3, u8::from(priority)); + } + } + + /// Enable GPIO3 in NVIC. + #[inline] + unsafe fn enable(&self) { + cortex_m::peripheral::NVIC::unmask(Interrupt::GPIO3); + } + + /// Disable GPIO3 in NVIC. + #[inline] + unsafe fn disable(&self) { + cortex_m::peripheral::NVIC::mask(Interrupt::GPIO3); + } + + /// Check if GPIO3 is pending in NVIC. + #[inline] + fn is_pending(&self) -> bool { + cortex_m::peripheral::NVIC::is_pending(Interrupt::GPIO3) + } +} + +pub struct Gpio4; +pub const GPIO4: Gpio4 = Gpio4; + +impl InterruptExt for Gpio4 { + /// Clear any pending GPIO4 in NVIC. + #[inline] + fn unpend(&self) { + cortex_m::peripheral::NVIC::unpend(Interrupt::GPIO4); + } + + /// Set NVIC priority for GPIO4. + #[inline] + fn set_priority(&self, priority: Priority) { + unsafe { + let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; + nvic.set_priority(Interrupt::GPIO4, u8::from(priority)); + } + } + + /// Enable GPIO4 in NVIC. + #[inline] + unsafe fn enable(&self) { + cortex_m::peripheral::NVIC::unmask(Interrupt::GPIO4); + } + + /// Disable GPIO4 in NVIC. + #[inline] + unsafe fn disable(&self) { + cortex_m::peripheral::NVIC::mask(Interrupt::GPIO4); + } + + /// Check if GPIO4 is pending in NVIC. + #[inline] + fn is_pending(&self) -> bool { + cortex_m::peripheral::NVIC::is_pending(Interrupt::GPIO4) + } +} + /// Set VTOR (Vector Table Offset) to a RAM-based vector table. /// Pass a pointer to the first word in the RAM table (stack pointer slot 0). /// Safety: Caller must ensure the RAM table is valid and aligned as required by the core. diff --git a/src/lib.rs b/src/lib.rs index 7fccc86c5..fb204d27b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,7 +18,6 @@ pub mod lpuart; pub mod ostimer; pub mod rtc; -#[cfg(feature = "rt")] pub use crate::pac::NVIC_PRIO_BITS; #[rustfmt::skip] @@ -347,10 +346,24 @@ pub fn init(cfg: crate::config::Config) -> Peripherals { crate::interrupt::RTC.set_priority(cfg.rtc_interrupt_priority); // Apply user-configured priority early; enabling is left to examples/apps crate::interrupt::ADC1.set_priority(cfg.adc_interrupt_priority); + // Apply user-configured priority early; enabling is left to examples/apps + crate::interrupt::GPIO0.set_priority(cfg.gpio_interrupt_priority); + // Apply user-configured priority early; enabling is left to examples/apps + crate::interrupt::GPIO1.set_priority(cfg.gpio_interrupt_priority); + // Apply user-configured priority early; enabling is left to examples/apps + crate::interrupt::GPIO2.set_priority(cfg.gpio_interrupt_priority); + // Apply user-configured priority early; enabling is left to examples/apps + crate::interrupt::GPIO3.set_priority(cfg.gpio_interrupt_priority); + // Apply user-configured priority early; enabling is left to examples/apps + crate::interrupt::GPIO4.set_priority(cfg.gpio_interrupt_priority); // Configure clocks crate::clocks::init(cfg.clock_cfg).unwrap(); + unsafe { + crate::gpio::init(); + } + // Initialize embassy-time global driver backed by OSTIMER0 #[cfg(feature = "time")] crate::ostimer::time_driver::init(crate::config::Config::default().time_interrupt_priority, 1_000_000); -- cgit