1 Commits

Author SHA1 Message Date
b751cb7d48 Asynchronous GPIO support 2025-02-11 18:50:58 +01:00
9 changed files with 119 additions and 76 deletions

View File

@@ -44,8 +44,8 @@ fn main() -> ! {
rprintln!("-- VA108xx Test Application --"); rprintln!("-- VA108xx Test Application --");
let mut dp = pac::Peripherals::take().unwrap(); let mut dp = pac::Peripherals::take().unwrap();
let cp = cortex_m::Peripherals::take().unwrap(); let cp = cortex_m::Peripherals::take().unwrap();
let pinsa = PinsA::new(&mut dp.sysconfig, None, dp.porta); let pinsa = PinsA::new(&mut dp.sysconfig, dp.porta);
let pinsb = PinsB::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.portb); let pinsb = PinsB::new(&mut dp.sysconfig, dp.portb);
let mut led1 = pinsa.pa10.into_readable_push_pull_output(); let mut led1 = pinsa.pa10.into_readable_push_pull_output();
let test_case = TestCase::DelayMs; let test_case = TestCase::DelayMs;

View File

@@ -1,10 +1,16 @@
//! This example demonstrates the usage of async GPIO operations on VA108xx.
//!
//! You need to tie the PA0 to the PA1 pin for this example to work. You can optionally tie the PB22 to PB23 pins well
//! and then set the `CHECK_PB22_TO_PB23` to true to also test async operations on Port B.
#![no_std] #![no_std]
#![no_main] #![no_main]
use embassy_executor::Spawner; use embassy_executor::Spawner;
use embassy_sync::channel::{Receiver, Sender};
use embassy_sync::{blocking_mutex::raw::ThreadModeRawMutex, channel::Channel}; use embassy_sync::{blocking_mutex::raw::ThreadModeRawMutex, channel::Channel};
use embassy_time::{Duration, Instant, Timer}; use embassy_time::{Duration, Instant, Timer};
use embedded_hal::digital::{InputPin, OutputPin, StatefulOutputPin}; use embedded_hal::digital::{InputPin, OutputPin, StatefulOutputPin};
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;
@@ -17,6 +23,12 @@ use va108xx_hal::{
const SYSCLK_FREQ: Hertz = Hertz::from_raw(50_000_000); const SYSCLK_FREQ: Hertz = Hertz::from_raw(50_000_000);
const CHECK_PA0_TO_PA1: bool = true;
const CHECK_PB22_TO_PB23: bool = false;
// Can also be set to OC10 and works as well.
const PB22_TO_PB23_IRQ: pac::Interrupt = pac::Interrupt::OC11;
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct GpioCmd { pub struct GpioCmd {
cmd_type: GpioCmdType, cmd_type: GpioCmdType,
@@ -44,9 +56,6 @@ pub enum GpioCmdType {
static CHANNEL_PA0_PA1: Channel<ThreadModeRawMutex, GpioCmd, 3> = Channel::new(); static CHANNEL_PA0_PA1: Channel<ThreadModeRawMutex, GpioCmd, 3> = Channel::new();
static CHANNEL_PB22_TO_PB23: Channel<ThreadModeRawMutex, GpioCmd, 3> = Channel::new(); static CHANNEL_PB22_TO_PB23: Channel<ThreadModeRawMutex, GpioCmd, 3> = Channel::new();
const CHECK_PA0_TO_PA1: bool = true;
const CHECK_PB22_TO_PB23: bool = true;
#[embassy_executor::main] #[embassy_executor::main]
async fn main(spawner: Spawner) { async fn main(spawner: Spawner) {
rtt_init_print!(); rtt_init_print!();
@@ -65,26 +74,42 @@ async fn main(spawner: Spawner) {
) )
}; };
let porta = PinsA::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.porta); let porta = PinsA::new(&mut dp.sysconfig, dp.porta);
let portb = PinsB::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.portb); let portb = PinsB::new(&mut dp.sysconfig, dp.portb);
let led0 = porta.pa10.into_readable_push_pull_output(); let mut led0 = porta.pa10.into_readable_push_pull_output();
let out_pa0 = porta.pa0.into_readable_push_pull_output(); let out_pa0 = porta.pa0.into_readable_push_pull_output();
let in_pa1 = porta.pa1.into_floating_input(); let in_pa1 = porta.pa1.into_floating_input();
let out_pb22 = portb.pb22.into_readable_push_pull_output(); let out_pb22 = portb.pb22.into_readable_push_pull_output();
let in_pb23 = portb.pb23.into_floating_input(); let in_pb23 = portb.pb23.into_floating_input();
let mut in_pa1_async = InputPinAsync::new(in_pa1, pac::Interrupt::OC10); let in_pa1_async = InputPinAsync::new(in_pa1, pac::Interrupt::OC10);
let out_pa0_dyn = out_pa0.downgrade(); let out_pa0_dyn = out_pa0.downgrade();
let mut in_pb23_async = InputPinAsync::new(in_pb23, pac::Interrupt::OC10); let in_pb23_async = InputDynPinAsync::new(in_pb23.downgrade(), PB22_TO_PB23_IRQ).unwrap();
let out_pb22_dyn = out_pb22.downgrade(); let out_pb22_dyn = out_pb22.downgrade();
spawner.spawn(output_task(out_pa0_dyn)).unwrap(); spawner
.spawn(output_task(
"PA0 to PA1",
out_pa0_dyn,
CHANNEL_PA0_PA1.receiver(),
))
.unwrap();
spawner
.spawn(output_task(
"PB22 to PB23",
out_pb22_dyn,
CHANNEL_PB22_TO_PB23.receiver(),
))
.unwrap();
if CHECK_PA0_TO_PA1 { if CHECK_PA0_TO_PA1 {
check_pin_to_pin_async_ops(&CHANNEL_PA0_PA1, in_pa1_async); check_pin_to_pin_async_ops("PA0 to PA1", CHANNEL_PA0_PA1.sender(), in_pa1_async).await;
rprintln!("Example PA0 to PA1 done");
} }
if CHECK_PB22_TO_PB23 { if CHECK_PB22_TO_PB23 {
check_pin_to_pin_async_ops(&CHANNEL_PB22_TO_PB23, in_pb23_async); check_pin_to_pin_async_ops("PB22 to PB23", CHANNEL_PB22_TO_PB23.sender(), in_pb23_async)
.await;
rprintln!("Example PB22 to PB23 done");
} }
rprintln!("Example done, toggling LED0"); rprintln!("Example done, toggling LED0");
@@ -95,113 +120,139 @@ async fn main(spawner: Spawner) {
} }
async fn check_pin_to_pin_async_ops( async fn check_pin_to_pin_async_ops(
ch: &'static Channel<ThreadModeRawMutex, GpioCmd, 3>, ctx: &'static str,
async_input: InputDynPinAsync, sender: Sender<'static, ThreadModeRawMutex, GpioCmd, 3>,
mut async_input: impl Wait,
) { ) {
rprintln!( rprintln!(
"sending SetHigh command ({} ms)", "{}: sending SetHigh command ({} ms)",
ctx,
Instant::now().as_millis() Instant::now().as_millis()
); );
CHANNEL_PA0_PA1 sender.send(GpioCmd::new(GpioCmdType::SetHigh, 20)).await;
.send(GpioCmd::new(GpioCmdType::SetHigh, 20)) async_input.wait_for_high().await.unwrap();
.await;
async_input.wait_for_high().await;
rprintln!("Input pin is high now ({} ms)", Instant::now().as_millis());
rprintln!("sending SetLow command ({} ms)", Instant::now().as_millis());
CHANNEL_PA0_PA1
.send(GpioCmd::new(GpioCmdType::SetLow, 20))
.await;
async_input.wait_for_low().await;
rprintln!("Input pin is low now ({} ms)", Instant::now().as_millis());
rprintln!( rprintln!(
"sending RisingEdge command ({} ms)", "{}: Input pin is high now ({} ms)",
Instant::now().as_millis() ctx,
);
CHANNEL_PA0_PA1
.send(GpioCmd::new(GpioCmdType::RisingEdge, 20))
.await;
async_input.wait_for_rising_edge().await;
rprintln!(
"input pin had rising edge ({} ms)",
Instant::now().as_millis() Instant::now().as_millis()
); );
rprintln!( rprintln!(
"sending Falling command ({} ms)", "{}: sending SetLow command ({} ms)",
ctx,
Instant::now().as_millis() Instant::now().as_millis()
); );
CHANNEL_PA0_PA1 sender.send(GpioCmd::new(GpioCmdType::SetLow, 20)).await;
async_input.wait_for_low().await.unwrap();
rprintln!(
"{}: Input pin is low now ({} ms)",
ctx,
Instant::now().as_millis()
);
rprintln!(
"{}: sending RisingEdge command ({} ms)",
ctx,
Instant::now().as_millis()
);
sender.send(GpioCmd::new(GpioCmdType::RisingEdge, 20)).await;
async_input.wait_for_rising_edge().await.unwrap();
rprintln!(
"{}: input pin had rising edge ({} ms)",
ctx,
Instant::now().as_millis()
);
rprintln!(
"{}: sending Falling command ({} ms)",
ctx,
Instant::now().as_millis()
);
sender
.send(GpioCmd::new(GpioCmdType::FallingEdge, 20)) .send(GpioCmd::new(GpioCmdType::FallingEdge, 20))
.await; .await;
async_input.wait_for_falling_edge().await; async_input.wait_for_falling_edge().await.unwrap();
rprintln!( rprintln!(
"input pin had a falling edge ({} ms)", "{}: input pin had a falling edge ({} ms)",
ctx,
Instant::now().as_millis() Instant::now().as_millis()
); );
rprintln!( rprintln!(
"sending Falling command ({} ms)", "{}: sending Falling command ({} ms)",
ctx,
Instant::now().as_millis() Instant::now().as_millis()
); );
CHANNEL_PA0_PA1 sender
.send(GpioCmd::new(GpioCmdType::FallingEdge, 20)) .send(GpioCmd::new(GpioCmdType::FallingEdge, 20))
.await; .await;
async_input.wait_for_any_edge().await; async_input.wait_for_any_edge().await.unwrap();
rprintln!( rprintln!(
"input pin had a falling (any) edge ({} ms)", "{}: input pin had a falling (any) edge ({} ms)",
ctx,
Instant::now().as_millis() Instant::now().as_millis()
); );
rprintln!( rprintln!(
"sending Falling command ({} ms)", "{}: sending Falling command ({} ms)",
ctx,
Instant::now().as_millis() Instant::now().as_millis()
); );
CHANNEL_PA0_PA1 sender.send(GpioCmd::new(GpioCmdType::RisingEdge, 20)).await;
.send(GpioCmd::new(GpioCmdType::RisingEdge, 20)) async_input.wait_for_any_edge().await.unwrap();
.await;
async_input.wait_for_any_edge().await;
rprintln!( rprintln!(
"input pin had a rising (any) edge ({} ms)", "{}: input pin had a rising (any) edge ({} ms)",
ctx,
Instant::now().as_millis() Instant::now().as_millis()
); );
} }
#[embassy_executor::task] #[embassy_executor::task(pool_size = 2)]
async fn output_task(mut out: DynPin) { async fn output_task(
ctx: &'static str,
mut out: DynPin,
receiver: Receiver<'static, ThreadModeRawMutex, GpioCmd, 3>,
) {
loop { loop {
let next_cmd = CHANNEL_PA0_PA1.receive().await; let next_cmd = receiver.receive().await;
Timer::after(Duration::from_millis(next_cmd.after_delay.into())).await; Timer::after(Duration::from_millis(next_cmd.after_delay.into())).await;
match next_cmd.cmd_type { match next_cmd.cmd_type {
GpioCmdType::SetHigh => { GpioCmdType::SetHigh => {
rprintln!("Set output high"); rprintln!("{}: Set output high", ctx);
out.set_high().unwrap(); out.set_high().unwrap();
} }
GpioCmdType::SetLow => { GpioCmdType::SetLow => {
rprintln!("Set output low"); rprintln!("{}: Set output low", ctx);
out.set_low().unwrap(); out.set_low().unwrap();
} }
GpioCmdType::RisingEdge => { GpioCmdType::RisingEdge => {
rprintln!("{}: Rising edge", ctx);
if !out.is_low().unwrap() { if !out.is_low().unwrap() {
out.set_low().unwrap(); out.set_low().unwrap();
} }
rprintln!("Rising edge");
out.set_high().unwrap(); out.set_high().unwrap();
} }
GpioCmdType::FallingEdge => { GpioCmdType::FallingEdge => {
rprintln!("{}: Falling edge", ctx);
if !out.is_high().unwrap() { if !out.is_high().unwrap() {
out.set_high().unwrap(); out.set_high().unwrap();
} }
rprintln!("Falling edge");
out.set_low().unwrap(); out.set_low().unwrap();
} }
} }
} }
} }
// 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() {
handle_interrupt_for_async_gpio(); handle_interrupt_for_async_gpio();
} }
#[interrupt]
#[allow(non_snake_case)]
fn OC11() {
handle_interrupt_for_async_gpio();
}

View File

@@ -52,7 +52,7 @@ async fn main(_spawner: Spawner) {
} }
} }
let porta = PinsA::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.porta); let porta = PinsA::new(&mut dp.sysconfig, dp.porta);
let mut led0 = porta.pa10.into_readable_push_pull_output(); let mut led0 = porta.pa10.into_readable_push_pull_output();
let mut led1 = porta.pa7.into_readable_push_pull_output(); let mut led1 = porta.pa7.into_readable_push_pull_output();
let mut led2 = porta.pa6.into_readable_push_pull_output(); let mut led2 = porta.pa6.into_readable_push_pull_output();

View File

@@ -110,7 +110,7 @@ mod app {
let mut dp = cx.device; let mut dp = cx.device;
let nvm = M95M01::new(&mut dp.sysconfig, SYSCLK_FREQ, dp.spic); let nvm = M95M01::new(&mut dp.sysconfig, SYSCLK_FREQ, dp.spic);
let gpioa = PinsA::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.porta); let gpioa = PinsA::new(&mut dp.sysconfig, dp.porta);
let tx = gpioa.pa9.into_funsel_2(); let tx = gpioa.pa9.into_funsel_2();
let rx = gpioa.pa8.into_funsel_2(); let rx = gpioa.pa8.into_funsel_2();

View File

@@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- `InvalidPinTypeError` now wraps the pin mode. - `InvalidPinTypeError` now wraps the pin mode.
- I2C `TimingCfg` constructor now returns explicit error instead of generic Error. - I2C `TimingCfg` constructor now returns explicit error instead of generic Error.
Removed the timing configuration error type from the generic I2C error enumeration. Removed the timing configuration error type from the generic I2C error enumeration.
- `PinsA` and `PinsB` constructor do not expect an optional `pac::Ioconfig` argument anymore.
## Added ## Added

View File

@@ -110,7 +110,7 @@ impl InputPinFuture {
irq_sel: &mut Irqsel, irq_sel: &mut Irqsel,
) -> Result<Self, InvalidPinTypeError> { ) -> Result<Self, InvalidPinTypeError> {
if !pin.is_input_pin() { if !pin.is_input_pin() {
return Err(InvalidPinTypeError); return Err(InvalidPinTypeError(pin.mode()));
} }
EDGE_DETECTION[pin_id_to_offset(pin.id())] EDGE_DETECTION[pin_id_to_offset(pin.id())]
@@ -204,7 +204,7 @@ impl InputDynPinAsync {
/// the asynchronous functionality to work. /// 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); return Err(InvalidPinTypeError(pin.mode()));
} }
Ok(Self { pin, irq }) Ok(Self { pin, irq })
} }

View File

@@ -105,7 +105,7 @@ pub type DynAlternate = FunSel;
#[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("Invalid pin type for operation: {0:?}")] #[error("Invalid pin type for operation: {0:?}")]
pub struct InvalidPinTypeError(DynPinMode); pub struct InvalidPinTypeError(pub DynPinMode);
impl embedded_hal::digital::Error for InvalidPinTypeError { impl embedded_hal::digital::Error for InvalidPinTypeError {
fn kind(&self) -> embedded_hal::digital::ErrorKind { fn kind(&self) -> embedded_hal::digital::ErrorKind {

View File

@@ -742,7 +742,6 @@ macro_rules! pins {
paste!( paste!(
/// Collection of all the individual [`Pin`]s for a given port (PORTA or PORTB) /// Collection of all the individual [`Pin`]s for a given port (PORTA or PORTB)
pub struct $PinsName { pub struct $PinsName {
iocfg: Option<va108xx::Ioconfig>,
port: $Port, port: $Port,
$( $(
#[doc = "Pin " $Id] #[doc = "Pin " $Id]
@@ -757,7 +756,6 @@ macro_rules! pins {
#[inline] #[inline]
pub fn new( pub fn new(
syscfg: &mut va108xx::Sysconfig, syscfg: &mut va108xx::Sysconfig,
iocfg: Option<va108xx::Ioconfig>,
port: $Port port: $Port
) -> $PinsName { ) -> $PinsName {
syscfg.peripheral_clk_enable().modify(|_, w| { syscfg.peripheral_clk_enable().modify(|_, w| {
@@ -766,7 +764,7 @@ macro_rules! pins {
w.ioconfig().set_bit() w.ioconfig().set_bit()
}); });
$PinsName { $PinsName {
iocfg, //iocfg,
port, port,
// Safe because we only create one `Pin` per `PinId` // Safe because we only create one `Pin` per `PinId`
$( $(
@@ -783,8 +781,8 @@ macro_rules! pins {
} }
/// Consumes the Pins struct and returns the port definitions /// Consumes the Pins struct and returns the port definitions
pub fn release(self) -> (Option<va108xx::Ioconfig>, $Port) { pub fn release(self) -> $Port {
(self.iocfg, self.port) self.port
} }
} }
); );

View File

@@ -73,13 +73,6 @@ impl From<DynPinMode> for ModeFields {
//================================================================================================== //==================================================================================================
pub type PortReg = ioconfig::Porta; pub type PortReg = ioconfig::Porta;
/*
pub type IocfgPort = ioconfig::Porta;
#[repr(C)]
pub(super) struct IocfgPortGroup {
port: [IocfgPort; 32],
}
*/
/// Provide a safe register interface for pin objects /// Provide a safe register interface for pin objects
/// ///