From ecf2f4bf112621b8cb107368f5b6d00affd61484 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Tue, 15 Apr 2025 10:54:34 +0200 Subject: [PATCH] still a lot to do --- va108xx-hal/src/gpio.rs | 2 - va108xx-hal/src/gpio/asynch.rs | 366 +++++++++++++++++++++++ va108xx-hal/src/gpio/mod.rs | 7 + va108xx-hal/src/gpio_tmp/asynch.rs | 366 ----------------------- va108xx-hal/src/lib.rs | 29 +- va108xx-hal/src/pins.rs | 4 +- va108xx-hal/src/pwm.rs | 214 +++---------- va108xx-hal/src/spi/mod.rs | 6 + va108xx-hal/src/spi/pins.rs | 8 +- va108xx-hal/src/timer.rs | 219 +++----------- va108xx-hal/src/uart/mod.rs | 34 ++- va108xx-hal/src/uart/rx_asynch.rs | 2 +- vorago-shared-periphs/Cargo.toml | 11 +- vorago-shared-periphs/src/gpio/asynch.rs | 332 ++++++++++++++++++++ vorago-shared-periphs/src/gpio/ll.rs | 127 ++++++-- vorago-shared-periphs/src/gpio/mod.rs | 38 ++- vorago-shared-periphs/src/gpio/regs.rs | 5 +- vorago-shared-periphs/src/lib.rs | 57 ++++ 18 files changed, 1014 insertions(+), 813 deletions(-) delete mode 100644 va108xx-hal/src/gpio.rs create mode 100644 va108xx-hal/src/gpio/asynch.rs create mode 100644 va108xx-hal/src/gpio/mod.rs create mode 100644 vorago-shared-periphs/src/gpio/asynch.rs diff --git a/va108xx-hal/src/gpio.rs b/va108xx-hal/src/gpio.rs deleted file mode 100644 index fc1fb33..0000000 --- a/va108xx-hal/src/gpio.rs +++ /dev/null @@ -1,2 +0,0 @@ -//! GPIO support module. -pub use vorago_shared_periphs::gpio::*; diff --git a/va108xx-hal/src/gpio/asynch.rs b/va108xx-hal/src/gpio/asynch.rs new file mode 100644 index 0000000..36c4ab8 --- /dev/null +++ b/va108xx-hal/src/gpio/asynch.rs @@ -0,0 +1,366 @@ +//! # Async GPIO functionality for the VA108xx family. +//! +//! This module provides the [InputPinAsync] and [InputDynPinAsync] which both implement +//! the [embedded_hal_async::digital::Wait] trait. These types allow for asynchronous waiting +//! on GPIO pins. Please note that this module does not specify/declare the interrupt handlers +//! which must be provided for async support to work. However, it provides the +//! [on_interrupt_for_async_gpio_for_port] generic interrupt handler. This should be called in all +//! IRQ functions which handle any GPIO interrupts with the corresponding [Port] argument. +//! +//! # Example +//! +//! - [Async GPIO example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/embassy/src/bin/async-gpio.rs) +use core::future::Future; + +use embassy_sync::waitqueue::AtomicWaker; +use embedded_hal_async::digital::Wait; +use portable_atomic::AtomicBool; +use va108xx::{self as pac}; + +use crate::InterruptConfig; + +use super::{ + pin, DynPin, DynPinId, InputConfig, InterruptEdge, InvalidPinTypeError, Pin, PinId, Port, + NUM_PINS_PORT_A, NUM_PINS_PORT_B, +}; + +static WAKERS_FOR_PORT_A: [AtomicWaker; NUM_PINS_PORT_A] = + [const { AtomicWaker::new() }; NUM_PINS_PORT_A]; +static WAKERS_FOR_PORT_B: [AtomicWaker; NUM_PINS_PORT_B] = + [const { AtomicWaker::new() }; NUM_PINS_PORT_B]; +static EDGE_DETECTION_PORT_A: [AtomicBool; NUM_PINS_PORT_A] = + [const { AtomicBool::new(false) }; NUM_PINS_PORT_A]; +static EDGE_DETECTION_PORT_B: [AtomicBool; NUM_PINS_PORT_B] = + [const { AtomicBool::new(false) }; NUM_PINS_PORT_B]; + +/// Generic interrupt handler for GPIO interrupts on a specific port to support async functionalities +/// +/// This function should be called in all interrupt handlers which handle any GPIO interrupts +/// matching the [Port] argument. +/// The handler will wake the corresponding wakers for the pins that triggered an interrupts +/// as well as update the static edge detection structures. This allows the pin future tocomplete +/// complete async operations. +pub fn on_interrupt_for_async_gpio_for_port(port: Port) { + let periphs = unsafe { pac::Peripherals::steal() }; + + let (irq_enb, edge_status, wakers, edge_detection) = match port { + Port::A => ( + periphs.porta.irq_enb().read().bits(), + periphs.porta.edge_status().read().bits(), + WAKERS_FOR_PORT_A.as_ref(), + EDGE_DETECTION_PORT_A.as_ref(), + ), + Port::B => ( + periphs.portb.irq_enb().read().bits(), + periphs.portb.edge_status().read().bits(), + WAKERS_FOR_PORT_B.as_ref(), + EDGE_DETECTION_PORT_B.as_ref(), + ), + }; + + on_interrupt_for_port(irq_enb, edge_status, wakers, edge_detection); +} + +#[inline] +fn on_interrupt_for_port( + mut irq_enb: u32, + edge_status: u32, + wakers: &'static [AtomicWaker], + edge_detection: &'static [AtomicBool], +) { + while irq_enb != 0 { + let bit_pos = irq_enb.trailing_zeros() as usize; + let bit_mask = 1 << bit_pos; + + wakers[bit_pos].wake(); + + if edge_status & bit_mask != 0 { + edge_detection[bit_pos].store(true, core::sync::atomic::Ordering::Relaxed); + + // Clear the processed bit + irq_enb &= !bit_mask; + } + } +} + +/// Input pin future which implements the [Future] trait. +/// +/// Generally, you want to use the [InputPinAsync] or [InputDynPinAsync] types instead of this +/// which also implements the [embedded_hal_async::digital::Wait] trait. However, access to this +/// struture is granted to allow writing custom async structures. +pub struct InputPinFuture { + pin_id: DynPinId, + waker_group: &'static [AtomicWaker], + edge_detection_group: &'static [AtomicBool], +} + +impl InputPinFuture { + #[inline] + pub fn pin_group_to_waker_and_edge_detection_group( + group: Port, + ) -> (&'static [AtomicWaker], &'static [AtomicBool]) { + match group { + Port::A => (WAKERS_FOR_PORT_A.as_ref(), EDGE_DETECTION_PORT_A.as_ref()), + Port::B => (WAKERS_FOR_PORT_B.as_ref(), EDGE_DETECTION_PORT_B.as_ref()), + } + } + + pub fn new_with_dyn_pin( + pin: &mut DynPin, + irq: pac::Interrupt, + edge: InterruptEdge, + ) -> Result { + if !pin.is_input_pin() { + return Err(InvalidPinTypeError(pin.mode())); + } + + let (waker_group, edge_detection_group) = + Self::pin_group_to_waker_and_edge_detection_group(pin.id().port()); + edge_detection_group[pin.id().num() as usize] + .store(false, core::sync::atomic::Ordering::Relaxed); + pin.configure_edge_interrupt(edge).unwrap(); + pin.enable_interrupt(InterruptConfig::new(irq, true, true)); + Ok(Self { + pin_id: pin.id(), + waker_group, + edge_detection_group, + }) + } + + pub fn new_with_pin( + pin: &mut Pin>, + irq: pac::Interrupt, + edge: InterruptEdge, + ) -> Self { + let (waker_group, edge_detection_group) = + Self::pin_group_to_waker_and_edge_detection_group(pin.id().port()); + edge_detection_group[pin.id().num() as usize] + .store(false, core::sync::atomic::Ordering::Relaxed); + pin.configure_edge_interrupt(edge); + pin.enable_interrupt(InterruptConfig::new(irq, true, true)); + Self { + pin_id: pin.id(), + edge_detection_group, + waker_group, + } + } +} + +impl Drop for InputPinFuture { + fn drop(&mut self) { + // The API ensures that we actually own the pin, so stealing it here is okay. + unsafe { DynPin::steal(self.pin_id) }.disable_interrupt(false); + } +} + +impl Future for InputPinFuture { + type Output = (); + fn poll( + self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> core::task::Poll { + let idx = self.pin_id.num() as usize; + self.waker_group[idx].register(cx.waker()); + if self.edge_detection_group[idx].swap(false, core::sync::atomic::Ordering::Relaxed) { + return core::task::Poll::Ready(()); + } + core::task::Poll::Pending + } +} + +pub struct InputDynPinAsync { + pin: DynPin, + irq: pac::Interrupt, +} + +impl InputDynPinAsync { + /// Create a new asynchronous input pin from a [DynPin]. The interrupt ID to be used must be + /// passed as well and is used to route and enable the interrupt. + /// + /// Please note that the interrupt handler itself must be provided by the user and the + /// generic [on_interrupt_for_async_gpio_for_port] function must be called inside that function + /// for the asynchronous functionality to work. + pub fn new(pin: DynPin, irq: pac::Interrupt) -> Result { + if !pin.is_input_pin() { + return Err(InvalidPinTypeError(pin.mode())); + } + Ok(Self { pin, irq }) + } + + /// Asynchronously wait until the pin is high. + /// + /// This returns immediately if the pin is already high. + pub async fn wait_for_high(&mut self) { + // Unwrap okay, checked pin in constructor. + let fut = + InputPinFuture::new_with_dyn_pin(&mut self.pin, self.irq, InterruptEdge::LowToHigh) + .unwrap(); + if self.pin.is_high().unwrap() { + return; + } + fut.await; + } + + /// Asynchronously wait until the pin is low. + /// + /// This returns immediately if the pin is already high. + pub async fn wait_for_low(&mut self) { + // Unwrap okay, checked pin in constructor. + let fut = + InputPinFuture::new_with_dyn_pin(&mut self.pin, self.irq, InterruptEdge::HighToLow) + .unwrap(); + if self.pin.is_low().unwrap() { + return; + } + fut.await; + } + + /// Asynchronously wait until the pin sees a falling edge. + pub async fn wait_for_falling_edge(&mut self) { + // Unwrap okay, checked pin in constructor. + InputPinFuture::new_with_dyn_pin(&mut self.pin, self.irq, InterruptEdge::HighToLow) + .unwrap() + .await; + } + + /// Asynchronously wait until the pin sees a rising edge. + pub async fn wait_for_rising_edge(&mut self) { + // Unwrap okay, checked pin in constructor. + InputPinFuture::new_with_dyn_pin(&mut self.pin, self.irq, InterruptEdge::LowToHigh) + .unwrap() + .await; + } + + /// Asynchronously wait until the pin sees any edge (either rising or falling). + pub async fn wait_for_any_edge(&mut self) { + // Unwrap okay, checked pin in constructor. + InputPinFuture::new_with_dyn_pin(&mut self.pin, self.irq, InterruptEdge::BothEdges) + .unwrap() + .await; + } + + pub fn release(self) -> DynPin { + self.pin + } +} + +impl embedded_hal::digital::ErrorType for InputDynPinAsync { + type Error = core::convert::Infallible; +} + +impl Wait for InputDynPinAsync { + 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(()) + } +} + +pub struct InputPinAsync { + pin: Pin>, + irq: pac::Interrupt, +} + +impl InputPinAsync { + /// Create a new asynchronous input pin from a typed [Pin]. The interrupt ID to be used must be + /// passed as well and is used to route and enable the interrupt. + /// + /// Please note that the interrupt handler itself must be provided by the user and the + /// generic [on_interrupt_for_async_gpio_for_port] function must be called inside that function + /// for the asynchronous functionality to work. + pub fn new(pin: Pin>, irq: pac::Interrupt) -> Self { + Self { pin, irq } + } + + /// Asynchronously wait until the pin is high. + /// + /// This returns immediately if the pin is already high. + pub async fn wait_for_high(&mut self) { + let fut = InputPinFuture::new_with_pin(&mut self.pin, self.irq, InterruptEdge::LowToHigh); + if self.pin.is_high() { + return; + } + fut.await; + } + + /// Asynchronously wait until the pin is low. + /// + /// This returns immediately if the pin is already high. + pub async fn wait_for_low(&mut self) { + let fut = InputPinFuture::new_with_pin(&mut self.pin, self.irq, InterruptEdge::HighToLow); + if self.pin.is_low() { + return; + } + fut.await; + } + + /// Asynchronously wait until the pin sees falling edge. + pub async fn wait_for_falling_edge(&mut self) { + // Unwrap okay, checked pin in constructor. + InputPinFuture::new_with_pin(&mut self.pin, self.irq, InterruptEdge::HighToLow).await; + } + + /// Asynchronously wait until the pin sees rising edge. + pub async fn wait_for_rising_edge(&mut self) { + // Unwrap okay, checked pin in constructor. + InputPinFuture::new_with_pin(&mut self.pin, self.irq, InterruptEdge::LowToHigh).await; + } + + /// Asynchronously wait until the pin sees any edge (either rising or falling). + pub async fn wait_for_any_edge(&mut self) { + InputPinFuture::new_with_pin(&mut self.pin, self.irq, InterruptEdge::BothEdges).await; + } + + pub fn release(self) -> Pin> { + self.pin + } +} +impl embedded_hal::digital::ErrorType for InputPinAsync { + type Error = core::convert::Infallible; +} + +impl Wait for InputPinAsync { + 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(()) + } +} diff --git a/va108xx-hal/src/gpio/mod.rs b/va108xx-hal/src/gpio/mod.rs new file mode 100644 index 0000000..be6f024 --- /dev/null +++ b/va108xx-hal/src/gpio/mod.rs @@ -0,0 +1,7 @@ +//! GPIO support module. +pub use vorago_shared_periphs::gpio::*; + +/// Low-level GPIO access. +pub use vorago_shared_periphs::gpio::ll; + +pub mod asynch; diff --git a/va108xx-hal/src/gpio_tmp/asynch.rs b/va108xx-hal/src/gpio_tmp/asynch.rs index 36c4ab8..e69de29 100644 --- a/va108xx-hal/src/gpio_tmp/asynch.rs +++ b/va108xx-hal/src/gpio_tmp/asynch.rs @@ -1,366 +0,0 @@ -//! # Async GPIO functionality for the VA108xx family. -//! -//! This module provides the [InputPinAsync] and [InputDynPinAsync] which both implement -//! the [embedded_hal_async::digital::Wait] trait. These types allow for asynchronous waiting -//! on GPIO pins. Please note that this module does not specify/declare the interrupt handlers -//! which must be provided for async support to work. However, it provides the -//! [on_interrupt_for_async_gpio_for_port] generic interrupt handler. This should be called in all -//! IRQ functions which handle any GPIO interrupts with the corresponding [Port] argument. -//! -//! # Example -//! -//! - [Async GPIO example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/embassy/src/bin/async-gpio.rs) -use core::future::Future; - -use embassy_sync::waitqueue::AtomicWaker; -use embedded_hal_async::digital::Wait; -use portable_atomic::AtomicBool; -use va108xx::{self as pac}; - -use crate::InterruptConfig; - -use super::{ - pin, DynPin, DynPinId, InputConfig, InterruptEdge, InvalidPinTypeError, Pin, PinId, Port, - NUM_PINS_PORT_A, NUM_PINS_PORT_B, -}; - -static WAKERS_FOR_PORT_A: [AtomicWaker; NUM_PINS_PORT_A] = - [const { AtomicWaker::new() }; NUM_PINS_PORT_A]; -static WAKERS_FOR_PORT_B: [AtomicWaker; NUM_PINS_PORT_B] = - [const { AtomicWaker::new() }; NUM_PINS_PORT_B]; -static EDGE_DETECTION_PORT_A: [AtomicBool; NUM_PINS_PORT_A] = - [const { AtomicBool::new(false) }; NUM_PINS_PORT_A]; -static EDGE_DETECTION_PORT_B: [AtomicBool; NUM_PINS_PORT_B] = - [const { AtomicBool::new(false) }; NUM_PINS_PORT_B]; - -/// Generic interrupt handler for GPIO interrupts on a specific port to support async functionalities -/// -/// This function should be called in all interrupt handlers which handle any GPIO interrupts -/// matching the [Port] argument. -/// The handler will wake the corresponding wakers for the pins that triggered an interrupts -/// as well as update the static edge detection structures. This allows the pin future tocomplete -/// complete async operations. -pub fn on_interrupt_for_async_gpio_for_port(port: Port) { - let periphs = unsafe { pac::Peripherals::steal() }; - - let (irq_enb, edge_status, wakers, edge_detection) = match port { - Port::A => ( - periphs.porta.irq_enb().read().bits(), - periphs.porta.edge_status().read().bits(), - WAKERS_FOR_PORT_A.as_ref(), - EDGE_DETECTION_PORT_A.as_ref(), - ), - Port::B => ( - periphs.portb.irq_enb().read().bits(), - periphs.portb.edge_status().read().bits(), - WAKERS_FOR_PORT_B.as_ref(), - EDGE_DETECTION_PORT_B.as_ref(), - ), - }; - - on_interrupt_for_port(irq_enb, edge_status, wakers, edge_detection); -} - -#[inline] -fn on_interrupt_for_port( - mut irq_enb: u32, - edge_status: u32, - wakers: &'static [AtomicWaker], - edge_detection: &'static [AtomicBool], -) { - while irq_enb != 0 { - let bit_pos = irq_enb.trailing_zeros() as usize; - let bit_mask = 1 << bit_pos; - - wakers[bit_pos].wake(); - - if edge_status & bit_mask != 0 { - edge_detection[bit_pos].store(true, core::sync::atomic::Ordering::Relaxed); - - // Clear the processed bit - irq_enb &= !bit_mask; - } - } -} - -/// Input pin future which implements the [Future] trait. -/// -/// Generally, you want to use the [InputPinAsync] or [InputDynPinAsync] types instead of this -/// which also implements the [embedded_hal_async::digital::Wait] trait. However, access to this -/// struture is granted to allow writing custom async structures. -pub struct InputPinFuture { - pin_id: DynPinId, - waker_group: &'static [AtomicWaker], - edge_detection_group: &'static [AtomicBool], -} - -impl InputPinFuture { - #[inline] - pub fn pin_group_to_waker_and_edge_detection_group( - group: Port, - ) -> (&'static [AtomicWaker], &'static [AtomicBool]) { - match group { - Port::A => (WAKERS_FOR_PORT_A.as_ref(), EDGE_DETECTION_PORT_A.as_ref()), - Port::B => (WAKERS_FOR_PORT_B.as_ref(), EDGE_DETECTION_PORT_B.as_ref()), - } - } - - pub fn new_with_dyn_pin( - pin: &mut DynPin, - irq: pac::Interrupt, - edge: InterruptEdge, - ) -> Result { - if !pin.is_input_pin() { - return Err(InvalidPinTypeError(pin.mode())); - } - - let (waker_group, edge_detection_group) = - Self::pin_group_to_waker_and_edge_detection_group(pin.id().port()); - edge_detection_group[pin.id().num() as usize] - .store(false, core::sync::atomic::Ordering::Relaxed); - pin.configure_edge_interrupt(edge).unwrap(); - pin.enable_interrupt(InterruptConfig::new(irq, true, true)); - Ok(Self { - pin_id: pin.id(), - waker_group, - edge_detection_group, - }) - } - - pub fn new_with_pin( - pin: &mut Pin>, - irq: pac::Interrupt, - edge: InterruptEdge, - ) -> Self { - let (waker_group, edge_detection_group) = - Self::pin_group_to_waker_and_edge_detection_group(pin.id().port()); - edge_detection_group[pin.id().num() as usize] - .store(false, core::sync::atomic::Ordering::Relaxed); - pin.configure_edge_interrupt(edge); - pin.enable_interrupt(InterruptConfig::new(irq, true, true)); - Self { - pin_id: pin.id(), - edge_detection_group, - waker_group, - } - } -} - -impl Drop for InputPinFuture { - fn drop(&mut self) { - // The API ensures that we actually own the pin, so stealing it here is okay. - unsafe { DynPin::steal(self.pin_id) }.disable_interrupt(false); - } -} - -impl Future for InputPinFuture { - type Output = (); - fn poll( - self: core::pin::Pin<&mut Self>, - cx: &mut core::task::Context<'_>, - ) -> core::task::Poll { - let idx = self.pin_id.num() as usize; - self.waker_group[idx].register(cx.waker()); - if self.edge_detection_group[idx].swap(false, core::sync::atomic::Ordering::Relaxed) { - return core::task::Poll::Ready(()); - } - core::task::Poll::Pending - } -} - -pub struct InputDynPinAsync { - pin: DynPin, - irq: pac::Interrupt, -} - -impl InputDynPinAsync { - /// Create a new asynchronous input pin from a [DynPin]. The interrupt ID to be used must be - /// passed as well and is used to route and enable the interrupt. - /// - /// Please note that the interrupt handler itself must be provided by the user and the - /// generic [on_interrupt_for_async_gpio_for_port] function must be called inside that function - /// for the asynchronous functionality to work. - pub fn new(pin: DynPin, irq: pac::Interrupt) -> Result { - if !pin.is_input_pin() { - return Err(InvalidPinTypeError(pin.mode())); - } - Ok(Self { pin, irq }) - } - - /// Asynchronously wait until the pin is high. - /// - /// This returns immediately if the pin is already high. - pub async fn wait_for_high(&mut self) { - // Unwrap okay, checked pin in constructor. - let fut = - InputPinFuture::new_with_dyn_pin(&mut self.pin, self.irq, InterruptEdge::LowToHigh) - .unwrap(); - if self.pin.is_high().unwrap() { - return; - } - fut.await; - } - - /// Asynchronously wait until the pin is low. - /// - /// This returns immediately if the pin is already high. - pub async fn wait_for_low(&mut self) { - // Unwrap okay, checked pin in constructor. - let fut = - InputPinFuture::new_with_dyn_pin(&mut self.pin, self.irq, InterruptEdge::HighToLow) - .unwrap(); - if self.pin.is_low().unwrap() { - return; - } - fut.await; - } - - /// Asynchronously wait until the pin sees a falling edge. - pub async fn wait_for_falling_edge(&mut self) { - // Unwrap okay, checked pin in constructor. - InputPinFuture::new_with_dyn_pin(&mut self.pin, self.irq, InterruptEdge::HighToLow) - .unwrap() - .await; - } - - /// Asynchronously wait until the pin sees a rising edge. - pub async fn wait_for_rising_edge(&mut self) { - // Unwrap okay, checked pin in constructor. - InputPinFuture::new_with_dyn_pin(&mut self.pin, self.irq, InterruptEdge::LowToHigh) - .unwrap() - .await; - } - - /// Asynchronously wait until the pin sees any edge (either rising or falling). - pub async fn wait_for_any_edge(&mut self) { - // Unwrap okay, checked pin in constructor. - InputPinFuture::new_with_dyn_pin(&mut self.pin, self.irq, InterruptEdge::BothEdges) - .unwrap() - .await; - } - - pub fn release(self) -> DynPin { - self.pin - } -} - -impl embedded_hal::digital::ErrorType for InputDynPinAsync { - type Error = core::convert::Infallible; -} - -impl Wait for InputDynPinAsync { - 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(()) - } -} - -pub struct InputPinAsync { - pin: Pin>, - irq: pac::Interrupt, -} - -impl InputPinAsync { - /// Create a new asynchronous input pin from a typed [Pin]. The interrupt ID to be used must be - /// passed as well and is used to route and enable the interrupt. - /// - /// Please note that the interrupt handler itself must be provided by the user and the - /// generic [on_interrupt_for_async_gpio_for_port] function must be called inside that function - /// for the asynchronous functionality to work. - pub fn new(pin: Pin>, irq: pac::Interrupt) -> Self { - Self { pin, irq } - } - - /// Asynchronously wait until the pin is high. - /// - /// This returns immediately if the pin is already high. - pub async fn wait_for_high(&mut self) { - let fut = InputPinFuture::new_with_pin(&mut self.pin, self.irq, InterruptEdge::LowToHigh); - if self.pin.is_high() { - return; - } - fut.await; - } - - /// Asynchronously wait until the pin is low. - /// - /// This returns immediately if the pin is already high. - pub async fn wait_for_low(&mut self) { - let fut = InputPinFuture::new_with_pin(&mut self.pin, self.irq, InterruptEdge::HighToLow); - if self.pin.is_low() { - return; - } - fut.await; - } - - /// Asynchronously wait until the pin sees falling edge. - pub async fn wait_for_falling_edge(&mut self) { - // Unwrap okay, checked pin in constructor. - InputPinFuture::new_with_pin(&mut self.pin, self.irq, InterruptEdge::HighToLow).await; - } - - /// Asynchronously wait until the pin sees rising edge. - pub async fn wait_for_rising_edge(&mut self) { - // Unwrap okay, checked pin in constructor. - InputPinFuture::new_with_pin(&mut self.pin, self.irq, InterruptEdge::LowToHigh).await; - } - - /// Asynchronously wait until the pin sees any edge (either rising or falling). - pub async fn wait_for_any_edge(&mut self) { - InputPinFuture::new_with_pin(&mut self.pin, self.irq, InterruptEdge::BothEdges).await; - } - - pub fn release(self) -> Pin> { - self.pin - } -} -impl embedded_hal::digital::ErrorType for InputPinAsync { - type Error = core::convert::Infallible; -} - -impl Wait for InputPinAsync { - 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(()) - } -} diff --git a/va108xx-hal/src/lib.rs b/va108xx-hal/src/lib.rs index ccf0861..3aa94c5 100644 --- a/va108xx-hal/src/lib.rs +++ b/va108xx-hal/src/lib.rs @@ -19,6 +19,9 @@ pub mod uart; pub use vorago_shared_periphs::FunSel; +/// This is the NONE destination reigster value for the IRQSEL peripheral. +pub const IRQ_DST_NONE: u32 = 0xffffffff; + #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum PeripheralSelect { @@ -37,32 +40,6 @@ pub enum PeripheralSelect { Gpio = 24, } -/// Generic interrupt config which can be used to specify whether the HAL driver will -/// use the IRQSEL register to route an interrupt, and whether the IRQ will be unmasked in the -/// Cortex-M0 NVIC. Both are generally necessary for IRQs to work, but the user might want to -/// perform those steps themselves. -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct InterruptConfig { - /// Interrupt target vector. Should always be set, might be required for disabling IRQs - pub id: pac::Interrupt, - /// Specfiy whether IRQ should be routed to an IRQ vector using the IRQSEL peripheral. - pub route: bool, - /// Specify whether the IRQ is unmasked in the Cortex-M NVIC. If an interrupt is used for - /// multiple purposes, the user can enable the interrupts themselves. - pub enable_in_nvic: bool, -} - -impl InterruptConfig { - pub fn new(id: pac::Interrupt, route: bool, enable_in_nvic: bool) -> Self { - InterruptConfig { - id, - route, - enable_in_nvic, - } - } -} - pub type IrqCfg = InterruptConfig; #[derive(Debug, PartialEq, Eq, thiserror::Error)] diff --git a/va108xx-hal/src/pins.rs b/va108xx-hal/src/pins.rs index b651c57..654b887 100644 --- a/va108xx-hal/src/pins.rs +++ b/va108xx-hal/src/pins.rs @@ -1,4 +1,4 @@ -pub use vorago_shared_periphs::gpio::{Pin, PinId, Port}; +pub use vorago_shared_periphs::gpio::{Pin, PinIdProvider, Port}; use crate::{sysconfig::reset_peripheral_for_cycles, PeripheralSelect}; @@ -19,7 +19,7 @@ macro_rules! pin_id { }; } -impl crate::sealed::Sealed for Pin {} +impl crate::sealed::Sealed for Pin {} pin_id!(Pa0, Port::A, 0); pin_id!(Pa1, Port::A, 1); diff --git a/va108xx-hal/src/pwm.rs b/va108xx-hal/src/pwm.rs index 409d1b7..0ea48af 100644 --- a/va108xx-hal/src/pwm.rs +++ b/va108xx-hal/src/pwm.rs @@ -11,191 +11,39 @@ use core::marker::PhantomData; use crate::clock::enable_peripheral_clock; use crate::pac; use crate::time::Hertz; -use crate::timer::{Tim, TimPeripheralMarker, TimPin, TimRegInterface}; +use crate::timer::{TimId, TimPeripheralMarker, TimPin, TimRegInterface}; const DUTY_MAX: u16 = u16::MAX; #[derive(Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub(crate) struct PwmCommon {} - enum StatusSelPwm { PwmA = 3, PwmB = 4, } +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct PwmA {} +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct PwmB {} -//================================================================================================== -// Strongly typed PWM pin -//================================================================================================== - -/* -pub struct PwmPin { - pin_and_tim: (Pin, Tim), - inner: ReducedPwmPin, - mode: PhantomData, +#[derive(Debug, thiserror::Error)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[error("pin tim ID {pin_tim:?} and timer tim id {tim_id:?} do not match")] +pub struct TimMissmatchError { + pin_tim: TimId, + tim_id: TimId, } -impl PwmPin -where - (Pin, Tim): ValidTimAndPin, -{ - /// Create a new stronlgy typed PWM pin - pub fn new( - sys_cfg: &mut pac::Sysconfig, - sys_clk: impl Into + Copy, - pin_and_tim: (Pin, Tim), - initial_period: impl Into + Copy, - ) -> Self { - let mut pin = PwmPin { - pin_and_tim, - inner: ReducedPwmPin::::new( - Tim::TIM_ID, - Pin::DYN, - PwmCommon { - current_duty: 0, - current_lower_limit: 0, - current_period: initial_period.into(), - current_rst_val: 0, - sys_clk: sys_clk.into(), - }, - ), - //unsafe { TimAndPin::new(tim_and_pin.0, tim_and_pin.1) }, - mode: PhantomData, - }; - enable_peripheral_clock(sys_cfg, crate::clock::PeripheralClocks::Gpio); - enable_peripheral_clock(sys_cfg, crate::clock::PeripheralClocks::Ioconfig); - sys_cfg - .tim_clk_enable() - .modify(|r, w| unsafe { w.bits(r.bits() | pin.pin_and_tim.1.mask_32()) }); - pin.enable_pwm_a(); - pin.set_period(initial_period); - pin - } - - pub fn downgrade(self) -> ReducedPwmPin { - self.inner - } - - pub fn release(self) -> (Pin, Tim) { - self.pin_and_tim - } - - #[inline] - fn enable_pwm_a(&mut self) { - self.inner.enable_pwm_a(); - } - - #[inline] - fn enable_pwm_b(&mut self) { - self.inner.enable_pwm_b(); - } - - #[inline] - pub fn get_period(&self) -> Hertz { - self.inner.get_period() - } - - #[inline] - pub fn set_period(&mut self, period: impl Into) { - self.inner.set_period(period); - } - - #[inline] - pub fn disable(&mut self) { - self.inner.disable(); - } - - #[inline] - pub fn enable(&mut self) { - self.inner.enable(); - } - - #[inline] - pub fn period(&self) -> Hertz { - self.inner.period() - } - - #[inline(always)] - pub fn duty(&self) -> u16 { - self.inner.duty() - } -} - -impl From> for PwmPin -where - (Pin, Tim): ValidTimAndPin, -{ - fn from(other: PwmPin) -> Self { - let mut pwmb = Self { - mode: PhantomData, - pin_and_tim: other.pin_and_tim, - inner: other.inner.into(), - }; - pwmb.enable_pwm_b(); - pwmb - } -} - -impl From> for PwmPin -where - (PIN, TIM): ValidTimAndPin, -{ - fn from(other: PwmPin) -> Self { - let mut pwma = Self { - mode: PhantomData, - pin_and_tim: other.pin_and_tim, - inner: other.inner.into(), - }; - pwma.enable_pwm_a(); - pwma - } -} - -impl PwmPin -where - (Pin, Tim): ValidTimAndPin, -{ - pub fn pwma( - sys_cfg: &mut pac::Sysconfig, - sys_clk: impl Into + Copy, - pin_and_tim: (Pin, Tim), - initial_period: impl Into + Copy, - ) -> Self { - let mut pin: PwmPin = - Self::new(sys_cfg, sys_clk, pin_and_tim, initial_period); - pin.enable_pwm_a(); - pin - } -} - -impl PwmPin -where - (Pin, Tim): ValidTimAndPin, -{ - pub fn pwmb( - sys_cfg: &mut pac::Sysconfig, - sys_clk: impl Into + Copy, - pin_and_tim: (Pin, Tim), - initial_period: impl Into + Copy, - ) -> Self { - let mut pin: PwmPin = - Self::new(sys_cfg, sys_clk, pin_and_tim, initial_period); - pin.enable_pwm_b(); - pin - } -} -*/ - //================================================================================================== // PWM pin //================================================================================================== /// Reduced version where type information is deleted pub struct PwmPin { - tim: Tim, + tim_id: TimId, sys_clk: Hertz, /// For PWMB, this is the upper limit current_duty: u16, @@ -208,14 +56,20 @@ pub struct PwmPin { impl PwmPin { /// Create a new strongly typed PWM pin - pub fn new( + pub fn new( sys_cfg: &mut pac::Sysconfig, sys_clk: impl Into + Copy, pin_and_tim: (Pin, Tim), initial_period: impl Into + Copy, - ) -> Self { + ) -> Result { + if Pin::TIM_ID != Tim::ID { + return Err(TimMissmatchError { + pin_tim: Pin::TIM_ID, + tim_id: Tim::ID, + }); + } let mut pin = PwmPin { - tim: Tim::TIM, + tim_id: Tim::ID, current_duty: 0, current_lower_limit: 0, current_period: initial_period.into(), @@ -227,15 +81,15 @@ impl PwmPin { enable_peripheral_clock(crate::clock::PeripheralClocks::Ioconfig); sys_cfg .tim_clk_enable() - .modify(|r, w| unsafe { w.bits(r.bits() | pin.pin_and_tim.1.mask_32()) }); + .modify(|r, w| unsafe { w.bits(r.bits() | pin_and_tim.1.mask_32()) }); pin.enable_pwm_a(); pin.set_period(initial_period); - pin + Ok(pin) } #[inline] fn enable_pwm_a(&mut self) { - self.tim + self.tim_id .reg_block() .ctrl() .modify(|_, w| unsafe { w.status_sel().bits(StatusSelPwm::PwmA as u8) }); @@ -243,7 +97,7 @@ impl PwmPin { #[inline] fn enable_pwm_b(&mut self) { - self.tim + self.tim_id .reg_block() .ctrl() .modify(|_, w| unsafe { w.status_sel().bits(StatusSelPwm::PwmB as u8) }); @@ -261,8 +115,8 @@ impl PwmPin { if self.current_period.raw() == 0 { return; } - self.current_rst_val = self.common.sys_clk.raw() / self.common.current_period.raw(); - self.tim + self.current_rst_val = self.sys_clk.raw() / self.current_period.raw(); + self.tim_id .reg_block() .rst_value() .write(|w| unsafe { w.bits(self.current_rst_val) }); @@ -270,7 +124,7 @@ impl PwmPin { #[inline] pub fn disable(&mut self) { - self.tim + self.tim_id .reg_block() .ctrl() .modify(|_, w| w.enable().clear_bit()); @@ -278,7 +132,7 @@ impl PwmPin { #[inline] pub fn enable(&mut self) { - self.tim + self.tim_id .reg_block() .ctrl() .modify(|_, w| w.enable().set_bit()); @@ -299,7 +153,7 @@ impl From> for PwmPin { fn from(other: PwmPin) -> Self { let mut pwmb = Self { mode: PhantomData, - tim: other.tim, + tim_id: other.tim_id, sys_clk: other.sys_clk, current_duty: other.current_duty, current_lower_limit: other.current_lower_limit, @@ -315,7 +169,7 @@ impl From> for PwmPin { fn from(other: PwmPin) -> Self { let mut pwmb = Self { mode: PhantomData, - tim: other.tim, + tim_id: other.tim_id, sys_clk: other.sys_clk, current_duty: other.current_duty, current_lower_limit: other.current_lower_limit, @@ -353,7 +207,7 @@ impl PwmPin { self.current_lower_limit = duty; let pwmb_val: u64 = (self.current_rst_val as u64 * self.current_lower_limit as u64) / DUTY_MAX as u64; - self.tim + self.tim_id .reg_block() .pwmb_value() .write(|w| unsafe { w.bits(pwmb_val as u32) }); @@ -369,7 +223,7 @@ impl PwmPin { self.current_duty = duty; let pwma_val: u64 = (self.current_rst_val as u64 * self.current_duty as u64) / DUTY_MAX as u64; - self.tim + self.tim_id .reg_block() .pwma_value() .write(|w| unsafe { w.bits(pwma_val as u32) }); @@ -396,7 +250,7 @@ impl embedded_hal::pwm::SetDutyCycle for PwmPin { let pwma_val: u64 = (self.current_rst_val as u64 * (DUTY_MAX as u64 - self.current_duty as u64)) / DUTY_MAX as u64; - self.tim + self.tim_id .reg_block() .pwma_value() .write(|w| unsafe { w.bits(pwma_val as u32) }); diff --git a/va108xx-hal/src/spi/mod.rs b/va108xx-hal/src/spi/mod.rs index b705446..bf65854 100644 --- a/va108xx-hal/src/spi/mod.rs +++ b/va108xx-hal/src/spi/mod.rs @@ -50,6 +50,12 @@ pub enum SpiId { } impl SpiId { + /// Unsafely steal a peripheral MMIO block for the given UART. + /// + /// # Safety + /// + /// Circumvents ownership and safety guarantees by the HAL which can lead to data races + /// on cuncurrent usage. pub unsafe fn reg_block(&self) -> &'static SpiRegBlock { unsafe { match self { diff --git a/va108xx-hal/src/spi/pins.rs b/va108xx-hal/src/spi/pins.rs index 515873b..a2afcb1 100644 --- a/va108xx-hal/src/spi/pins.rs +++ b/va108xx-hal/src/spi/pins.rs @@ -57,10 +57,10 @@ macro_rules! hw_cs_multi_pin { $cs_id:path ) => { #[doc = concat!( - "Newtype wrapper to use [Pin] [`", stringify!($pin_ty), - "`] as a HW CS pin for [`", stringify!($spi_id), - "`] with [`", stringify!($cs_id), "`]." - )] + "Newtype wrapper to use [Pin] [`", stringify!($pin_ty), + "`] as a HW CS pin for [`", stringify!($spi_id), + "`] with [`", stringify!($cs_id), "`]." + )] pub struct $name(Pin<$pin_id>); impl $name { diff --git a/va108xx-hal/src/timer.rs b/va108xx-hal/src/timer.rs index 2a43b07..f8155c3 100644 --- a/va108xx-hal/src/timer.rs +++ b/va108xx-hal/src/timer.rs @@ -6,7 +6,6 @@ //! - [Cascade feature example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/simple/examples/cascade.rs) pub use crate::InterruptConfig; use crate::{ - clock::{enable_peripheral_clock, PeripheralClocks}, enable_nvic_interrupt, pac::{self, tim0}, pins::{ @@ -26,7 +25,6 @@ use vorago_shared_periphs::{ Port, }; -const IRQ_DST_NONE: u32 = 0xffffffff; pub static MS_COUNTER: Mutex> = Mutex::new(Cell::new(0)); /// Get the peripheral block of a TIM peripheral given the index. @@ -165,23 +163,25 @@ impl CascadeSource { // Valid TIM and PIN combinations //================================================================================================== -pub trait TimPin { +pub trait TimPin: Sealed { const PORT: Port; const OFFSET: usize; const FUN_SEL: FunSel; - const TIM: Tim; + const TIM_ID: TimId; } -pub trait TimPeripheralMarker { +pub trait TimPeripheralMarker: Sealed { // TIM ID ranging from 0 to 23 for 24 TIM peripherals - const TIM: Tim; + const ID: TimId; } macro_rules! tim_marker { ($TIMX:path, $ID:expr) => { impl TimPeripheralMarker for $TIMX { - const TIM: Tim = Tim($ID); + const ID: TimId = TimId($ID); } + + impl Sealed for $TIMX {} }; } @@ -212,77 +212,6 @@ tim_marker!(pac::Tim23, 23); pub trait ValidTimAndPin: Sealed {} -/* -macro_rules! pin_and_tim { - ($PAX:ident, $ALTFUNC:ident, $ID:expr, $TIMX:path) => { - impl TimPin for Pin<$PAX, $ALTFUNC> - where - $PAX: PinId, - { - const DYN: DynPinId = $PAX::DYN; - } - - impl ValidTimAndPin for (Pin<$PAX, $ALTFUNC>, $TIMX) - where - Pin<$PAX, $ALTFUNC>: TimPin, - $PAX: PinId, - { - } - - impl Sealed for (Pin<$PAX, $ALTFUNC>, $TIMX) {} - }; -} - -pin_and_tim!(PA31, AltFunc2, 23, pac::Tim23); -pin_and_tim!(PA30, AltFunc2, 22, pac::Tim22); -pin_and_tim!(PA29, AltFunc2, 21, pac::Tim21); -pin_and_tim!(PA28, AltFunc2, 20, pac::Tim20); -pin_and_tim!(PA27, AltFunc2, 19, pac::Tim19); -pin_and_tim!(PA26, AltFunc2, 18, pac::Tim18); -pin_and_tim!(PA25, AltFunc2, 17, pac::Tim17); -pin_and_tim!(PA24, AltFunc2, 16, pac::Tim16); - -pin_and_tim!(PA15, AltFunc1, 15, pac::Tim15); -pin_and_tim!(PA14, AltFunc1, 14, pac::Tim14); -pin_and_tim!(PA13, AltFunc1, 13, pac::Tim13); -pin_and_tim!(PA12, AltFunc1, 12, pac::Tim12); -pin_and_tim!(PA11, AltFunc1, 11, pac::Tim11); -pin_and_tim!(PA10, AltFunc1, 10, pac::Tim10); -pin_and_tim!(PA9, AltFunc1, 9, pac::Tim9); -pin_and_tim!(PA8, AltFunc1, 8, pac::Tim8); -pin_and_tim!(PA7, AltFunc1, 7, pac::Tim7); -pin_and_tim!(PA6, AltFunc1, 6, pac::Tim6); -pin_and_tim!(PA5, AltFunc1, 5, pac::Tim5); -pin_and_tim!(PA4, AltFunc1, 4, pac::Tim4); -pin_and_tim!(PA3, AltFunc1, 3, pac::Tim3); -pin_and_tim!(PA2, AltFunc1, 2, pac::Tim2); -pin_and_tim!(PA1, AltFunc1, 1, pac::Tim1); -pin_and_tim!(PA0, AltFunc1, 0, pac::Tim0); - -pin_and_tim!(PB23, AltFunc3, 23, pac::Tim23); -pin_and_tim!(PB22, AltFunc3, 22, pac::Tim22); -pin_and_tim!(PB21, AltFunc3, 21, pac::Tim21); -pin_and_tim!(PB20, AltFunc3, 20, pac::Tim20); -pin_and_tim!(PB19, AltFunc3, 19, pac::Tim19); -pin_and_tim!(PB18, AltFunc3, 18, pac::Tim18); -pin_and_tim!(PB17, AltFunc3, 17, pac::Tim17); -pin_and_tim!(PB16, AltFunc3, 16, pac::Tim16); -pin_and_tim!(PB15, AltFunc3, 15, pac::Tim15); -pin_and_tim!(PB14, AltFunc3, 14, pac::Tim14); -pin_and_tim!(PB13, AltFunc3, 13, pac::Tim13); -pin_and_tim!(PB12, AltFunc3, 12, pac::Tim12); -pin_and_tim!(PB11, AltFunc3, 11, pac::Tim11); -pin_and_tim!(PB10, AltFunc3, 10, pac::Tim10); - -pin_and_tim!(PB6, AltFunc3, 6, pac::Tim6); -pin_and_tim!(PB5, AltFunc3, 5, pac::Tim5); -pin_and_tim!(PB4, AltFunc3, 4, pac::Tim4); -pin_and_tim!(PB3, AltFunc3, 3, pac::Tim3); -pin_and_tim!(PB2, AltFunc3, 2, pac::Tim2); -pin_and_tim!(PB1, AltFunc3, 1, pac::Tim1); -pin_and_tim!(PB0, AltFunc3, 0, pac::Tim0); -*/ - macro_rules! pin_and_tim { ($Px:ident, $FunSel:path, $ID:expr) => { impl TimPin for Pin<$Px> @@ -292,19 +221,8 @@ macro_rules! pin_and_tim { const PORT: Port = $Px::PORT; const OFFSET: usize = $Px::OFFSET; const FUN_SEL: FunSel = $FunSel; - const TIM: Tim = Tim($ID); + const TIM_ID: TimId = TimId($ID); } - - /* - impl ValidTimAndPin for (Pin<$Px>, $TIMX) - where - Pin<$PAX, $ALTFUNC>: TimPin, - $PAX: PinId, - { - } - - impl Sealed for (Pin<$PAX, $ALTFUNC>, $TIMX) {} - */ }; } @@ -375,7 +293,7 @@ pub type TimRegBlock = tim0::RegisterBlock; /// "control" over the corresponding pin ID, i.e. it must guarantee that a each /// pin ID is a singleton. pub unsafe trait TimRegInterface { - fn tim_id(&self) -> u8; + fn raw_id(&self) -> u8; const PORT_BASE: *const tim0::RegisterBlock = pac::Tim0::ptr() as *const _; @@ -383,12 +301,12 @@ pub unsafe trait TimRegInterface { /// memory mapped peripheral depending on the TIM ID. #[inline(always)] fn reg_block(&self) -> &TimRegBlock { - unsafe { &*Self::PORT_BASE.offset(self.tim_id() as isize) } + unsafe { &*Self::PORT_BASE.offset(self.raw_id() as isize) } } #[inline(always)] fn mask_32(&self) -> u32 { - 1 << self.tim_id() + 1 << self.raw_id() } /// Clear the reset bit of the TIM, holding it in reset @@ -419,62 +337,44 @@ pub unsafe trait TimRegInterface { } } -#[derive(Debug)] -pub struct Tim(u8); +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct TimId(u8); -impl Tim { - pub const fn raw_id(&self) -> u8 { +unsafe impl TimRegInterface for TimId { + fn raw_id(&self) -> u8 { self.0 } } -unsafe impl TimRegInterface for Tim { - fn tim_id(&self) -> u8 { - self.0 - } -} - -pub(crate) struct TimDynRegister { - pub(crate) tim_id: u8, -} - -unsafe impl TimRegInterface for TimDynRegister { - #[inline(always)] - fn tim_id(&self) -> u8 { - self.tim_id - } -} - //================================================================================================== // Timers //================================================================================================== /// Hardware timers pub struct CountdownTimer { - tim: Tim, + tim: TimId, curr_freq: Hertz, sys_clk: Hertz, rst_val: u32, last_cnt: u32, - listening: bool, } unsafe impl TimRegInterface for CountdownTimer { - fn tim_id(&self) -> u8 { + fn raw_id(&self) -> u8 { self.tim.0 } } impl CountdownTimer { /// Configures a TIM peripheral as a periodic count down timer - pub fn new(sys_clk: impl Into, tim_id: u8) -> Self { - enable_tim_clk(tim_id); + pub fn new(sys_clk: Hertz, _tim: Tim) -> Self { + enable_tim_clk(Tim::ID.raw_id()); let cd_timer = CountdownTimer { - tim: Tim(tim_id), - sys_clk: sys_clk.into(), + tim: Tim::ID, + sys_clk, rst_val: 0, curr_freq: 0.Hz(), - listening: false, last_cnt: 0, }; cd_timer @@ -485,52 +385,33 @@ impl CountdownTimer { cd_timer } - /// Listen for events. Depending on the IRQ configuration, this also activates the IRQ in the - /// IRQSEL peripheral for the provided interrupt and unmasks the interrupt - pub fn listen(&mut self, event: Event, irq_cfg: InterruptConfig) { - enable_peripheral_clock(PeripheralClocks::Irqsel); + pub fn enable_interupt(&mut self, irq_cfg: InterruptConfig) { if irq_cfg.route { let irqsel = unsafe { pac::Irqsel::steal() }; irqsel - .tim0(self.tim_id() as usize) + .tim0(self.raw_id() as usize) .write(|w| unsafe { w.bits(irq_cfg.id as u32) }); } if irq_cfg.enable_in_nvic { unsafe { enable_nvic_interrupt(irq_cfg.id) }; } - match event { - Event::TimeOut => { - cortex_m::peripheral::NVIC::mask(irq_cfg.id); - self.irq_cfg = Some(irq_cfg); - self.listening = true; - } - } - } - - pub fn unlisten(&mut self, event: Event, unroute_irq: bool) { - match event { - Event::TimeOut => { - enable_peripheral_clock(PeripheralClocks::Irqsel); - if unroute_irq { - let irqsel = unsafe { pac::Irqsel::steal() }; - irqsel - .tim0(self.tim_id() as usize) - .write(|w| unsafe { w.bits(IRQ_DST_NONE) }); - } - self.disable_interrupt(); - self.listening = false; - } - } - } - - #[inline(always)] - pub fn enable_interrupt(&mut self) { self.tim .reg_block() .ctrl() .modify(|_, w| w.irq_enb().set_bit()); } + #[inline(always)] + pub fn enable(&mut self) { + self.tim + .reg_block() + .enable() + .write(|w| unsafe { w.bits(1) }); + } + + /// This function only clears the interrupt enable bit. + /// + /// It does not mask the interrupt in the NVIC or un-route the IRQ. #[inline(always)] pub fn disable_interrupt(&mut self) { self.tim @@ -539,15 +420,16 @@ impl CountdownTimer { .modify(|_, w| w.irq_enb().clear_bit()); } - pub fn release(self, syscfg: &mut pac::Sysconfig) -> Tim { + /// Disables the TIM and the dedicated TIM clock. + pub fn stop(self) { self.tim .reg_block() .ctrl() .write(|w| w.enable().clear_bit()); + let syscfg = unsafe { va108xx::Sysconfig::steal() }; syscfg .tim_clk_enable() - .modify(|r, w| unsafe { w.bits(r.bits() & !(1 << self.tim_id())) }); - self.tim + .modify(|r, w| unsafe { w.bits(r.bits() & !(1 << self.raw_id())) }); } /// Load the count down timer with a timeout but do not start it. @@ -583,20 +465,6 @@ impl CountdownTimer { self.tim.reg_block().cnt_value().read().bits() } - #[inline(always)] - pub fn enable(&mut self) { - if let Some(irq_cfg) = self.irq_cfg { - self.enable_interrupt(); - if irq_cfg.enable_in_nvic { - unsafe { enable_nvic_interrupt(irq_cfg.id) }; - } - } - self.tim - .reg_block() - .enable() - .write(|w| unsafe { w.bits(1) }); - } - #[inline(always)] pub fn disable(&mut self) { self.tim @@ -686,10 +554,6 @@ impl CountdownTimer { pub fn curr_freq(&self) -> Hertz { self.curr_freq } - - pub fn listening(&self) -> bool { - self.listening - } } /// CountDown implementation for TIMx @@ -783,10 +647,7 @@ impl embedded_hal::delay::DelayNs for CountdownTimer { } } -pub fn set_up_ms_delay_provider( - sys_clk: impl Into, - tim: impl TimPeripheralMarker, -) -> CountdownTimer { +pub fn set_up_ms_delay_provider(sys_clk: Hertz, tim: impl TimPeripheralMarker) -> CountdownTimer { let mut provider = CountdownTimer::new(sys_clk, tim); provider.start(1000.Hz()); provider diff --git a/va108xx-hal/src/uart/mod.rs b/va108xx-hal/src/uart/mod.rs index f070994..f4bd925 100644 --- a/va108xx-hal/src/uart/mod.rs +++ b/va108xx-hal/src/uart/mod.rs @@ -30,7 +30,7 @@ use crate::{ }; use embedded_hal_nb::serial::Read; -#[derive(Debug, Clone, Copy)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum UartId { A = 0, @@ -38,7 +38,12 @@ pub enum UartId { } impl UartId { - // TODO: Safety docs. + /// Unsafely steal a peripheral MMIO block for the given UART. + /// + /// # Safety + /// + /// Circumvents ownership and safety guarantees by the HAL which can lead to data races + /// on cuncurrent usage. pub const unsafe fn reg_block(&self) -> &'static uart_base::RegisterBlock { match self { UartId::A => unsafe { &*pac::Uarta::PTR }, @@ -494,6 +499,11 @@ impl UartPeripheralMarker for pac::Uartb { } } +#[derive(Debug, thiserror::Error)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[error("UART ID missmatch between peripheral and pins.")] +pub struct UartIdMissmatchError; + //================================================================================================== // UART implementation //================================================================================================== @@ -512,7 +522,7 @@ impl Uart { pins: (Tx, Rx), config: Config, irq_cfg: InterruptConfig, - ) -> Self { + ) -> Result { Self::new(sys_clk, uart, pins, config, Some(irq_cfg)) } @@ -522,7 +532,7 @@ impl Uart { uart: UartI, pins: (Tx, Rx), config: Config, - ) -> Self { + ) -> Result { Self::new(sys_clk, uart, pins, config, None) } @@ -540,11 +550,14 @@ impl Uart { /// any interrupt support is used, this can be [None]. pub fn new( sys_clk: Hertz, - uart: UartI, - pins: (TxPinI, RxPinI), + _uart: UartI, + _pins: (TxPinI, RxPinI), config: Config, opt_irq_cfg: Option, - ) -> Self { + ) -> Result { + if UartI::ID != TxPinI::UART_ID || UartI::ID != RxPinI::UART_ID { + return Err(UartIdMissmatchError); + } crate::clock::enable_peripheral_clock(UartI::PERIPH_SEL); let reg_block = unsafe { UartI::reg_block() }; @@ -608,11 +621,10 @@ impl Uart { } } - // TODO: Validity checks. - Uart { + Ok(Uart { tx: Tx::new(UartI::ID), rx: Rx::new(UartI::ID), - } + }) } #[inline] @@ -973,7 +985,7 @@ impl Tx { /// You must ensure that only registers related to the operation of the TX side are used. #[inline(always)] pub unsafe fn regs(&self) -> &'static uart_base::RegisterBlock { - &self.0.reg_block() + self.0.reg_block() } #[inline(always)] diff --git a/va108xx-hal/src/uart/rx_asynch.rs b/va108xx-hal/src/uart/rx_asynch.rs index 0ef374c..78857a0 100644 --- a/va108xx-hal/src/uart/rx_asynch.rs +++ b/va108xx-hal/src/uart/rx_asynch.rs @@ -26,7 +26,7 @@ use embedded_io::ErrorType; use portable_atomic::AtomicBool; use va108xx::uarta as uart_base; -use super::{Rx, UartErrors, UartId, UartPeripheralMarker}; +use super::{Rx, UartErrors, UartId}; static UART_RX_WAKERS: [AtomicWaker; 2] = [const { AtomicWaker::new() }; 2]; static RX_READ_ACTIVE: [AtomicBool; 2] = [const { AtomicBool::new(false) }; 2]; diff --git a/vorago-shared-periphs/Cargo.toml b/vorago-shared-periphs/Cargo.toml index 1cbe8ea..805a85d 100644 --- a/vorago-shared-periphs/Cargo.toml +++ b/vorago-shared-periphs/Cargo.toml @@ -4,18 +4,27 @@ version = "0.1.0" edition = "2024" [dependencies] +cortex-m = { version = "0.7" } cfg-if = "1" derive-mmio = { path = "../../../ROMEO/derive-mmio" } bitbybit = "1.3" arbitrary-int = "1.3" static_assertions = "1.1" embedded-hal = { version = "1.0" } +embedded-hal-async = "1" thiserror = { version = "2", default-features = false } paste = "1" +embassy-sync = "0.6" defmt = { version = "1", optional = true } +va108xx = { version = "0.5", default-features = false, optional = true } + +[target.'cfg(all(target_arch = "arm", target_os = "none"))'.dependencies] +portable-atomic = { version = "1", features = ["unsafe-assume-single-core"] } +[target.'cfg(not(all(target_arch = "arm", target_os = "none")))'.dependencies] +portable-atomic = "1" [features] -vor1x = ["_family-selected"] +vor1x = ["_family-selected", "va108xx"] vor4x = ["_family-selected"] _family-selected = [] diff --git a/vorago-shared-periphs/src/gpio/asynch.rs b/vorago-shared-periphs/src/gpio/asynch.rs new file mode 100644 index 0000000..3adf288 --- /dev/null +++ b/vorago-shared-periphs/src/gpio/asynch.rs @@ -0,0 +1,332 @@ +//! # Async GPIO functionality for the Vorago GPIO peripherals. +//! +//! This module provides the [InputPinAsync] and [InputDynPinAsync] which both implement +//! the [embedded_hal_async::digital::Wait] trait. These types allow for asynchronous waiting +//! on GPIO pins. Please note that this module does not specify/declare the interrupt handlers +//! which must be provided for async support to work. However, it provides the +//! [on_interrupt_for_async_gpio_for_port] generic interrupt handler. This should be called in all +//! IRQ functions which handle any GPIO interrupts with the corresponding [Port] argument. +//! +//! # Example +//! +//! - [Async GPIO example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/embassy/src/bin/async-gpio.rs) +use core::future::Future; + +use embassy_sync::waitqueue::AtomicWaker; +use embedded_hal_async::digital::Wait; +use portable_atomic::AtomicBool; + +use crate::{InterruptConfig, NUM_PORT_A, NUM_PORT_B}; + +pub use super::ll::InterruptEdge; +use super::{Input, Pin, PinIdProvider, Port, ll::PinId}; + +static WAKERS_FOR_PORT_A: [AtomicWaker; NUM_PORT_A] = [const { AtomicWaker::new() }; NUM_PORT_A]; +static WAKERS_FOR_PORT_B: [AtomicWaker; NUM_PORT_B] = [const { AtomicWaker::new() }; NUM_PORT_B]; +static EDGE_DETECTION_PORT_A: [AtomicBool; NUM_PORT_A] = + [const { AtomicBool::new(false) }; NUM_PORT_A]; +static EDGE_DETECTION_PORT_B: [AtomicBool; NUM_PORT_B] = + [const { AtomicBool::new(false) }; NUM_PORT_B]; + +/// Generic interrupt handler for GPIO interrupts on a specific port to support async functionalities +/// +/// This function should be called in all interrupt handlers which handle any GPIO interrupts +/// matching the [Port] argument. +/// The handler will wake the corresponding wakers for the pins that triggered an interrupts +/// as well as update the static edge detection structures. This allows the pin future tocomplete +/// complete async operations. +pub fn on_interrupt_for_async_gpio_for_port(port: Port) { + let gpio = unsafe { port.steal_gpio() }; + + let irq_enb = gpio.read_irq_enb(); + let edge_status = gpio.read_edge_status(); + let (wakers, edge_detection) = match port { + Port::A => (WAKERS_FOR_PORT_A.as_ref(), EDGE_DETECTION_PORT_A.as_ref()), + Port::B => (WAKERS_FOR_PORT_B.as_ref(), EDGE_DETECTION_PORT_B.as_ref()), + }; + + on_interrupt_for_port(irq_enb, edge_status, wakers, edge_detection); +} + +#[inline] +fn on_interrupt_for_port( + mut irq_enb: u32, + edge_status: u32, + wakers: &'static [AtomicWaker], + edge_detection: &'static [AtomicBool], +) { + while irq_enb != 0 { + let bit_pos = irq_enb.trailing_zeros() as usize; + let bit_mask = 1 << bit_pos; + + wakers[bit_pos].wake(); + + if edge_status & bit_mask != 0 { + edge_detection[bit_pos].store(true, core::sync::atomic::Ordering::Relaxed); + + // Clear the processed bit + irq_enb &= !bit_mask; + } + } +} + +/// Input pin future which implements the [Future] trait. +/// +/// Generally, you want to use the [InputPinAsync] or [InputDynPinAsync] types instead of this +/// which also implements the [embedded_hal_async::digital::Wait] trait. However, access to this +/// struture is granted to allow writing custom async structures. +pub struct InputPinFuture { + id: PinId, + waker_group: &'static [AtomicWaker], + edge_detection_group: &'static [AtomicBool], +} + +impl InputPinFuture { + #[inline] + pub fn pin_group_to_waker_and_edge_detection_group( + group: Port, + ) -> (&'static [AtomicWaker], &'static [AtomicBool]) { + match group { + Port::A => (WAKERS_FOR_PORT_A.as_ref(), EDGE_DETECTION_PORT_A.as_ref()), + Port::B => (WAKERS_FOR_PORT_B.as_ref(), EDGE_DETECTION_PORT_B.as_ref()), + } + } + + pub fn new_with_input_pin( + pin: &mut Input, + irq: va108xx::Interrupt, + edge: InterruptEdge, + ) -> Self { + let (waker_group, edge_detection_group) = + Self::pin_group_to_waker_and_edge_detection_group(pin.port()); + edge_detection_group[pin.offset() as usize] + .store(false, core::sync::atomic::Ordering::Relaxed); + pin.configure_edge_interrupt(edge).unwrap(); + #[cfg(feature = "vor1x")] + pin.enable_interrupt(InterruptConfig::new(irq, true, true)); + Self { + port: pin.port(), + offset: pin.offset(), + waker_group, + edge_detection_group, + } + } +} + +impl Drop for InputPinFuture { + fn drop(&mut self) { + // The API ensures that we actually own the pin, so stealing it here is okay. + unsafe { DynPin::steal(self.pin_id) }.disable_interrupt(false); + } +} + +impl Future for InputPinFuture { + type Output = (); + fn poll( + self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> core::task::Poll { + let idx = self.pin_id.num() as usize; + self.waker_group[idx].register(cx.waker()); + if self.edge_detection_group[idx].swap(false, core::sync::atomic::Ordering::Relaxed) { + return core::task::Poll::Ready(()); + } + core::task::Poll::Pending + } +} + +pub struct InputPinAsync { + pin: Input, + #[cfg(feature = "vor1x")] + irq: va108xx::Interrupt, +} + +impl InputPinAsync { + /// Create a new asynchronous input pin from a [DynPin]. The interrupt ID to be used must be + /// passed as well and is used to route and enable the interrupt. + /// + /// Please note that the interrupt handler itself must be provided by the user and the + /// generic [on_interrupt_for_async_gpio_for_port] function must be called inside that function + /// for the asynchronous functionality to work. + #[cfg(feature = "vor1x")] + pub fn new(pin: Input, irq: va108xx::Interrupt) -> Self { + Self { pin, irq } + } + + /// Asynchronously wait until the pin is high. + /// + /// This returns immediately if the pin is already high. + pub async fn wait_for_high(&mut self) { + // Unwrap okay, checked pin in constructor. + let fut = + InputPinFuture::new_with_input_pin(&mut self.pin, self.irq, InterruptEdge::LowToHigh) + .unwrap(); + if self.pin.is_high().unwrap() { + return; + } + fut.await; + } + + /// Asynchronously wait until the pin is low. + /// + /// This returns immediately if the pin is already high. + pub async fn wait_for_low(&mut self) { + // Unwrap okay, checked pin in constructor. + let fut = + InputPinFuture::new_with_input_pin(&mut self.pin, self.irq, InterruptEdge::HighToLow) + .unwrap(); + if self.pin.is_low().unwrap() { + return; + } + fut.await; + } + + /// Asynchronously wait until the pin sees a falling edge. + pub async fn wait_for_falling_edge(&mut self) { + // Unwrap okay, checked pin in constructor. + InputPinFuture::new_with_input_pin(&mut self.pin, self.irq, InterruptEdge::HighToLow) + .unwrap() + .await; + } + + /// Asynchronously wait until the pin sees a rising edge. + pub async fn wait_for_rising_edge(&mut self) { + // Unwrap okay, checked pin in constructor. + InputPinFuture::new_with_input_pin(&mut self.pin, self.irq, InterruptEdge::LowToHigh) + .unwrap() + .await; + } + + /// Asynchronously wait until the pin sees any edge (either rising or falling). + pub async fn wait_for_any_edge(&mut self) { + // Unwrap okay, checked pin in constructor. + InputPinFuture::new_with_input_pin(&mut self.pin, self.irq, InterruptEdge::BothEdges) + .unwrap() + .await; + } + + pub fn release(self) -> Input { + self.pin + } +} + +impl embedded_hal::digital::ErrorType for InputPinAsync { + type Error = core::convert::Infallible; +} + +impl Wait for InputPinAsync { + 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(()) + } +} + +pub struct InputPinAsync { + pin: Pin>, + irq: pac::Interrupt, +} + +impl InputPinAsync { + /// Create a new asynchronous input pin from a typed [Pin]. The interrupt ID to be used must be + /// passed as well and is used to route and enable the interrupt. + /// + /// Please note that the interrupt handler itself must be provided by the user and the + /// generic [on_interrupt_for_async_gpio_for_port] function must be called inside that function + /// for the asynchronous functionality to work. + pub fn new(pin: Pin>, irq: pac::Interrupt) -> Self { + Self { pin, irq } + } + + /// Asynchronously wait until the pin is high. + /// + /// This returns immediately if the pin is already high. + pub async fn wait_for_high(&mut self) { + let fut = InputPinFuture::new_with_pin(&mut self.pin, self.irq, InterruptEdge::LowToHigh); + if self.pin.is_high() { + return; + } + fut.await; + } + + /// Asynchronously wait until the pin is low. + /// + /// This returns immediately if the pin is already high. + pub async fn wait_for_low(&mut self) { + let fut = InputPinFuture::new_with_pin(&mut self.pin, self.irq, InterruptEdge::HighToLow); + if self.pin.is_low() { + return; + } + fut.await; + } + + /// Asynchronously wait until the pin sees falling edge. + pub async fn wait_for_falling_edge(&mut self) { + // Unwrap okay, checked pin in constructor. + InputPinFuture::new_with_pin(&mut self.pin, self.irq, InterruptEdge::HighToLow).await; + } + + /// Asynchronously wait until the pin sees rising edge. + pub async fn wait_for_rising_edge(&mut self) { + // Unwrap okay, checked pin in constructor. + InputPinFuture::new_with_pin(&mut self.pin, self.irq, InterruptEdge::LowToHigh).await; + } + + /// Asynchronously wait until the pin sees any edge (either rising or falling). + pub async fn wait_for_any_edge(&mut self) { + InputPinFuture::new_with_pin(&mut self.pin, self.irq, InterruptEdge::BothEdges).await; + } + + pub fn release(self) -> Pin> { + self.pin + } +} +impl embedded_hal::digital::ErrorType for InputPinAsync { + type Error = core::convert::Infallible; +} + +impl Wait for InputPinAsync { + 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(()) + } +} diff --git a/vorago-shared-periphs/src/gpio/ll.rs b/vorago-shared-periphs/src/gpio/ll.rs index c378020..38ace2c 100644 --- a/vorago-shared-periphs/src/gpio/ll.rs +++ b/vorago-shared-periphs/src/gpio/ll.rs @@ -5,37 +5,63 @@ pub use crate::Port; pub use crate::ioconfig::regs::Pull; use crate::ioconfig::regs::{FunSel, IoConfig, MmioIoConfig}; -pub struct LowLevelGpio { - gpio: super::regs::MmioGpio<'static>, - ioconfig: MmioIoConfig<'static>, +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum InterruptEdge { + HighToLow, + LowToHigh, + BothEdges, +} + +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum InterruptLevel { + Low = 0, + High = 1, +} + +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PinId { port: Port, offset: usize, } -impl LowLevelGpio { - pub fn new(port: Port, offset: usize) -> Result { +impl PinId { + pub const fn new(port: Port, offset: usize) -> Result { if offset >= port.max_offset() { - return Err(InvalidOffsetError { - offset, - port: Port::A, - }); + return Err(InvalidOffsetError { offset, port }); } - Ok(LowLevelGpio { - gpio: super::regs::Gpio::new_mmio(port), - ioconfig: IoConfig::new_mmio(), - port, - offset, - }) + Ok(PinId { port, offset }) } - #[inline] - pub fn port(&self) -> Port { + pub const fn port(&self) -> Port { self.port } + pub const fn offset(&self) -> usize { + self.port + } +} + +pub struct LowLevelGpio { + gpio: super::regs::MmioGpio<'static>, + ioconfig: MmioIoConfig<'static>, + id: PinId, +} + +impl LowLevelGpio { + pub fn new(id: PinId) -> Self { + LowLevelGpio { + gpio: super::regs::Gpio::new_mmio(id.port), + ioconfig: IoConfig::new_mmio(), + id, + } + } + #[inline] - pub fn offset(&self) -> usize { - self.offset + pub fn id(&self) -> PinId { + self.id } pub fn configure_as_input_floating(&mut self) { @@ -54,7 +80,7 @@ impl LowLevelGpio { }); } self.gpio.modify_dir(|mut dir| { - dir &= !(1 << self.offset); + dir &= !self.mask_32(); dir }); } @@ -76,7 +102,7 @@ impl LowLevelGpio { }); } self.gpio.modify_dir(|mut dir| { - dir &= !(1 << self.offset); + dir &= !self.mask_32(); dir }); } @@ -101,7 +127,7 @@ impl LowLevelGpio { PinState::High => self.gpio.write_set_out(1 << self.offset), } self.gpio.modify_dir(|mut dir| { - dir |= 1 << self.offset; + dir |= self.mask_32(); dir }); } @@ -160,12 +186,12 @@ impl LowLevelGpio { #[inline] pub fn set_high(&mut self) { - self.gpio.write_set_out(1 << self.offset); + self.gpio.write_set_out(self.mask_32()); } #[inline] pub fn set_low(&mut self) { - self.gpio.write_clr_out(1 << self.offset); + self.gpio.write_clr_out(self.mask_32()); } #[inline] @@ -180,6 +206,57 @@ impl LowLevelGpio { #[inline] pub fn toggle(&mut self) { - self.gpio.write_tog_out(1 << self.offset); + self.gpio.write_tog_out(self.mask_32()); + } + + #[cfg(feature = "vor1x")] + pub fn enable_interrupt(&mut self, irq_cfg: crate::InterruptConfig) { + if irq_cfg.route { + self.configure_irqsel(irq_cfg.id); + } + if irq_cfg.enable_in_nvic { + unsafe { crate::enable_nvic_interrupt(irq_cfg.id) }; + } + self.gpio.modify_irq_enb(|mut value| { + value |= self.mask_32(); + value + }); + } + + /// Only useful for interrupt pins. Configure whether to use edges or level as interrupt soure + /// When using edge mode, it is possible to generate interrupts on both edges as well + #[inline] + pub fn configure_edge_interrupt(&mut self, edge_type: InterruptEdge) { + unsafe { + self.gpio.modify_irq_sen(|mut value| { + value &= !self.mask_32(); + value + }); + match edge_type { + InterruptEdge::HighToLow => { + self.gpio.modify_irq_evt(|mut value| { + value &= !self.mask_32(); + value + }); + } + InterruptEdge::LowToHigh => { + self.gpio.modify_irq_evt(|mut value| { + value |= self.mask_32(); + value + }); + } + InterruptEdge::BothEdges => { + self.gpio.modify_irq_edge(|mut value| { + value |= self.mask_32(); + value + }); + } + } + } + } + + #[inline(always)] + pub const fn mask_32(&self) -> u32 { + 1 << self.offset } } diff --git a/vorago-shared-periphs/src/gpio/mod.rs b/vorago-shared-periphs/src/gpio/mod.rs index af2c191..8a6e331 100644 --- a/vorago-shared-periphs/src/gpio/mod.rs +++ b/vorago-shared-periphs/src/gpio/mod.rs @@ -5,19 +5,19 @@ pub use ll::{Port, Pull}; use crate::ioconfig::regs::FunSel; +pub mod asynch; pub mod ll; pub mod regs; -pub trait PinId { - const PORT: Port; - const OFFSET: usize; +pub trait PinIdProvider { + const ID: ll::PinId; } -pub struct Pin { +pub struct Pin { phantom: core::marker::PhantomData, } -impl Pin { +impl Pin { #[allow(clippy::new_without_default)] pub const fn new() -> Self { Self { @@ -29,8 +29,8 @@ impl Pin { pub struct Output(ll::LowLevelGpio); impl Output { - pub fn new(_pin: Pin, init_level: PinState) -> Self { - let mut ll = ll::LowLevelGpio::new(I::PORT, I::OFFSET).unwrap(); + pub fn new(_pin: Pin, init_level: PinState) -> Self { + let mut ll = ll::LowLevelGpio::new(I::ID); ll.configure_as_output_push_pull(init_level); Output(ll) } @@ -101,14 +101,14 @@ impl embedded_hal::digital::StatefulOutputPin for Output { pub struct Input(ll::LowLevelGpio); impl Input { - pub fn new_floating(_pin: Pin) -> Self { - let mut ll = ll::LowLevelGpio::new(I::PORT, I::OFFSET).unwrap(); + pub fn new_floating(_pin: Pin) -> Self { + let mut ll = ll::LowLevelGpio::new(I::ID); ll.configure_as_input_floating(); Input(ll) } - pub fn new_with_pull(_pin: Pin, pull: Pull) -> Self { - let mut ll = ll::LowLevelGpio::new(I::PORT, I::OFFSET).unwrap(); + pub fn new_with_pull(_pin: Pin, pull: Pull) -> Self { + let mut ll = ll::LowLevelGpio::new(I::ID); ll.configure_as_input_with_pull(pull); Input(ll) } @@ -122,6 +122,14 @@ impl Input { pub fn offset(&self) -> usize { self.0.offset() } + + #[cfg(feature = "vor1x")] + pub fn enable_interrupt(&mut self, irq_cfg: crate::InterruptConfig) { + self.0.enable_interrupt(irq_cfg); + } + pub fn configure_edge_interrupt(&mut self, edge: ll::InterruptEdge) { + self.0.configure_edge_interrupt(edge); + } } impl embedded_hal::digital::ErrorType for Input { @@ -161,8 +169,8 @@ pub struct Flex { } impl Flex { - pub fn new(_pin: Pin) -> Self { - let mut ll = ll::LowLevelGpio::new(I::PORT, I::OFFSET).unwrap(); + pub fn new(_pin: Pin) -> Self { + let mut ll = ll::LowLevelGpio::new(I::ID); ll.configure_as_input_floating(); Flex { ll, @@ -270,8 +278,8 @@ pub struct IoPeriphPin { } impl IoPeriphPin { - pub fn new(_pin: Pin, fun_sel: FunSel, pull: Option) -> Self { - let mut ll = ll::LowLevelGpio::new(I::PORT, I::OFFSET).unwrap(); + pub fn new(_pin: Pin, fun_sel: FunSel, pull: Option) -> Self { + let mut ll = ll::LowLevelGpio::new(I::ID); ll.configure_as_peripheral_pin(fun_sel, pull); IoPeriphPin { ll, fun_sel } } diff --git a/vorago-shared-periphs/src/gpio/regs.rs b/vorago-shared-periphs/src/gpio/regs.rs index b23beac..f87d178 100644 --- a/vorago-shared-periphs/src/gpio/regs.rs +++ b/vorago-shared-periphs/src/gpio/regs.rs @@ -51,10 +51,13 @@ pub struct Gpio { irq_edge: u32, irq_evt: u32, irq_enb: u32, + /// Raw interrupt status. This register is not latched and may not indicated edge sensitive + /// events. #[mmio(PureRead)] irq_raw: u32, + /// Read only register which shows the AND of IRQ_RAW and IRQ_ENB. Called IRQ_END by Vorago. #[mmio(PureRead)] - irq_end: u32, + irq_active: u32, #[mmio(PureRead)] edge_status: u32, diff --git a/vorago-shared-periphs/src/lib.rs b/vorago-shared-periphs/src/lib.rs index b0d0ac3..f1ecc1c 100644 --- a/vorago-shared-periphs/src/lib.rs +++ b/vorago-shared-periphs/src/lib.rs @@ -53,6 +53,15 @@ impl Port { Port::G => NUM_PORT_G, } } + + /// Unsafely steal the GPIO peripheral block for the given port. + /// + /// # Safety + /// + /// Circumvents ownership and safety guarantees by the HAL. + pub unsafe fn steal_gpio(&self) -> gpio::regs::MmioGpio<'static> { + gpio::regs::Gpio::new_mmio(self) + } } #[derive(Debug, thiserror::Error)] @@ -63,6 +72,54 @@ pub struct InvalidOffsetError { port: Port, } +/// Generic interrupt config which can be used to specify whether the HAL driver will +/// use the IRQSEL register to route an interrupt, and whether the IRQ will be unmasked in the +/// Cortex-M0 NVIC. Both are generally necessary for IRQs to work, but the user might want to +/// perform those steps themselves. +#[cfg(feature = "vor1x")] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct InterruptConfig { + /// Interrupt target vector. Should always be set, might be required for disabling IRQs + pub id: va108xx::Interrupt, + /// Specfiy whether IRQ should be routed to an IRQ vector using the IRQSEL peripheral. + pub route: bool, + /// Specify whether the IRQ is unmasked in the Cortex-M NVIC. If an interrupt is used for + /// multiple purposes, the user can enable the interrupts themselves. + pub enable_in_nvic: bool, +} + +#[cfg(feature = "vor1x")] +impl InterruptConfig { + pub fn new(id: va108xx::Interrupt, route: bool, enable_in_nvic: bool) -> Self { + InterruptConfig { + id, + route, + enable_in_nvic, + } + } +} + +/// Enable a specific interrupt using the NVIC peripheral. +/// +/// # Safety +/// +/// This function is `unsafe` because it can break mask-based critical sections. +#[cfg(feature = "vor1x")] +#[inline] +pub unsafe fn enable_nvic_interrupt(irq: va108xx::Interrupt) { + unsafe { + cortex_m::peripheral::NVIC::unmask(irq); + } +} + +/// Disable a specific interrupt using the NVIC peripheral. +#[cfg(feature = "vor1x")] +#[inline] +pub fn disable_nvic_interrupt(irq: va108xx::Interrupt) { + cortex_m::peripheral::NVIC::mask(irq); +} + #[allow(dead_code)] pub(crate) mod sealed { pub trait Sealed {}