Bugfix and improvements for async GPIO

This commit is contained in:
Robin Müller 2025-02-14 17:09:37 +01:00
parent a65f4039ee
commit 8b55d0923f
9 changed files with 153 additions and 111 deletions

View File

@ -27,8 +27,8 @@ embassy-executor = { version = "0.7", features = [
"executor-interrupt" "executor-interrupt"
]} ]}
va108xx-hal = "0.9" va108xx-hal = { version = "0.9", path = "../../va108xx-hal" }
va108xx-embassy = "0.1" va108xx-embassy = { version = "0.1", path = "../../va108xx-embassy" }
[features] [features]
default = ["ticks-hz-1_000", "va108xx-embassy/irq-oc30-oc31"] default = ["ticks-hz-1_000", "va108xx-embassy/irq-oc30-oc31"]

View File

@ -14,7 +14,9 @@ use embedded_hal_async::digital::Wait;
use panic_rtt_target as _; use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print}; use rtt_target::{rprintln, rtt_init_print};
use va108xx_embassy::embassy; use va108xx_embassy::embassy;
use va108xx_hal::gpio::{on_interrupt_for_asynch_gpio, InputDynPinAsync, InputPinAsync, PinsB}; use va108xx_hal::gpio::{
on_interrupt_for_async_gpio_for_port, InputDynPinAsync, InputPinAsync, PinsB, Port,
};
use va108xx_hal::{ use va108xx_hal::{
gpio::{DynPin, PinsA}, gpio::{DynPin, PinsA},
pac::{self, interrupt}, pac::{self, interrupt},
@ -244,15 +246,16 @@ async fn output_task(
} }
// PB22 to PB23 can be handled by both OC10 and OC11 depending on configuration. // PB22 to PB23 can be handled by both OC10 and OC11 depending on configuration.
#[interrupt] #[interrupt]
#[allow(non_snake_case)] #[allow(non_snake_case)]
fn OC10() { fn OC10() {
on_interrupt_for_asynch_gpio(); on_interrupt_for_async_gpio_for_port(Port::A);
on_interrupt_for_async_gpio_for_port(Port::B);
} }
// This interrupt only handles PORT B interrupts.
#[interrupt] #[interrupt]
#[allow(non_snake_case)] #[allow(non_snake_case)]
fn OC11() { fn OC11() {
on_interrupt_for_asynch_gpio(); on_interrupt_for_async_gpio_for_port(Port::B);
} }

View File

@ -20,7 +20,7 @@ embassy-time-queue-utils = "0.1"
once_cell = { version = "1", default-features = false, features = ["critical-section"] } once_cell = { version = "1", default-features = false, features = ["critical-section"] }
va108xx-hal = "0.9" va108xx-hal = { version = "0.9", path = "../va108xx-hal" }
[target.'cfg(all(target_arch = "arm", target_os = "none"))'.dependencies] [target.'cfg(all(target_arch = "arm", target_os = "none"))'.dependencies]
portable-atomic = { version = "1", features = ["unsafe-assume-single-core"] } portable-atomic = { version = "1", features = ["unsafe-assume-single-core"] }

View File

@ -17,6 +17,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## Changed ## Changed
- Missing GPIO API replacements from `x` to `configure_x` - Missing GPIO API replacements from `x` to `configure_x`
- Renamed GPIO `DynGroup` to `Port`
- Rename generic GPIO interrupt handler into `on_interrupt_for_asynch_gpio`
into `on_interrupt_for_async_gpio_for_port` which expects a Port argument
## Fixed
- Bug in async GPIO interrupt handler where all enabled interrupts, even the ones which might
be unrelated to the pin, were disabled.
## [v0.9.0] ## [v0.9.0]

View File

@ -3,9 +3,9 @@
//! This module provides the [InputPinAsync] and [InputDynPinAsync] which both implement //! This module provides the [InputPinAsync] and [InputDynPinAsync] which both implement
//! the [embedded_hal_async::digital::Wait] trait. These types allow for asynchronous waiting //! 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 //! 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 one generic //! which must be provided for async support to work. However, it provides the
//! [handler][on_interrupt_for_asynch_gpio] which should be called in ALL user interrupt handlers //! [on_interrupt_for_async_gpio_for_port] generic interrupt handler. This should be called in all
//! which handle GPIO interrupts. //! IRQ functions which handle any GPIO interrupts with the corresponding [Port] argument.
//! //!
//! # Example //! # Example
//! //!
@ -21,60 +21,66 @@ use va108xx::{self as pac, Irqsel, Sysconfig};
use crate::InterruptConfig; use crate::InterruptConfig;
use super::{ use super::{
pin, DynGroup, DynPin, DynPinId, InputConfig, InterruptEdge, InvalidPinTypeError, Pin, PinId, pin, DynPin, DynPinId, InputConfig, InterruptEdge, InvalidPinTypeError, Pin, PinId, Port,
NUM_GPIO_PINS, NUM_PINS_PORT_A, NUM_PINS_PORT_A, NUM_PINS_PORT_B,
}; };
static WAKERS: [AtomicWaker; NUM_GPIO_PINS] = [const { AtomicWaker::new() }; NUM_GPIO_PINS]; static WAKERS_FOR_PORT_A: [AtomicWaker; NUM_PINS_PORT_A] =
static EDGE_DETECTION: [AtomicBool; NUM_GPIO_PINS] = [const { AtomicWaker::new() }; NUM_PINS_PORT_A];
[const { AtomicBool::new(false) }; NUM_GPIO_PINS]; 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];
#[inline] /// Generic interrupt handler for GPIO interrupts on a specific port to support async functionalities
fn pin_id_to_offset(dyn_pin_id: DynPinId) -> usize {
match dyn_pin_id.group {
DynGroup::A => dyn_pin_id.num as usize,
DynGroup::B => NUM_PINS_PORT_A + dyn_pin_id.num as usize,
}
}
/// Generic interrupt handler for GPIO interrupts to support the async functionalities.
/// ///
/// This handler will wake the correspoding wakers for the pins which triggered an interrupt /// This function should be called in all interrupt handlers which handle any GPIO interrupts
/// as well as updating the static edge detection structures. This allows the pin future to /// matching the [Port] argument.
/// complete async operations. The user should call this function in ALL interrupt handlers /// The handler will wake the corresponding wakers for the pins that triggered an interrupts
/// which handle any GPIO interrupts. /// as well as update the static edge detection structures. This allows the pin future tocomplete
#[inline] /// complete async operations.
pub fn on_interrupt_for_asynch_gpio() { pub fn on_interrupt_for_async_gpio_for_port(port: Port) {
let periphs = unsafe { pac::Peripherals::steal() }; let periphs = unsafe { pac::Peripherals::steal() };
handle_interrupt_for_gpio_and_port( let (irq_enb, edge_status, wakers, edge_detection) = match port {
periphs.porta.irq_enb().read().bits(), Port::A => (
periphs.porta.edge_status().read().bits(), periphs.porta.irq_enb().read().bits(),
0, periphs.porta.edge_status().read().bits(),
); WAKERS_FOR_PORT_A.as_ref(),
handle_interrupt_for_gpio_and_port( EDGE_DETECTION_PORT_A.as_ref(),
periphs.portb.irq_enb().read().bits(), ),
periphs.portb.edge_status().read().bits(), Port::B => (
NUM_PINS_PORT_A, 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);
} }
// Uses the enabled interrupt register and the persistent edge status to capture all GPIO events.
#[inline] #[inline]
fn handle_interrupt_for_gpio_and_port(mut irq_enb: u32, edge_status: u32, pin_base_offset: usize) { fn on_interrupt_for_port(
mut irq_enb: u32,
edge_status: u32,
wakers: &'static [AtomicWaker],
edge_detection: &'static [AtomicBool],
) {
while irq_enb != 0 { while irq_enb != 0 {
let bit_pos = irq_enb.trailing_zeros() as usize; let bit_pos = irq_enb.trailing_zeros() as usize;
let bit_mask = 1 << bit_pos; let bit_mask = 1 << bit_pos;
WAKERS[pin_base_offset + bit_pos].wake(); wakers[bit_pos].wake();
if edge_status & bit_mask != 0 { if edge_status & bit_mask != 0 {
EDGE_DETECTION[pin_base_offset + bit_pos] edge_detection[bit_pos].store(true, core::sync::atomic::Ordering::Relaxed);
.store(true, core::sync::atomic::Ordering::Relaxed);
}
// Clear the processed bit // Clear the processed bit
irq_enb &= !bit_mask; irq_enb &= !bit_mask;
}
} }
} }
@ -85,6 +91,8 @@ fn handle_interrupt_for_gpio_and_port(mut irq_enb: u32, edge_status: u32, pin_ba
/// struture is granted to allow writing custom async structures. /// struture is granted to allow writing custom async structures.
pub struct InputPinFuture { pub struct InputPinFuture {
pin_id: DynPinId, pin_id: DynPinId,
waker_group: &'static [AtomicWaker],
edge_detection_group: &'static [AtomicBool],
} }
impl InputPinFuture { impl InputPinFuture {
@ -102,6 +110,16 @@ impl InputPinFuture {
Self::new_with_dyn_pin(pin, irq, edge, &mut periphs.sysconfig, &mut periphs.irqsel) Self::new_with_dyn_pin(pin, irq, edge, &mut periphs.sysconfig, &mut periphs.irqsel)
} }
#[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( pub fn new_with_dyn_pin(
pin: &mut DynPin, pin: &mut DynPin,
irq: pac::Interrupt, irq: pac::Interrupt,
@ -113,7 +131,9 @@ impl InputPinFuture {
return Err(InvalidPinTypeError(pin.mode())); return Err(InvalidPinTypeError(pin.mode()));
} }
EDGE_DETECTION[pin_id_to_offset(pin.id())] let (waker_group, edge_detection_group) =
Self::pin_group_to_waker_and_edge_detection_group(pin.id().group);
edge_detection_group[pin.id().num as usize]
.store(false, core::sync::atomic::Ordering::Relaxed); .store(false, core::sync::atomic::Ordering::Relaxed);
pin.configure_edge_interrupt( pin.configure_edge_interrupt(
edge, edge,
@ -122,7 +142,11 @@ impl InputPinFuture {
Some(irq_sel), Some(irq_sel),
) )
.unwrap(); .unwrap();
Ok(Self { pin_id: pin.id() }) Ok(Self {
pin_id: pin.id(),
waker_group,
edge_detection_group,
})
} }
/// # Safety /// # Safety
@ -146,7 +170,9 @@ impl InputPinFuture {
sys_cfg: &mut Sysconfig, sys_cfg: &mut Sysconfig,
irq_sel: &mut Irqsel, irq_sel: &mut Irqsel,
) -> Self { ) -> Self {
EDGE_DETECTION[pin_id_to_offset(pin.id())] let (waker_group, edge_detection_group) =
Self::pin_group_to_waker_and_edge_detection_group(pin.id().group);
edge_detection_group[pin.id().num as usize]
.store(false, core::sync::atomic::Ordering::Relaxed); .store(false, core::sync::atomic::Ordering::Relaxed);
pin.configure_edge_interrupt( pin.configure_edge_interrupt(
edge, edge,
@ -154,14 +180,18 @@ impl InputPinFuture {
Some(sys_cfg), Some(sys_cfg),
Some(irq_sel), Some(irq_sel),
); );
Self { pin_id: pin.id() } Self {
pin_id: pin.id(),
edge_detection_group,
waker_group,
}
} }
} }
impl Drop for InputPinFuture { impl Drop for InputPinFuture {
fn drop(&mut self) { fn drop(&mut self) {
let periphs = unsafe { pac::Peripherals::steal() }; let periphs = unsafe { pac::Peripherals::steal() };
if self.pin_id.group == DynGroup::A { if self.pin_id.group == Port::A {
periphs periphs
.porta .porta
.irq_enb() .irq_enb()
@ -181,9 +211,9 @@ impl Future for InputPinFuture {
self: core::pin::Pin<&mut Self>, self: core::pin::Pin<&mut Self>,
cx: &mut core::task::Context<'_>, cx: &mut core::task::Context<'_>,
) -> core::task::Poll<Self::Output> { ) -> core::task::Poll<Self::Output> {
let idx = pin_id_to_offset(self.pin_id); let idx = self.pin_id.num as usize;
WAKERS[idx].register(cx.waker()); self.waker_group[idx].register(cx.waker());
if EDGE_DETECTION[idx].swap(false, core::sync::atomic::Ordering::Relaxed) { if self.edge_detection_group[idx].swap(false, core::sync::atomic::Ordering::Relaxed) {
return core::task::Poll::Ready(()); return core::task::Poll::Ready(());
} }
core::task::Poll::Pending core::task::Poll::Pending
@ -200,8 +230,8 @@ impl InputDynPinAsync {
/// passed as well and is used to route and enable the interrupt. /// 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 /// Please note that the interrupt handler itself must be provided by the user and the
/// generic [on_interrupt_for_asynch_gpio] function must be called inside that function for /// generic [on_interrupt_for_async_gpio_for_port] function must be called inside that function
/// the asynchronous functionality to work. /// for the asynchronous functionality to work.
pub fn new(pin: DynPin, irq: pac::Interrupt) -> Result<Self, InvalidPinTypeError> { pub fn new(pin: DynPin, irq: pac::Interrupt) -> Result<Self, InvalidPinTypeError> {
if !pin.is_input_pin() { if !pin.is_input_pin() {
return Err(InvalidPinTypeError(pin.mode())); return Err(InvalidPinTypeError(pin.mode()));
@ -335,8 +365,8 @@ impl<I: PinId, C: InputConfig> InputPinAsync<I, C> {
/// passed as well and is used to route and enable the interrupt. /// 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 /// Please note that the interrupt handler itself must be provided by the user and the
/// generic [on_interrupt_for_asynch_gpio] function must be called inside that function for /// generic [on_interrupt_for_async_gpio_for_port] function must be called inside that function
/// the asynchronous functionality to work. /// for the asynchronous functionality to work.
pub fn new(pin: Pin<I, pin::Input<C>>, irq: pac::Interrupt) -> Self { pub fn new(pin: Pin<I, pin::Input<C>>, irq: pac::Interrupt) -> Self {
Self { pin, irq } Self { pin, irq }
} }

View File

@ -57,9 +57,9 @@
//! [InvalidPinTypeError]. //! [InvalidPinTypeError].
use super::{ use super::{
pin::{FilterType, InterruptEdge, InterruptLevel, Pin, PinId, PinMode, PinState}, pin::{FilterType, Pin, PinId, PinMode},
reg::RegisterInterface, reg::RegisterInterface,
InputDynPinAsync, InputDynPinAsync, InterruptEdge, InterruptLevel, PinState,
}; };
use crate::{clock::FilterClkSel, enable_nvic_interrupt, pac, FunSel, InterruptConfig}; use crate::{clock::FilterClkSel, enable_nvic_interrupt, pac, FunSel, InterruptConfig};
@ -156,19 +156,13 @@ pub const DYN_ALT_FUNC_3: DynPinMode = DynPinMode::Alternate(DynAlternate::Sel3)
// DynGroup & DynPinId // DynGroup & DynPinId
//================================================================================================== //==================================================================================================
/// Value-level `enum` for pin groups pub type DynGroup = super::Port;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum DynGroup {
A,
B,
}
/// Value-level `struct` representing pin IDs /// Value-level `struct` representing pin IDs
#[derive(Debug, PartialEq, Eq, Clone, Copy)] #[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct DynPinId { pub struct DynPinId {
pub group: DynGroup, pub group: super::Port,
pub num: u8, pub num: u8,
} }
@ -369,12 +363,12 @@ impl DynPin {
if irq_cfg.route { if irq_cfg.route {
match self.regs.id().group { match self.regs.id().group {
// Set the correct interrupt number in the IRQSEL register // Set the correct interrupt number in the IRQSEL register
DynGroup::A => { super::Port::A => {
irqsel irqsel
.porta0(self.regs.id().num as usize) .porta0(self.regs.id().num as usize)
.write(|w| unsafe { w.bits(irq_cfg.id as u32) }); .write(|w| unsafe { w.bits(irq_cfg.id as u32) });
} }
DynGroup::B => { super::Port::B => {
irqsel irqsel
.portb0(self.regs.id().num as usize) .portb0(self.regs.id().num as usize)
.write(|w| unsafe { w.bits(irq_cfg.id as u32) }); .write(|w| unsafe { w.bits(irq_cfg.id as u32) });

View File

@ -22,14 +22,47 @@
//! //!
//! - [Blinky example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/simple/examples/blinky.rs) //! - [Blinky example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/simple/examples/blinky.rs)
//==================================================================================================
// Errors, Definitions and Constants
//==================================================================================================
pub const NUM_PINS_PORT_A: usize = 32;
pub const NUM_PINS_PORT_B: usize = 24;
pub const NUM_GPIO_PINS: usize = NUM_PINS_PORT_A + NUM_PINS_PORT_B;
#[derive(Debug, PartialEq, Eq, thiserror::Error)] #[derive(Debug, PartialEq, Eq, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[error("The pin is masked")] #[error("The pin is masked")]
pub struct IsMaskedError; pub struct IsMaskedError;
pub const NUM_PINS_PORT_A: usize = 32; #[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub const NUM_PINS_PORT_B: usize = 24; #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub const NUM_GPIO_PINS: usize = NUM_PINS_PORT_A + NUM_PINS_PORT_B; pub enum Port {
A,
B,
}
#[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 enum PinState {
Low = 0,
High = 1,
}
pub mod dynpin; pub mod dynpin;
pub use dynpin::*; pub use dynpin::*;

View File

@ -70,9 +70,9 @@
//! This module implements all of the embedded HAL GPIO traits for each [`Pin`] //! This module implements all of the embedded HAL GPIO traits for each [`Pin`]
//! in the corresponding [`PinMode`]s, namely: [`InputPin`], [`OutputPin`], //! in the corresponding [`PinMode`]s, namely: [`InputPin`], [`OutputPin`],
//! and [`StatefulOutputPin`]. //! and [`StatefulOutputPin`].
use super::dynpin::{DynAlternate, DynGroup, DynInput, DynOutput, DynPinId, DynPinMode}; use super::dynpin::{DynAlternate, DynInput, DynOutput, DynPinId, DynPinMode};
use super::reg::RegisterInterface; use super::reg::RegisterInterface;
use super::{DynPin, InputPinAsync}; use super::{DynPin, InputPinAsync, InterruptEdge, InterruptLevel, PinState, Port};
use crate::{ use crate::{
pac::{Irqsel, Porta, Portb, Sysconfig}, pac::{Irqsel, Porta, Portb, Sysconfig},
typelevel::Sealed, typelevel::Sealed,
@ -84,32 +84,6 @@ use core::mem::transmute;
use embedded_hal::digital::{InputPin, OutputPin, StatefulOutputPin}; use embedded_hal::digital::{InputPin, OutputPin, StatefulOutputPin};
use paste::paste; use paste::paste;
//==================================================================================================
// Errors and Definitions
//==================================================================================================
#[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 enum PinState {
Low = 0,
High = 1,
}
//================================================================================================== //==================================================================================================
// Input configuration // Input configuration
//================================================================================================== //==================================================================================================
@ -321,7 +295,7 @@ macro_rules! pin_id {
impl Sealed for $Id {} impl Sealed for $Id {}
impl PinId for $Id { impl PinId for $Id {
const DYN: DynPinId = DynPinId { const DYN: DynPinId = DynPinId {
group: DynGroup::$Group, group: Port::$Group,
num: $NUM, num: $NUM,
}; };
} }

View File

@ -1,6 +1,6 @@
use super::dynpin::{self, DynGroup, DynPinId, DynPinMode}; use super::dynpin::{self, DynPinId, DynPinMode};
use super::pin::{FilterType, InterruptEdge, InterruptLevel, PinState}; use super::pin::FilterType;
use super::IsMaskedError; use super::{InterruptEdge, InterruptLevel, IsMaskedError, PinState, Port};
use crate::clock::FilterClkSel; use crate::clock::FilterClkSel;
use va108xx::{ioconfig, porta}; use va108xx::{ioconfig, porta};
@ -146,15 +146,15 @@ pub(super) unsafe trait RegisterInterface {
#[inline] #[inline]
fn port_reg(&self) -> &PortRegisterBlock { fn port_reg(&self) -> &PortRegisterBlock {
match self.id().group { match self.id().group {
DynGroup::A => unsafe { &(*Self::PORTA) }, Port::A => unsafe { &(*Self::PORTA) },
DynGroup::B => unsafe { &(*Self::PORTB) }, Port::B => unsafe { &(*Self::PORTB) },
} }
} }
fn iocfg_port(&self) -> &PortReg { fn iocfg_port(&self) -> &PortReg {
let ioconfig = unsafe { va108xx::Ioconfig::ptr().as_ref().unwrap() }; let ioconfig = unsafe { va108xx::Ioconfig::ptr().as_ref().unwrap() };
match self.id().group { match self.id().group {
DynGroup::A => ioconfig.porta(self.id().num as usize), Port::A => ioconfig.porta(self.id().num as usize),
DynGroup::B => ioconfig.portb0(self.id().num as usize), Port::B => ioconfig.portb0(self.id().num as usize),
} }
} }