diff --git a/Cargo.toml b/Cargo.toml index 92f18eb..d962f6e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,8 +76,9 @@ embedded-hal = "1.0.0" defmt = { version = "1.0.0", optional = true } paste = "1.0.15" log = { version = "0.4.20", optional = true} -futures-util = { version = "0.3", default-features = false, features = ["async-await-macro"], optional = true} +futures-util = { version = "0.3", default-features = false, features = ["async-await-macro"], optional = true } stm32-usbd = "0.8.0" +embedded-hal-async = "1.0.0" [dev-dependencies] log = { version = "0.4.20"} @@ -95,6 +96,7 @@ panic-probe = "0.3.2" panic-semihosting = "0.6" usbd-serial = "0.2.2" usb-device = { version = "0.3.2", features = ["defmt", "log"] } +rtic = { version = "2.2", features = ["thumbv8main-backend"] } [profile.release] codegen-units = 1 # better optimizations @@ -116,3 +118,7 @@ required-features = ["stm32h503"] [[example]] name = "i2c_target_manual_ack" required-features = ["stm32h503"] + +[[example]] +name = "await-pins" +required-features = ["futures"] diff --git a/examples/await-pins.rs b/examples/await-pins.rs new file mode 100644 index 0000000..78e37a7 --- /dev/null +++ b/examples/await-pins.rs @@ -0,0 +1,60 @@ +#![deny(warnings)] +#![no_main] +#![no_std] + +mod utilities; +use embedded_hal_async::digital::Wait; +use stm32h5xx_hal::{ + gpio::{ExtiExt, ExtiPin}, + pac, + prelude::*, +}; +use utilities::logger::info; + +#[rtic::app(device = pac, dispatchers = [USART1, USART2], peripherals = true)] +mod app { + use stm32h5xx_hal::gpio; + + use super::*; + + #[shared] + struct Shared {} + + #[local] + struct Local { + pin: gpio::PA5, + } + + #[init] + fn init(ctx: init::Context) -> (Shared, Local) { + let dp = ctx.device; + + let pwr = dp.PWR.constrain(); + let pwrcfg = pwr.vos0().freeze(); + + // Constrain and Freeze clock + let rcc = dp.RCC.constrain(); + let ccdr = rcc.sys_ck(250.MHz()).freeze(pwrcfg, &dp.SBS); + + let gpioa = dp.GPIOA.split(ccdr.peripheral.GPIOA); + + let (mut exti, exti_channels) = dp.EXTI.split(); + + let pin = gpioa + .pa5 + .into_input() + .make_interrupt_source(exti_channels.ch5, &mut exti); + + (Shared {}, Local { pin }) + } + + #[task(local = [pin])] + async fn foo(ctx: foo::Context) { + loop { + ctx.local.pin.wait_for_high().await.unwrap(); + info!("On"); + ctx.local.pin.wait_for_low().await.unwrap(); + info!("Off"); + } + } +} diff --git a/src/gpio.rs b/src/gpio.rs index fd92cc0..10cb8f6 100644 --- a/src/gpio.rs +++ b/src/gpio.rs @@ -58,6 +58,9 @@ mod convert; mod dynamic; mod erased; mod exti; + +#[cfg(feature = "futures")] +mod future; mod gpio_def; mod hal; mod partially_erased; @@ -71,7 +74,7 @@ pub use dynamic::{Dynamic, DynamicPin}; pub use embedded_hal::digital::PinState; pub use erased::{EPin, ErasedPin}; -pub use exti::ExtiPin; +pub use exti::{ExtiExt, ExtiPin, ExtiedPin}; pub use gpio_def::*; pub use partially_erased::{PEPin, PartiallyErasedPin}; @@ -264,10 +267,16 @@ pub type AF15 = Alternate<15, Otype>; /// - `MODE` is one of the pin modes (see [Modes](crate::gpio#modes) section). /// - `P` is port name: `A` for GPIOA, `B` for GPIOB, etc. /// - `N` is pin number: from `0` to `15`. -pub struct Pin { +/// - `INT` is true of the pin is configured for EXTI interrupt +pub struct Pin< + const P: char, + const N: u8, + MODE = Analog, + const INT: bool = false, +> { _mode: PhantomData, } -impl Pin { +impl Pin { const fn new() -> Self { Self { _mode: PhantomData } } @@ -412,7 +421,7 @@ impl From> } } -impl Pin { +impl Pin { /// Set the output of the pin regardless of its mode. /// Primarily used to set the output value of the pin /// before changing its mode to an output to avoid diff --git a/src/gpio/exti.rs b/src/gpio/exti.rs index cc68ba3..f4dd102 100644 --- a/src/gpio/exti.rs +++ b/src/gpio/exti.rs @@ -1,5 +1,72 @@ +use core::{marker::PhantomData, ops::Deref}; + use super::{marker, Edge, Pin, PinExt}; -use crate::pac::{Interrupt, EXTI}; +use crate::{ + gpio, + pac::{Interrupt, EXTI}, +}; + +pub trait ExtiExt { + fn split(self) -> (Exti, ExtiChannels); +} + +impl ExtiExt for EXTI { + fn split(self) -> (Exti, ExtiChannels) { + ( + Exti(self), + ExtiChannels { + ch0: ExtiChannel, + ch1: ExtiChannel, + ch2: ExtiChannel, + ch3: ExtiChannel, + ch4: ExtiChannel, + ch5: ExtiChannel, + ch6: ExtiChannel, + ch7: ExtiChannel, + ch8: ExtiChannel, + ch9: ExtiChannel, + ch10: ExtiChannel, + ch11: ExtiChannel, + ch12: ExtiChannel, + ch13: ExtiChannel, + ch14: ExtiChannel, + ch15: ExtiChannel, + }, + ) + } +} + +pub struct Exti(pub(crate) EXTI); + +impl Deref for Exti { + type Target = EXTI; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[non_exhaustive] +pub struct ExtiChannel; + +pub struct ExtiChannels { + pub ch0: ExtiChannel<0>, + pub ch1: ExtiChannel<1>, + pub ch2: ExtiChannel<2>, + pub ch3: ExtiChannel<3>, + pub ch4: ExtiChannel<4>, + pub ch5: ExtiChannel<5>, + pub ch6: ExtiChannel<6>, + pub ch7: ExtiChannel<7>, + pub ch8: ExtiChannel<8>, + pub ch9: ExtiChannel<9>, + pub ch10: ExtiChannel<10>, + pub ch11: ExtiChannel<11>, + pub ch12: ExtiChannel<12>, + pub ch13: ExtiChannel<13>, + pub ch14: ExtiChannel<14>, + pub ch15: ExtiChannel<15>, +} impl Pin { /// NVIC interrupt number of interrupt from this pin @@ -30,25 +97,38 @@ impl Pin { } /// External Interrupt Pin -pub trait ExtiPin { - fn make_interrupt_source(&mut self, exti: &mut EXTI); - fn trigger_on_edge(&mut self, exti: &mut EXTI, level: Edge); - fn enable_event(&mut self, exti: &mut EXTI); - fn disable_event(&mut self, exti: &mut EXTI); - fn enable_interrupt(&mut self, exti: &mut EXTI); - fn disable_interrupt(&mut self, exti: &mut EXTI); +pub trait ExtiPin { + fn make_interrupt_source( + self, + _ch: ExtiChannel, + ch: &mut Exti, + ) -> Pin; +} + +// TODO: Find better name +/// Only available on pins where interrupts have been enabled by the user +pub trait ExtiedPin { + fn trigger_on_edge(&mut self, exti: &mut Exti, level: Edge); + fn enable_event(&mut self, exti: &mut Exti); + fn disable_event(&mut self, exti: &mut Exti); + fn enable_interrupt(&mut self, exti: &mut Exti); + fn disable_interrupt(&mut self, exti: &mut Exti); fn clear_interrupt_pending_bit(&mut self, edge: Edge); fn check_interrupt(&self, edge: Edge) -> bool; } -impl ExtiPin for PIN +impl ExtiPin + for gpio::Pin where - PIN: PinExt, - PIN::Mode: marker::Interruptable, + M: marker::Interruptable, { /// Make corresponding EXTI line sensitive to this pin #[inline(always)] - fn make_interrupt_source(&mut self, exti: &mut EXTI) { + fn make_interrupt_source( + self, + _ch: ExtiChannel, + exti: &mut Exti, + ) -> Pin { let i = self.pin_id(); let port = self.port_id() as u32; let offset = 8 * (i % 4); @@ -75,12 +155,16 @@ where } _ => unreachable!(), } + + Pin { _mode: PhantomData } } +} +impl ExtiedPin for gpio::Pin { /// Generate interrupt on rising edge, falling edge or both #[inline(always)] - fn trigger_on_edge(&mut self, exti: &mut EXTI, edge: Edge) { - let i = self.pin_id(); + fn trigger_on_edge(&mut self, exti: &mut Exti, edge: Edge) { + let i = N; match edge { Edge::Rising => { exti.rtsr1() @@ -105,30 +189,30 @@ where /// Enable external interrupts from this pin. #[inline(always)] - fn enable_event(&mut self, exti: &mut EXTI) { + fn enable_event(&mut self, exti: &mut Exti) { exti.emr1() - .modify(|r, w| unsafe { w.bits(r.bits() | (1 << self.pin_id())) }); + .modify(|r, w| unsafe { w.bits(r.bits() | (1 << N)) }); } /// Disable external interrupts from this pin #[inline(always)] - fn disable_event(&mut self, exti: &mut EXTI) { + fn disable_event(&mut self, exti: &mut Exti) { exti.emr1() - .modify(|r, w| unsafe { w.bits(r.bits() & !(1 << self.pin_id())) }); + .modify(|r, w| unsafe { w.bits(r.bits() & !(1 << N)) }); } /// Enable external interrupts from this pin. #[inline(always)] - fn enable_interrupt(&mut self, exti: &mut EXTI) { + fn enable_interrupt(&mut self, exti: &mut Exti) { exti.imr1() - .modify(|r, w| unsafe { w.bits(r.bits() | (1 << self.pin_id())) }); + .modify(|r, w| unsafe { w.bits(r.bits() | (1 << N)) }); } /// Disable external interrupts from this pin #[inline(always)] - fn disable_interrupt(&mut self, exti: &mut EXTI) { + fn disable_interrupt(&mut self, exti: &mut Exti) { exti.imr1() - .modify(|r, w| unsafe { w.bits(r.bits() & !(1 << self.pin_id())) }); + .modify(|r, w| unsafe { w.bits(r.bits() & !(1 << N)) }); } /// Clear the interrupt pending bit for this pin @@ -137,7 +221,7 @@ where unsafe { let exti = &(*EXTI::ptr()); - let mask = 1 << self.pin_id(); + let mask = 1 << N; match edge { Edge::Rising => exti.rpr1().write(|w| w.bits(mask)), Edge::Falling => exti.fpr1().write(|w| w.bits(mask)), @@ -158,7 +242,7 @@ where _ => panic!("Must choose a rising or falling edge"), }; - bits & (1 << self.pin_id()) != 0 + bits & (1 << N) != 0 } } } diff --git a/src/gpio/future.rs b/src/gpio/future.rs new file mode 100644 index 0000000..6fe2fb8 --- /dev/null +++ b/src/gpio/future.rs @@ -0,0 +1,152 @@ +use core::{convert::Infallible, future::Future, task::Poll}; + +use crate::{ + gpio::{exti::Exti, Edge, Input, Pin}, + interrupt, +}; +use futures_util::task::AtomicWaker; + +use crate::pac::EXTI; + +static WAKERS: [AtomicWaker; 16] = [const { AtomicWaker::new() }; 16]; + +macro_rules! impl_irq { + ($($INT:ident => $rpifX:ident, $fpifX:ident, $imX:ident, $i:literal),*) => {$( + #[interrupt] + fn $INT() { + let exti = unsafe { EXTI::steal() }; + + // Check pending + let is_set = exti.rpr1().read().$rpifX().is_pending() + | exti.fpr1().read().$fpifX().is_pending(); + + cortex_m::interrupt::free(|_| { + // Disable the triggered interrupt to prevent further + // triggers and to signal the future that it is done + exti.imr1().modify(|_, w| w.$imX().clear_bit()); + }); + + if is_set { + WAKERS[$i].wake(); + } + + // Clear pending bits + exti.rpr1().write(|w| w.$rpifX().clear()); + exti.fpr1().write(|w| w.$fpifX().clear()); + } + )*}; +} + +impl_irq! { + EXTI0 => rpif0, fpif0, im0, 0, + EXTI1 => rpif1, fpif1, im1, 1, + EXTI2 => rpif2, fpif2, im2, 2, + EXTI3 => rpif3, fpif3, im3, 3, + EXTI4 => rpif4, fpif4, im4, 4, + EXTI5 => rpif5, fpif5, im5, 5, + EXTI6 => rpif6, fpif6, im6, 6, + EXTI7 => rpif7, fpif7, im7, 7, + EXTI8 => rpif8, fpif8, im8, 8, + EXTI9 => rpif9, fpif9, im9, 9, + EXTI10 => rpif10, fpif10, im10, 10, + EXTI11 => rpif11, fpif11, im11, 11, + EXTI12 => rpif12, fpif12, im12, 12, + EXTI13 => rpif13, fpif13, im13, 13, + EXTI14 => rpif14, fpif14, im14, 14, + EXTI15 => rpif15, fpif15, im15, 15 +} + +#[non_exhaustive] +#[must_use = "Futures no nothing unless polled or awaited"] +struct ExtiFuture; + +impl Future for ExtiFuture { + type Output = (); + + fn poll( + self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> core::task::Poll { + WAKERS[N as usize].register(cx.waker()); + + let exti = unsafe { EXTI::steal() }; + + if exti.imr1().read().bits() & N as u32 == 0 { + // EXTIx interrupt disabled which the irq does once an edge is detected + Poll::Ready(()) + } else { + Poll::Pending + } + } +} + +impl ExtiFuture { + fn new(edge: Edge) -> Self { + use crate::gpio::exti::ExtiedPin; + + // SAFETY: + // * Conjure a pin with mathing number to be able to setup exti + // - This isa safe since all pins with the same number share the + // same exti registers and only one pin per number can be setup for + // exti since they consume that exti channel. + unsafe { + let mut pin_handle = Pin::<'A', N, Input, true>::new(); + let mut exti = Exti(EXTI::steal()); + + // Only modifies Exti channel N's entries of EXTI + cortex_m::interrupt::free(|_| { + pin_handle.trigger_on_edge(&mut exti, edge); + + pin_handle.clear_interrupt_pending_bit(Edge::Rising); + pin_handle.clear_interrupt_pending_bit(Edge::Falling); + + pin_handle.enable_interrupt(&mut exti); + }); + }; + + ExtiFuture + } +} + +impl embedded_hal::digital::ErrorType + for Pin +{ + type Error = Infallible; +} + +impl embedded_hal_async::digital::Wait + for Pin +{ + async fn wait_for_high(&mut self) -> Result<(), Self::Error> { + if !self._is_low() { + // Pin is already high, done. + return Ok(()); + } + ExtiFuture::::new(Edge::Rising).await; + Ok(()) + } + + async fn wait_for_low(&mut self) -> Result<(), Self::Error> { + if self._is_low() { + // Pin is already low, done. + return Ok(()); + } + ExtiFuture::::new(Edge::Rising).await; + Ok(()) + } + + async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { + ExtiFuture::::new(Edge::Rising).await; + Ok(()) + } + + async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { + ExtiFuture::::new(Edge::Falling).await; + Ok(()) + } + + async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { + ExtiFuture::::new(Edge::RisingFalling).await; + Ok(()) + } +} diff --git a/src/gpio/gpio_def.rs b/src/gpio/gpio_def.rs index 931cee4..0ddcbfb 100644 --- a/src/gpio/gpio_def.rs +++ b/src/gpio/gpio_def.rs @@ -47,7 +47,7 @@ macro_rules! gpio { $( #[doc=concat!("P", $port_id, $i, " pin")] - pub type $PXi = crate::gpio::Pin<$port_id, $i, MODE>; + pub type $PXi = crate::gpio::Pin<$port_id, $i, MODE, INT>; $( impl crate::gpio::marker::IntoAf<$A> for $PXi { }