From 8979959dd15be496e74a1a670a468d4d5168f0c8 Mon Sep 17 00:00:00 2001 From: Daniel Bevenius Date: Sun, 10 Jul 2022 06:47:08 +0200 Subject: [PATCH] Add embedded_hal_async support for embassy-rp This commit adds support for embedded-hal-async to the Embassy Raspberry PI crate. --- embassy-rp/Cargo.toml | 1 + embassy-rp/src/gpio.rs | 302 +++++++++++++++++++++++++++++- examples/rp/src/bin/gpio_async.rs | 38 ++++ tests/rp/Cargo.toml | 1 + tests/rp/src/bin/gpio_async.rs | 148 +++++++++++++++ 5 files changed, 489 insertions(+), 1 deletion(-) create mode 100644 examples/rp/src/bin/gpio_async.rs create mode 100644 tests/rp/src/bin/gpio_async.rs diff --git a/embassy-rp/Cargo.toml b/embassy-rp/Cargo.toml index 95ae76cd..ffb4c914 100644 --- a/embassy-rp/Cargo.toml +++ b/embassy-rp/Cargo.toml @@ -38,6 +38,7 @@ log = { version = "0.4.14", optional = true } cortex-m-rt = ">=0.6.15,<0.8" cortex-m = "0.7.3" critical-section = "0.2.5" +futures = { version = "0.3.17", default-features = false, features = ["async-await"] } rp2040-pac2 = { git = "https://github.com/embassy-rs/rp2040-pac2", rev="9ad7223a48a065e612bc7dc7be5bf5bd0b41cfc4", features = ["rt"] } #rp2040-pac2 = { path = "../../rp/rp2040-pac2", features = ["rt"] } diff --git a/embassy-rp/src/gpio.rs b/embassy-rp/src/gpio.rs index c6dbb3d4..ef15a5ef 100644 --- a/embassy-rp/src/gpio.rs +++ b/embassy-rp/src/gpio.rs @@ -1,11 +1,20 @@ use core::convert::Infallible; +use core::future::Future; use core::marker::PhantomData; +use core::pin::Pin as FuturePin; +use core::task::{Context, Poll}; +use embassy::waitqueue::AtomicWaker; +use embassy_cortex_m::interrupt::{Interrupt, InterruptExt}; use embassy_hal_common::{unborrow, unsafe_impl_unborrow}; use crate::pac::common::{Reg, RW}; use crate::pac::SIO; -use crate::{pac, peripherals, Unborrow}; +use crate::{interrupt, pac, peripherals, Unborrow}; + +const PIN_COUNT: usize = 30; +const NEW_AW: AtomicWaker = AtomicWaker::new(); +static INTERRUPT_WAKERS: [AtomicWaker; PIN_COUNT] = [NEW_AW; PIN_COUNT]; /// Represents a digital input or output level. #[derive(Debug, Eq, PartialEq)] @@ -75,6 +84,204 @@ impl<'d, T: Pin> Input<'d, T> { pub fn get_level(&self) -> Level { self.pin.get_level() } + + pub async fn wait_for_high<'a>(&mut self) { + self.pin.wait_for_high().await; + } + + pub async fn wait_for_low<'a>(&mut self) { + self.pin.wait_for_low().await; + } + + pub async fn wait_for_rising_edge<'a>(&mut self) { + self.pin.wait_for_rising_edge().await; + } + + pub async fn wait_for_falling_edge<'a>(&mut self) { + self.pin.wait_for_falling_edge().await; + } + + pub async fn wait_for_any_edge<'a>(&mut self) { + self.pin.wait_for_any_edge().await; + } +} + +/// Interrupt trigger levels. +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum InterruptTrigger { + LevelLow, + LevelHigh, + EdgeLow, + EdgeHigh, + AnyEdge, +} + +impl InterruptTrigger { + fn from_u32(value: u32) -> Option { + match value { + 1 => Some(InterruptTrigger::LevelLow), + 2 => Some(InterruptTrigger::LevelHigh), + 3 => Some(InterruptTrigger::EdgeLow), + 4 => Some(InterruptTrigger::EdgeHigh), + _ => None, + } + } +} + +#[interrupt] +unsafe fn IO_IRQ_BANK0() { + let cpu = SIO.cpuid().read() as usize; + // There are two sets of interrupt registers, one for cpu0 and one for cpu1 + // and here we are selecting the set that belongs to the currently executing + // cpu. + let proc_intx: pac::io::Int = pac::IO_BANK0.int_proc(cpu); + for pin in 0..PIN_COUNT { + // There are 4 raw interrupt status registers, PROCx_INTS0, PROCx_INTS1, + // PROCx_INTS2, and PROCx_INTS3, and we are selecting the one that the + // current pin belongs to. + let intsx = proc_intx.ints(pin / 8); + // The status register is divided into groups of four, one group for + // each pin. Each group consists of four trigger levels LEVEL_LOW, + // LEVEL_HIGH, EDGE_LOW, and EDGE_HIGH for each pin. + let pin_group = (pin % 8) as usize; + let event = (intsx.read().0 >> pin_group * 4) & 0xf as u32; + + if let Some(trigger) = InterruptTrigger::from_u32(event) { + proc_intx.inte(pin / 8).write(|w| match trigger { + InterruptTrigger::AnyEdge => { + w.set_edge_high(pin_group, false); + w.set_edge_low(pin_group, false); + } + InterruptTrigger::LevelHigh => { + debug!("IO_IRQ_BANK0 pin {} LevelHigh triggered\n", pin); + w.set_level_high(pin_group, false); + } + InterruptTrigger::LevelLow => { + w.set_level_low(pin_group, false); + } + InterruptTrigger::EdgeHigh => { + w.set_edge_high(pin_group, false); + } + InterruptTrigger::EdgeLow => { + w.set_edge_low(pin_group, false); + } + }); + INTERRUPT_WAKERS[pin as usize].wake(); + } + } +} + +struct InputFuture<'a, T: Pin> { + pin: &'a mut T, + level: InterruptTrigger, + phantom: PhantomData<&'a mut AnyPin>, +} + +impl<'d, T: Pin> InputFuture<'d, T> { + pub fn new(pin: &'d mut T, level: InterruptTrigger) -> Self { + unsafe { + let irq = interrupt::IO_IRQ_BANK0::steal(); + irq.disable(); + irq.set_priority(interrupt::Priority::P6); + + // Each INTR register is divided into 8 groups, one group for each + // pin, and each group consists of LEVEL_LOW, LEVEL_HIGH, EDGE_LOW, + // and EGDE_HIGH. + let pin_group = (pin.pin() % 8) as usize; + pin.int_proc().inte((pin.pin() / 8) as usize).write(|w| match level { + InterruptTrigger::LevelHigh => { + debug!("InputFuture::new enable LevelHigh for pin {} \n", pin.pin()); + w.set_level_high(pin_group, true); + } + InterruptTrigger::LevelLow => { + w.set_level_low(pin_group, true); + } + InterruptTrigger::EdgeHigh => { + w.set_edge_high(pin_group, true); + } + InterruptTrigger::EdgeLow => { + w.set_edge_low(pin_group, true); + } + InterruptTrigger::AnyEdge => { + // noop + } + }); + + irq.enable(); + } + + Self { + pin, + level, + phantom: PhantomData, + } + } +} + +impl<'d, T: Pin> Future for InputFuture<'d, T> { + type Output = (); + + fn poll(self: FuturePin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // We need to register/re-register the waker for each poll because any + // calls to wake will deregister the waker. + INTERRUPT_WAKERS[self.pin.pin() as usize].register(cx.waker()); + + // self.int_proc() will get the register offset for the current cpu, + // then we want to access the interrupt enable register for our + // pin (there are 4 of these PROC0_INTE0, PROC0_INTE1, PROC0_INTE2, and + // PROC0_INTE3 per cpu). + let inte: pac::io::regs::Int = unsafe { self.pin.int_proc().inte((self.pin.pin() / 8) as usize).read() }; + // The register is divided into groups of four, one group for + // each pin. Each group consists of four trigger levels LEVEL_LOW, + // LEVEL_HIGH, EDGE_LOW, and EDGE_HIGH for each pin. + let pin_group = (self.pin.pin() % 8) as usize; + + // This should check the the level of the interrupt trigger level of + // the pin and if it has been disabled that means it was done by the + // interrupt service routine, so we then know that the event/trigger + // happened and Poll::Ready will be returned. + debug!("{:?} for pin {}\n", self.level, self.pin.pin()); + match self.level { + InterruptTrigger::AnyEdge => { + if !inte.edge_high(pin_group) && !inte.edge_low(pin_group) { + #[rustfmt::skip] + debug!("{:?} for pin {} was cleared, return Poll::Ready\n", self.level, self.pin.pin()); + return Poll::Ready(()); + } + } + InterruptTrigger::LevelHigh => { + if !inte.level_high(pin_group) { + #[rustfmt::skip] + debug!("{:?} for pin {} was cleared, return Poll::Ready\n", self.level, self.pin.pin()); + return Poll::Ready(()); + } + } + InterruptTrigger::LevelLow => { + if !inte.level_low(pin_group) { + #[rustfmt::skip] + debug!("{:?} for pin {} was cleared, return Poll::Ready\n", self.level, self.pin.pin()); + return Poll::Ready(()); + } + } + InterruptTrigger::EdgeHigh => { + if !inte.edge_high(pin_group) { + #[rustfmt::skip] + debug!("{:?} for pin {} was cleared, return Poll::Ready\n", self.level, self.pin.pin()); + return Poll::Ready(()); + } + } + InterruptTrigger::EdgeLow => { + if !inte.edge_low(pin_group) { + #[rustfmt::skip] + debug!("{:?} for pin {} was cleared, return Poll::Ready\n", self.level, self.pin.pin()); + return Poll::Ready(()); + } + } + } + debug!("InputFuture::poll return Poll::Pending\n"); + Poll::Pending + } } pub struct Output<'d, T: Pin> { @@ -340,6 +547,32 @@ impl<'d, T: Pin> Flex<'d, T> { pub fn toggle(&mut self) { unsafe { self.pin.sio_out().value_xor().write_value(self.bit()) } } + + pub async fn wait_for_high<'a>(&mut self) { + InputFuture::new(&mut self.pin, InterruptTrigger::LevelHigh).await; + } + + pub async fn wait_for_low<'a>(&mut self) { + InputFuture::new(&mut self.pin, InterruptTrigger::LevelLow).await; + } + + pub async fn wait_for_rising_edge<'a>(&mut self) { + self.wait_for_low().await; + self.wait_for_high().await; + } + + pub async fn wait_for_falling_edge<'a>(&mut self) { + self.wait_for_high().await; + self.wait_for_low().await; + } + + pub async fn wait_for_any_edge<'a>(&mut self) { + if self.is_high() { + self.wait_for_low().await; + } else { + self.wait_for_high().await; + } + } } impl<'d, T: Pin> Drop for Flex<'d, T> { @@ -401,6 +634,15 @@ pub(crate) mod sealed { fn sio_in(&self) -> Reg { SIO.gpio_in(self.bank() as _) } + + fn int_proc(&self) -> pac::io::Int { + let io_block = match self.bank() { + Bank::Bank0 => crate::pac::IO_BANK0, + Bank::Qspi => crate::pac::IO_QSPI, + }; + let proc = unsafe { SIO.cpuid().read() }; + io_block.int_proc(proc as _) + } } } @@ -478,6 +720,8 @@ impl_pin!(PIN_QSPI_SD3, Bank::Qspi, 5); // ==================== mod eh02 { + use futures::FutureExt; + use super::*; impl<'d, T: Pin> embedded_hal_02::digital::v2::InputPin for Input<'d, T> { @@ -595,6 +839,62 @@ mod eh02 { Ok(self.toggle()) } } + + use core::convert::Infallible; + + impl<'d, T: Pin> embedded_hal_async::digital::Wait for Flex<'d, T> { + type WaitForHighFuture<'a> = impl Future> + 'a where Self: 'a; + fn wait_for_high<'a>(&'a mut self) -> Self::WaitForHighFuture<'a> { + self.wait_for_high().map(Ok) + } + + type WaitForLowFuture<'a> = impl Future> + 'a where Self: 'a; + fn wait_for_low<'a>(&'a mut self) -> Self::WaitForLowFuture<'a> { + self.wait_for_low().map(Ok) + } + + type WaitForRisingEdgeFuture<'a> = impl Future> + 'a where Self: 'a; + fn wait_for_rising_edge<'a>(&'a mut self) -> Self::WaitForRisingEdgeFuture<'a> { + self.wait_for_rising_edge().map(Ok) + } + + type WaitForFallingEdgeFuture<'a> = impl Future> + 'a where Self: 'a; + fn wait_for_falling_edge<'a>(&'a mut self) -> Self::WaitForFallingEdgeFuture<'a> { + self.wait_for_falling_edge().map(Ok) + } + + type WaitForAnyEdgeFuture<'a> = impl Future> + 'a where Self: 'a; + fn wait_for_any_edge<'a>(&'a mut self) -> Self::WaitForAnyEdgeFuture<'a> { + self.wait_for_any_edge().map(Ok) + } + } + + impl<'d, T: Pin> embedded_hal_async::digital::Wait for Input<'d, T> { + type WaitForHighFuture<'a> = impl Future> + 'a where Self: 'a; + fn wait_for_high<'a>(&'a mut self) -> Self::WaitForHighFuture<'a> { + self.wait_for_high().map(Ok) + } + + type WaitForLowFuture<'a> = impl Future> + 'a where Self: 'a; + fn wait_for_low<'a>(&'a mut self) -> Self::WaitForLowFuture<'a> { + self.wait_for_low().map(Ok) + } + + type WaitForRisingEdgeFuture<'a> = impl Future> + 'a where Self: 'a; + fn wait_for_rising_edge<'a>(&'a mut self) -> Self::WaitForRisingEdgeFuture<'a> { + self.wait_for_rising_edge().map(Ok) + } + + type WaitForFallingEdgeFuture<'a> = impl Future> + 'a where Self: 'a; + fn wait_for_falling_edge<'a>(&'a mut self) -> Self::WaitForFallingEdgeFuture<'a> { + self.wait_for_falling_edge().map(Ok) + } + + type WaitForAnyEdgeFuture<'a> = impl Future> + 'a where Self: 'a; + fn wait_for_any_edge<'a>(&'a mut self) -> Self::WaitForAnyEdgeFuture<'a> { + self.wait_for_any_edge().map(Ok) + } + } } #[cfg(feature = "unstable-traits")] diff --git a/examples/rp/src/bin/gpio_async.rs b/examples/rp/src/bin/gpio_async.rs new file mode 100644 index 00000000..e0f2aa96 --- /dev/null +++ b/examples/rp/src/bin/gpio_async.rs @@ -0,0 +1,38 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy::executor::Spawner; +use embassy::time::{Duration, Timer}; +use embassy_rp::{gpio, Peripherals}; +use gpio::{Input, Level, Output, Pull}; +use {defmt_rtt as _, panic_probe as _}; + +/// This example shows how async gpio can be used with a RP2040. +/// +/// It requires an external signal to be manually triggered on PIN 16. For +/// example, this could be accomplished using an external power source with a +/// button so that it is possible to toggle the signal from low to high. +/// +/// This example will begin with turning on the LED on the board and wait for a +/// high signal on PIN 16. Once the high event/signal occurs the program will +/// continue and turn off the LED, and then wait for 2 seconds before completing +/// the loop and starting over again. +#[embassy::main] +async fn main(_spawner: Spawner, p: Peripherals) { + let mut led = Output::new(p.PIN_25, Level::Low); + let mut async_input = Input::new(p.PIN_16, Pull::None); + + loop { + info!("wait_for_high. Turn on LED"); + led.set_high(); + + async_input.wait_for_high().await; + + info!("done wait_for_high. Turn off LED"); + led.set_low(); + + Timer::after(Duration::from_secs(2)).await; + } +} diff --git a/tests/rp/Cargo.toml b/tests/rp/Cargo.toml index b3067fff..fe791d0d 100644 --- a/tests/rp/Cargo.toml +++ b/tests/rp/Cargo.toml @@ -16,6 +16,7 @@ embedded-hal = "0.2.6" embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.8" } embedded-hal-async = { version = "0.1.0-alpha.1" } panic-probe = { version = "0.3.0", features = ["print-defmt"] } +futures = { version = "0.3.17", default-features = false, features = ["async-await"] } [profile.dev] debug = 2 diff --git a/tests/rp/src/bin/gpio_async.rs b/tests/rp/src/bin/gpio_async.rs new file mode 100644 index 00000000..46df4105 --- /dev/null +++ b/tests/rp/src/bin/gpio_async.rs @@ -0,0 +1,148 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::{assert, *}; +use embassy::executor::Spawner; +use embassy::time::{Duration, Instant, Timer}; +use embassy_rp::gpio::{Input, Level, Output, Pull}; +use embassy_rp::Peripherals; +use futures::future::join; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy::main] +async fn main(_spawner: Spawner, p: Peripherals) { + info!("embassy-rp gpio_async test"); + + // On the CI device the following pins are connected with each other. + let (mut output_pin, mut input_pin) = (p.PIN_0, p.PIN_1); + + { + info!("test wait_for_high"); + let mut output = Output::new(&mut output_pin, Level::Low); + let mut input = Input::new(&mut input_pin, Pull::None); + + assert!(input.is_low(), "input was expected to be low"); + + let set_high_future = async { + // Allow time for wait_for_high_future to await wait_for_high(). + Timer::after(Duration::from_millis(10)).await; + output.set_high(); + }; + let wait_for_high_future = async { + let start = Instant::now(); + input.wait_for_high().await; + assert_duration(start); + }; + join(set_high_future, wait_for_high_future).await; + info!("test wait_for_high: OK\n"); + } + + { + info!("test wait_for_low"); + let mut output = Output::new(&mut output_pin, Level::High); + let mut input = Input::new(&mut input_pin, Pull::None); + + assert!(input.is_high(), "input was expected to be high"); + + let set_low_future = async { + Timer::after(Duration::from_millis(10)).await; + output.set_low(); + }; + let wait_for_low_future = async { + let start = Instant::now(); + input.wait_for_low().await; + assert_duration(start); + }; + join(set_low_future, wait_for_low_future).await; + info!("test wait_for_low: OK\n"); + } + + { + info!("test wait_for_rising_edge"); + let mut output = Output::new(&mut output_pin, Level::Low); + let mut input = Input::new(&mut input_pin, Pull::None); + + assert!(input.is_low(), "input was expected to be low"); + + let set_high_future = async { + Timer::after(Duration::from_millis(10)).await; + output.set_high(); + }; + let wait_for_rising_edge_future = async { + let start = Instant::now(); + input.wait_for_rising_edge().await; + assert_duration(start); + }; + join(set_high_future, wait_for_rising_edge_future).await; + info!("test wait_for_rising_edge: OK\n"); + } + + { + info!("test wait_for_falling_edge"); + let mut output = Output::new(&mut output_pin, Level::High); + let mut input = Input::new(&mut input_pin, Pull::None); + + assert!(input.is_high(), "input was expected to be high"); + + let set_low_future = async { + Timer::after(Duration::from_millis(10)).await; + output.set_low(); + }; + let wait_for_falling_edge_future = async { + let start = Instant::now(); + input.wait_for_falling_edge().await; + assert_duration(start); + }; + join(set_low_future, wait_for_falling_edge_future).await; + info!("test wait_for_falling_edge: OK\n"); + } + + { + info!("test wait_for_any_edge (falling)"); + let mut output = Output::new(&mut output_pin, Level::High); + let mut input = Input::new(&mut input_pin, Pull::None); + + assert!(input.is_high(), "input was expected to be high"); + + let set_low_future = async { + Timer::after(Duration::from_millis(10)).await; + output.set_low(); + }; + let wait_for_any_edge_future = async { + let start = Instant::now(); + input.wait_for_any_edge().await; + assert_duration(start); + }; + join(set_low_future, wait_for_any_edge_future).await; + info!("test wait_for_any_edge (falling): OK\n"); + } + + { + info!("test wait_for_any_edge (rising)"); + let mut output = Output::new(&mut output_pin, Level::Low); + let mut input = Input::new(&mut input_pin, Pull::None); + + assert!(input.is_low(), "input was expected to be low"); + + let set_high_future = async { + Timer::after(Duration::from_millis(10)).await; + output.set_high(); + }; + let wait_for_any_edge_future = async { + let start = Instant::now(); + input.wait_for_any_edge().await; + assert_duration(start); + }; + join(set_high_future, wait_for_any_edge_future).await; + info!("test wait_for_any_edge (rising): OK\n"); + } + + info!("Test OK"); + cortex_m::asm::bkpt(); + + fn assert_duration(start: Instant) { + let dur = Instant::now() - start; + assert!(dur >= Duration::from_millis(10) && dur < Duration::from_millis(11)); + } +}