Compare commits

..

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 --");
let mut dp = pac::Peripherals::take().unwrap();
let cp = cortex_m::Peripherals::take().unwrap();
let pinsa = PinsA::new(&mut dp.sysconfig, None, dp.porta);
let pinsb = PinsB::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.portb);
let pinsa = PinsA::new(&mut dp.sysconfig, dp.porta);
let pinsb = PinsB::new(&mut dp.sysconfig, dp.portb);
let mut led1 = pinsa.pa10.into_readable_push_pull_output();
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_main]
use embassy_executor::Spawner;
use embassy_sync::channel::{Receiver, Sender};
use embassy_sync::{blocking_mutex::raw::ThreadModeRawMutex, channel::Channel};
use embassy_time::{Duration, Instant, Timer};
use embedded_hal::digital::{InputPin, OutputPin, StatefulOutputPin};
use embedded_hal_async::digital::Wait;
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use va108xx_embassy::embassy;
@ -17,6 +23,12 @@ use va108xx_hal::{
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)]
pub struct GpioCmd {
cmd_type: GpioCmdType,
@ -44,9 +56,6 @@ pub enum GpioCmdType {
static CHANNEL_PA0_PA1: 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]
async fn main(spawner: Spawner) {
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 portb = PinsB::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.portb);
let led0 = porta.pa10.into_readable_push_pull_output();
let porta = PinsA::new(&mut dp.sysconfig, dp.porta);
let portb = PinsB::new(&mut dp.sysconfig, dp.portb);
let mut led0 = porta.pa10.into_readable_push_pull_output();
let out_pa0 = porta.pa0.into_readable_push_pull_output();
let in_pa1 = porta.pa1.into_floating_input();
let out_pb22 = portb.pb22.into_readable_push_pull_output();
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 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();
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 {
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 {
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");
@ -95,113 +120,139 @@ async fn main(spawner: Spawner) {
}
async fn check_pin_to_pin_async_ops(
ch: &'static Channel<ThreadModeRawMutex, GpioCmd, 3>,
async_input: InputDynPinAsync,
ctx: &'static str,
sender: Sender<'static, ThreadModeRawMutex, GpioCmd, 3>,
mut async_input: impl Wait,
) {
rprintln!(
"sending SetHigh command ({} ms)",
"{}: sending SetHigh command ({} ms)",
ctx,
Instant::now().as_millis()
);
CHANNEL_PA0_PA1
.send(GpioCmd::new(GpioCmdType::SetHigh, 20))
.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());
sender.send(GpioCmd::new(GpioCmdType::SetHigh, 20)).await;
async_input.wait_for_high().await.unwrap();
rprintln!(
"sending RisingEdge command ({} ms)",
Instant::now().as_millis()
);
CHANNEL_PA0_PA1
.send(GpioCmd::new(GpioCmdType::RisingEdge, 20))
.await;
async_input.wait_for_rising_edge().await;
rprintln!(
"input pin had rising edge ({} ms)",
"{}: Input pin is high now ({} ms)",
ctx,
Instant::now().as_millis()
);
rprintln!(
"sending Falling command ({} ms)",
"{}: sending SetLow command ({} ms)",
ctx,
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))
.await;
async_input.wait_for_falling_edge().await;
async_input.wait_for_falling_edge().await.unwrap();
rprintln!(
"input pin had a falling edge ({} ms)",
"{}: input pin had a falling edge ({} ms)",
ctx,
Instant::now().as_millis()
);
rprintln!(
"sending Falling command ({} ms)",
"{}: sending Falling command ({} ms)",
ctx,
Instant::now().as_millis()
);
CHANNEL_PA0_PA1
sender
.send(GpioCmd::new(GpioCmdType::FallingEdge, 20))
.await;
async_input.wait_for_any_edge().await;
async_input.wait_for_any_edge().await.unwrap();
rprintln!(
"input pin had a falling (any) edge ({} ms)",
"{}: input pin had a falling (any) edge ({} ms)",
ctx,
Instant::now().as_millis()
);
rprintln!(
"sending Falling command ({} ms)",
"{}: sending Falling command ({} ms)",
ctx,
Instant::now().as_millis()
);
CHANNEL_PA0_PA1
.send(GpioCmd::new(GpioCmdType::RisingEdge, 20))
.await;
async_input.wait_for_any_edge().await;
sender.send(GpioCmd::new(GpioCmdType::RisingEdge, 20)).await;
async_input.wait_for_any_edge().await.unwrap();
rprintln!(
"input pin had a rising (any) edge ({} ms)",
"{}: input pin had a rising (any) edge ({} ms)",
ctx,
Instant::now().as_millis()
);
}
#[embassy_executor::task]
async fn output_task(mut out: DynPin) {
#[embassy_executor::task(pool_size = 2)]
async fn output_task(
ctx: &'static str,
mut out: DynPin,
receiver: Receiver<'static, ThreadModeRawMutex, GpioCmd, 3>,
) {
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;
match next_cmd.cmd_type {
GpioCmdType::SetHigh => {
rprintln!("Set output high");
rprintln!("{}: Set output high", ctx);
out.set_high().unwrap();
}
GpioCmdType::SetLow => {
rprintln!("Set output low");
rprintln!("{}: Set output low", ctx);
out.set_low().unwrap();
}
GpioCmdType::RisingEdge => {
rprintln!("{}: Rising edge", ctx);
if !out.is_low().unwrap() {
out.set_low().unwrap();
}
rprintln!("Rising edge");
out.set_high().unwrap();
}
GpioCmdType::FallingEdge => {
rprintln!("{}: Falling edge", ctx);
if !out.is_high().unwrap() {
out.set_high().unwrap();
}
rprintln!("Falling edge");
out.set_low().unwrap();
}
}
}
}
// PB22 to PB23 can be handled by both OC10 and OC11 depending on configuration.
#[interrupt]
#[allow(non_snake_case)]
fn OC10() {
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 led1 = porta.pa7.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 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 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.
- I2C `TimingCfg` constructor now returns explicit error instead of generic Error.
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

View File

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

View File

@ -105,7 +105,7 @@ pub type DynAlternate = FunSel;
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[error("Invalid pin type for operation: {0:?}")]
pub struct InvalidPinTypeError(DynPinMode);
pub struct InvalidPinTypeError(pub DynPinMode);
impl embedded_hal::digital::Error for InvalidPinTypeError {
fn kind(&self) -> embedded_hal::digital::ErrorKind {

View File

@ -742,7 +742,6 @@ macro_rules! pins {
paste!(
/// Collection of all the individual [`Pin`]s for a given port (PORTA or PORTB)
pub struct $PinsName {
iocfg: Option<va108xx::Ioconfig>,
port: $Port,
$(
#[doc = "Pin " $Id]
@ -757,7 +756,6 @@ macro_rules! pins {
#[inline]
pub fn new(
syscfg: &mut va108xx::Sysconfig,
iocfg: Option<va108xx::Ioconfig>,
port: $Port
) -> $PinsName {
syscfg.peripheral_clk_enable().modify(|_, w| {
@ -766,7 +764,7 @@ macro_rules! pins {
w.ioconfig().set_bit()
});
$PinsName {
iocfg,
//iocfg,
port,
// 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
pub fn release(self) -> (Option<va108xx::Ioconfig>, $Port) {
(self.iocfg, self.port)
pub fn release(self) -> $Port {
self.port
}
}
);

View File

@ -73,13 +73,6 @@ impl From<DynPinMode> for ModeFields {
//==================================================================================================
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
///