still a lot to do

This commit is contained in:
2025-04-15 10:54:34 +02:00
parent 2835231f2d
commit ecf2f4bf11
18 changed files with 1014 additions and 813 deletions

View File

@ -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 = []

View File

@ -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<Self::Output> {
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<I: PinIdProvider, C: InputConfig> {
pin: Pin<I, pin::Input<C>>,
irq: pac::Interrupt,
}
impl<I: PinIdProvider, C: InputConfig> InputPinAsync<I, C> {
/// 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<I, pin::Input<C>>, 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<I, pin::Input<C>> {
self.pin
}
}
impl<I: PinIdProvider, C: InputConfig> embedded_hal::digital::ErrorType for InputPinAsync<I, C> {
type Error = core::convert::Infallible;
}
impl<I: PinIdProvider, C: InputConfig> Wait for InputPinAsync<I, C> {
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(())
}
}

View File

@ -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<Self, InvalidOffsetError> {
impl PinId {
pub const fn new(port: Port, offset: usize) -> Result<Self, InvalidOffsetError> {
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
}
}

View File

@ -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<I: PinId> {
pub struct Pin<I: PinIdProvider> {
phantom: core::marker::PhantomData<I>,
}
impl<I: PinId> Pin<I> {
impl<I: PinIdProvider> Pin<I> {
#[allow(clippy::new_without_default)]
pub const fn new() -> Self {
Self {
@ -29,8 +29,8 @@ impl<I: PinId> Pin<I> {
pub struct Output(ll::LowLevelGpio);
impl Output {
pub fn new<I: PinId>(_pin: Pin<I>, init_level: PinState) -> Self {
let mut ll = ll::LowLevelGpio::new(I::PORT, I::OFFSET).unwrap();
pub fn new<I: PinIdProvider>(_pin: Pin<I>, 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<I: PinId>(_pin: Pin<I>) -> Self {
let mut ll = ll::LowLevelGpio::new(I::PORT, I::OFFSET).unwrap();
pub fn new_floating<I: PinIdProvider>(_pin: Pin<I>) -> Self {
let mut ll = ll::LowLevelGpio::new(I::ID);
ll.configure_as_input_floating();
Input(ll)
}
pub fn new_with_pull<I: PinId>(_pin: Pin<I>, pull: Pull) -> Self {
let mut ll = ll::LowLevelGpio::new(I::PORT, I::OFFSET).unwrap();
pub fn new_with_pull<I: PinIdProvider>(_pin: Pin<I>, 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<I: PinId>(_pin: Pin<I>) -> Self {
let mut ll = ll::LowLevelGpio::new(I::PORT, I::OFFSET).unwrap();
pub fn new<I: PinIdProvider>(_pin: Pin<I>) -> 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<I: PinId>(_pin: Pin<I>, fun_sel: FunSel, pull: Option<Pull>) -> Self {
let mut ll = ll::LowLevelGpio::new(I::PORT, I::OFFSET).unwrap();
pub fn new<I: PinIdProvider>(_pin: Pin<I>, fun_sel: FunSel, pull: Option<Pull>) -> Self {
let mut ll = ll::LowLevelGpio::new(I::ID);
ll.configure_as_peripheral_pin(fun_sel, pull);
IoPeriphPin { ll, fun_sel }
}

View File

@ -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,

View File

@ -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 {}