From 7999b13c9435d858119803ae74c6138af8eeb1d7 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sat, 15 Feb 2025 21:04:52 +0100 Subject: [PATCH] added async support for UART --- examples/embassy/Cargo.toml | 2 + examples/embassy/src/bin/async-uart-rx.rs | 111 +++++ examples/embassy/src/bin/async-uart-tx.rs | 96 ++++ .../embassy/src/bin/uart-echo-with-irq.rs | 2 +- flashloader/src/main.rs | 2 +- va416xx-hal/Cargo.toml | 2 + va416xx-hal/src/{uart.rs => uart/mod.rs} | 158 ++++++- va416xx-hal/src/uart/rx_asynch.rs | 440 ++++++++++++++++++ va416xx-hal/src/uart/tx_asynch.rs | 255 ++++++++++ 9 files changed, 1062 insertions(+), 6 deletions(-) create mode 100644 examples/embassy/src/bin/async-uart-rx.rs create mode 100644 examples/embassy/src/bin/async-uart-tx.rs rename va416xx-hal/src/{uart.rs => uart/mod.rs} (90%) create mode 100644 va416xx-hal/src/uart/rx_asynch.rs create mode 100644 va416xx-hal/src/uart/tx_asynch.rs diff --git a/examples/embassy/Cargo.toml b/examples/embassy/Cargo.toml index 8c661f0..a581254 100644 --- a/examples/embassy/Cargo.toml +++ b/examples/embassy/Cargo.toml @@ -13,7 +13,9 @@ embedded-hal-async = "1" embedded-io-async = "0.6" rtt-target = { version = "0.5" } +heapless = "0.8" panic-rtt-target = { version = "0.1" } +static_cell = "2" critical-section = "1" once_cell = { version = "1", default-features = false, features = ["critical-section"] } ringbuf = { version = "0.4", default-features = false } diff --git a/examples/embassy/src/bin/async-uart-rx.rs b/examples/embassy/src/bin/async-uart-rx.rs new file mode 100644 index 0000000..b35f1b4 --- /dev/null +++ b/examples/embassy/src/bin/async-uart-rx.rs @@ -0,0 +1,111 @@ +//! Asynchronous UART reception example application. +//! +//! This application receives data on two UARTs permanently using a ring buffer. +//! The ring buffer are read them asynchronously. +//! It uses PORTG0 as TX pin and PORTG1 as RX pin, which is the UART0 on the PEB1 board. +//! +//! Instructions: +//! +//! 1. Tie a USB to UART converter with RX to PORTG0 and TX to PORTG1. +//! 2. Connect to the serial interface by using an application like Putty or picocom. You can +//! type something in the terminal and check if the data is echoed back. You can also check the +//! RTT logs to see received data. +#![no_std] +#![no_main] +use core::cell::RefCell; + +use critical_section::Mutex; +use embassy_example::EXTCLK_FREQ; +use embassy_executor::Spawner; +use embassy_time::Instant; +use embedded_io::Write; +use embedded_io_async::Read; +use heapless::spsc::{Producer, Queue}; +use panic_rtt_target as _; +use rtt_target::{rprintln, rtt_init_print}; +use va416xx_hal::{ + gpio::PinsG, + pac::{self, interrupt}, + prelude::*, + time::Hertz, + uart::{ + self, + rx_asynch::{on_interrupt_rx, RxAsync}, + Bank, + }, +}; + +static QUEUE_UART_A: static_cell::ConstStaticCell> = + static_cell::ConstStaticCell::new(Queue::new()); +static PRODUCER_UART_A: Mutex>>> = Mutex::new(RefCell::new(None)); + +// main is itself an async function. +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + rtt_init_print!(); + rprintln!("-- VA108xx Async UART RX Demo --"); + + let mut dp = pac::Peripherals::take().unwrap(); + + // Initialize the systick interrupt & obtain the token to prove that we did + // Use the external clock connected to XTAL_N. + let clocks = dp + .clkgen + .constrain() + .xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ)) + .freeze(&mut dp.sysconfig) + .unwrap(); + // Safety: Only called once here. + unsafe { + va416xx_embassy::init( + &mut dp.sysconfig, + &dp.irq_router, + dp.tim15, + dp.tim14, + &clocks, + ); + } + + let portg = PinsG::new(&mut dp.sysconfig, dp.portg); + let mut led = portg.pg5.into_readable_push_pull_output(); + + let tx = portg.pg0.into_funsel_1(); + let rx = portg.pg1.into_funsel_1(); + + let uarta = uart::Uart::new(&mut dp.sysconfig, dp.uart0, (tx, rx), 115200.Hz(), &clocks); + + let (mut tx_uart_a, rx_uart_a) = uarta.split(); + let (prod_uart_a, cons_uart_a) = QUEUE_UART_A.take().split(); + // Pass the producer to the interrupt handler. + critical_section::with(|cs| { + *PRODUCER_UART_A.borrow(cs).borrow_mut() = Some(prod_uart_a); + }); + + let mut async_uart_rx = RxAsync::new(rx_uart_a, cons_uart_a); + let mut buf = [0u8; 256]; + loop { + rprintln!("Current time UART A: {}", Instant::now().as_secs()); + led.toggle(); + let read_bytes = async_uart_rx.read(&mut buf).await.unwrap(); + let read_str = core::str::from_utf8(&buf[..read_bytes]).unwrap(); + rprintln!( + "Read {} bytes asynchronously on UART A: {:?}", + read_bytes, + read_str + ); + tx_uart_a.write_all(read_str.as_bytes()).unwrap(); + } +} + +#[interrupt] +#[allow(non_snake_case)] +fn UART0_RX() { + let mut prod = + critical_section::with(|cs| PRODUCER_UART_A.borrow(cs).borrow_mut().take().unwrap()); + let errors = on_interrupt_rx(Bank::Uart0, &mut prod); + critical_section::with(|cs| *PRODUCER_UART_A.borrow(cs).borrow_mut() = Some(prod)); + // In a production app, we could use a channel to send the errors to the main task. + if let Err(errors) = errors { + rprintln!("UART A errors: {:?}", errors); + } +} diff --git a/examples/embassy/src/bin/async-uart-tx.rs b/examples/embassy/src/bin/async-uart-tx.rs new file mode 100644 index 0000000..2628dda --- /dev/null +++ b/examples/embassy/src/bin/async-uart-tx.rs @@ -0,0 +1,96 @@ +//! Asynchronous UART transmission example application. +//! +//! This application receives sends 4 strings with different sizes permanently. +//! It uses PORTG0 as TX pin and PORTG1 as RX pin, which is the UART0 on the PEB1 board. +//! +//! Instructions: +//! +//! 1. Tie a USB to UART converter with RX to PORTG0 and TX to PORTG1. +//! 2. Connect to the serial interface by using an application like Putty or picocom. You can +//! type something in the terminal and check if the data is echoed back. You can also check the +//! RTT logs to see received data. +#![no_std] +#![no_main] +use embassy_example::EXTCLK_FREQ; +use embassy_executor::Spawner; +use embassy_time::{Duration, Instant, Ticker}; +use embedded_io_async::Write; +use panic_rtt_target as _; +use rtt_target::{rprintln, rtt_init_print}; +use va416xx_hal::{ + gpio::PinsG, + pac::{self, interrupt}, + prelude::*, + time::Hertz, + uart::{ + self, + tx_asynch::{on_interrupt_tx, TxAsync}, + Bank, + }, +}; + +const STR_LIST: &[&str] = &[ + "Hello World\r\n", + "Smoll\r\n", + "A string which is larger than the FIFO size\r\n", + "A really large string which is significantly larger than the FIFO size\r\n", +]; + +// main is itself an async function. +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + rtt_init_print!(); + rprintln!("-- VA108xx Async UART TX Demo --"); + + let mut dp = pac::Peripherals::take().unwrap(); + + // Initialize the systick interrupt & obtain the token to prove that we did + // Use the external clock connected to XTAL_N. + let clocks = dp + .clkgen + .constrain() + .xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ)) + .freeze(&mut dp.sysconfig) + .unwrap(); + // Safety: Only called once here. + unsafe { + va416xx_embassy::init( + &mut dp.sysconfig, + &dp.irq_router, + dp.tim15, + dp.tim14, + &clocks, + ); + } + + let portg = PinsG::new(&mut dp.sysconfig, dp.portg); + let mut led = portg.pg5.into_readable_push_pull_output(); + + let tx = portg.pg0.into_funsel_1(); + let rx = portg.pg1.into_funsel_1(); + + let uarta = uart::Uart::new(&mut dp.sysconfig, dp.uart0, (tx, rx), 115200.Hz(), &clocks); + let (tx, _rx) = uarta.split(); + let mut async_tx = TxAsync::new(tx); + let mut ticker = Ticker::every(Duration::from_secs(1)); + let mut idx = 0; + loop { + rprintln!("Current time: {}", Instant::now().as_secs()); + led.toggle(); + let _written = async_tx + .write(STR_LIST[idx].as_bytes()) + .await + .expect("writing failed"); + idx += 1; + if idx == STR_LIST.len() { + idx = 0; + } + ticker.next().await; + } +} + +#[interrupt] +#[allow(non_snake_case)] +fn UART0_TX() { + on_interrupt_tx(Bank::Uart0); +} diff --git a/examples/embassy/src/bin/uart-echo-with-irq.rs b/examples/embassy/src/bin/uart-echo-with-irq.rs index c8e7446..a4efdc8 100644 --- a/examples/embassy/src/bin/uart-echo-with-irq.rs +++ b/examples/embassy/src/bin/uart-echo-with-irq.rs @@ -79,10 +79,10 @@ async fn main(spawner: Spawner) { let rx = portg.pg1.into_funsel_1(); let uart0 = uart::Uart::new( + &mut dp.sysconfig, dp.uart0, (tx, rx), Hertz::from_raw(BAUDRATE), - &mut dp.sysconfig, &clocks, ); let (mut tx, rx) = uart0.split(); diff --git a/flashloader/src/main.rs b/flashloader/src/main.rs index e0d4040..df3e0a9 100644 --- a/flashloader/src/main.rs +++ b/flashloader/src/main.rs @@ -171,10 +171,10 @@ mod app { let rx = gpiog.pg1.into_funsel_1(); let uart0 = Uart::new( + &mut cx.device.sysconfig, cx.device.uart0, (tx, rx), Hertz::from_raw(UART_BAUDRATE), - &mut cx.device.sysconfig, &clocks, ); let (tx, rx) = uart0.split(); diff --git a/va416xx-hal/Cargo.toml b/va416xx-hal/Cargo.toml index 2a3d3b3..faec8b0 100644 --- a/va416xx-hal/Cargo.toml +++ b/va416xx-hal/Cargo.toml @@ -19,12 +19,14 @@ embedded-hal-nb = "1" embedded-hal-async = "1" embedded-hal = "1" embedded-io = "0.6" +embedded-io-async = "0.6" num_enum = { version = "0.7", default-features = false } typenum = "1" bitflags = "2" bitfield = "0.17" fugit = "0.3" delegate = "0.12" +heapless = "0.8" void = { version = "1", default-features = false } thiserror = { version = "2", default-features = false } portable-atomic = "1" diff --git a/va416xx-hal/src/uart.rs b/va416xx-hal/src/uart/mod.rs similarity index 90% rename from va416xx-hal/src/uart.rs rename to va416xx-hal/src/uart/mod.rs index 30cae8e..cc3c8a7 100644 --- a/va416xx-hal/src/uart.rs +++ b/va416xx-hal/src/uart/mod.rs @@ -30,6 +30,14 @@ use crate::{ #[cfg(not(feature = "va41628"))] use crate::gpio::{PC15, PF8}; +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Bank { + Uart0 = 0, + Uart1 = 1, + Uart2 = 2, +} + //================================================================================================== // Type-Level support //================================================================================================== @@ -391,6 +399,7 @@ pub struct BufferTooShortError { pub trait Instance: Deref { const IDX: u8; const PERIPH_SEL: PeripheralSelect; + const PTR: *const uart_base::RegisterBlock; const IRQ_RX: pac::Interrupt; const IRQ_TX: pac::Interrupt; @@ -400,7 +409,21 @@ pub trait Instance: Deref { /// /// This circumvents the safety guarantees of the HAL. unsafe fn steal() -> Self; - fn ptr() -> *const uart_base::RegisterBlock; + + #[inline(always)] + fn ptr() -> *const uart_base::RegisterBlock { + Self::PTR + } + + /// Retrieve the type erased peripheral register block. + /// + /// # Safety + /// + /// This circumvents the safety guarantees of the HAL. + #[inline(always)] + unsafe fn reg_block() -> &'static uart_base::RegisterBlock { + unsafe { &(*Self::ptr()) } + } } impl Instance for Uart0 { @@ -408,6 +431,7 @@ impl Instance for Uart0 { const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Uart0; const IRQ_RX: pac::Interrupt = pac::Interrupt::UART0_RX; const IRQ_TX: pac::Interrupt = pac::Interrupt::UART0_TX; + const PTR: *const uart_base::RegisterBlock = Self::PTR; unsafe fn steal() -> Self { Self::steal() @@ -422,6 +446,7 @@ impl Instance for Uart1 { const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Uart1; const IRQ_RX: pac::Interrupt = pac::Interrupt::UART1_RX; const IRQ_TX: pac::Interrupt = pac::Interrupt::UART1_TX; + const PTR: *const uart_base::RegisterBlock = Self::PTR; unsafe fn steal() -> Self { Self::steal() @@ -436,6 +461,7 @@ impl Instance for Uart2 { const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Uart2; const IRQ_RX: pac::Interrupt = pac::Interrupt::UART2_RX; const IRQ_TX: pac::Interrupt = pac::Interrupt::UART2_TX; + const PTR: *const uart_base::RegisterBlock = Self::PTR; unsafe fn steal() -> Self { Self::steal() @@ -445,6 +471,21 @@ impl Instance for Uart2 { } } +impl Bank { + /// Retrieve the peripheral register block. + /// + /// # Safety + /// + /// Circumvents the HAL safety guarantees. + pub unsafe fn reg_block(&self) -> &'static uart_base::RegisterBlock { + match self { + Bank::Uart0 => unsafe { pac::Uart0::reg_block() }, + Bank::Uart1 => unsafe { pac::Uart1::reg_block() }, + Bank::Uart2 => unsafe { pac::Uart2::reg_block() }, + } + } +} + //================================================================================================== // UART implementation //================================================================================================== @@ -633,10 +674,10 @@ impl, RxPinInst: RxPin, UartInstanc Uart { pub fn new( + syscfg: &mut va416xx::Sysconfig, uart: UartInstance, pins: (TxPinInst, RxPinInst), config: impl Into, - syscfg: &mut va416xx::Sysconfig, clocks: &Clocks, ) -> Self { crate::clock::enable_peripheral_clock(syscfg, UartInstance::PERIPH_SEL); @@ -654,10 +695,10 @@ impl, RxPinInst: RxPin, UartInstanc } pub fn new_with_clock_freq( + syscfg: &mut va416xx::Sysconfig, uart: UartInstance, pins: (TxPinInst, RxPinInst), config: impl Into, - syscfg: &mut va416xx::Sysconfig, clock: impl Into, ) -> Self { crate::clock::enable_peripheral_clock(syscfg, UartInstance::PERIPH_SEL); @@ -724,6 +765,34 @@ impl, RxPinInst: RxPin, UartInstanc } } +#[inline(always)] +pub fn enable_rx(uart: &uart_base::RegisterBlock) { + uart.enable().modify(|_, w| w.rxenable().set_bit()); +} + +#[inline(always)] +pub fn disable_rx(uart: &uart_base::RegisterBlock) { + uart.enable().modify(|_, w| w.rxenable().clear_bit()); +} + +#[inline(always)] +pub fn enable_rx_interrupts(uart: &uart_base::RegisterBlock) { + uart.irq_enb().modify(|_, w| { + w.irq_rx().set_bit(); + w.irq_rx_to().set_bit(); + w.irq_rx_status().set_bit() + }); +} + +#[inline(always)] +pub fn disable_rx_interrupts(uart: &uart_base::RegisterBlock) { + uart.irq_enb().modify(|_, w| { + w.irq_rx().clear_bit(); + w.irq_rx_to().clear_bit(); + w.irq_rx_status().clear_bit() + }); +} + /// Serial receiver. /// /// Can be created by using the [Uart::split] or [UartBase::split] API. @@ -758,6 +827,15 @@ impl Rx { self.0.enable().modify(|_, w| w.rxenable().clear_bit()); } + #[inline] + pub fn disable_interrupts(&mut self) { + disable_rx_interrupts(unsafe { Uart::reg_block() }); + } + #[inline] + pub fn enable_interrupts(&mut self) { + enable_rx_interrupts(unsafe { Uart::reg_block() }); + } + /// Low level function to read a word from the UART FIFO. /// /// Uses the [nb] API to allow usage in blocking and non-blocking contexts. @@ -847,12 +925,51 @@ impl embedded_io::Read for Rx { } } +#[inline(always)] +pub fn enable_tx(uart: &uart_base::RegisterBlock) { + uart.enable().modify(|_, w| w.txenable().set_bit()); +} + +#[inline(always)] +pub fn disable_tx(uart: &uart_base::RegisterBlock) { + uart.enable().modify(|_, w| w.txenable().clear_bit()); +} + +#[inline(always)] +pub fn enable_tx_interrupts(uart: &uart_base::RegisterBlock) { + uart.irq_enb().modify(|_, w| { + w.irq_tx().set_bit(); + w.irq_tx_status().set_bit(); + w.irq_tx_empty().set_bit() + }); +} + +#[inline(always)] +pub fn disable_tx_interrupts(uart: &uart_base::RegisterBlock) { + uart.irq_enb().modify(|_, w| { + w.irq_tx().clear_bit(); + w.irq_tx_status().clear_bit(); + w.irq_tx_empty().clear_bit() + }); +} + /// Serial transmitter /// /// Can be created by using the [Uart::split] or [UartBase::split] API. pub struct Tx(Uart); impl Tx { + /// Retrieve a TX pin without expecting an explicit UART structure + /// + /// # Safety + /// + /// Circumvents the HAL safety guarantees. + #[inline(always)] + pub unsafe fn steal() -> Self { + Self(Uart::steal()) + } + + #[inline(always)] fn new(uart: Uart) -> Self { Self(uart) } @@ -862,7 +979,8 @@ impl Tx { /// # Safety /// /// You must ensure that only registers related to the operation of the TX side are used. - pub unsafe fn uart(&self) -> &Uart { + #[inline(always)] + pub const unsafe fn uart(&self) -> &Uart { &self.0 } @@ -881,6 +999,27 @@ impl Tx { self.0.enable().modify(|_, w| w.txenable().clear_bit()); } + /// Enables the IRQ_TX, IRQ_TX_STATUS and IRQ_TX_EMPTY interrupts. + /// + /// - The IRQ_TX interrupt is generated when the TX FIFO is at least half empty. + /// - The IRQ_TX_STATUS interrupt is generated when write data is lost due to a FIFO overflow + /// - The IRQ_TX_EMPTY interrupt is generated when the TX FIFO is empty and the TXBUSY signal + /// is 0 + #[inline] + pub fn enable_interrupts(&self) { + // Safety: We own the UART structure + enable_tx_interrupts(unsafe { Uart::reg_block() }); + } + + /// Disables the IRQ_TX, IRQ_TX_STATUS and IRQ_TX_EMPTY interrupts. + /// + /// [Self::enable_interrupts] documents the interrupts. + #[inline] + pub fn disable_interrupts(&self) { + // Safety: We own the UART structure + disable_tx_interrupts(unsafe { Uart::reg_block() }); + } + /// Low level function to write a word to the UART FIFO. /// /// Uses the [nb] API to allow usage in blocking and non-blocking contexts. @@ -906,6 +1045,11 @@ impl Tx { pub fn write_fifo_unchecked(&self, data: u32) { self.0.data().write(|w| unsafe { w.bits(data) }); } + + #[inline] + pub fn into_async(self) -> TxAsync { + TxAsync::new(self) + } } impl embedded_io::ErrorType for Tx { @@ -1233,3 +1377,9 @@ impl RxWithInterrupt { self.0.release() } } + +pub mod tx_asynch; +pub use tx_asynch::*; + +pub mod rx_asynch; +pub use rx_asynch::*; diff --git a/va416xx-hal/src/uart/rx_asynch.rs b/va416xx-hal/src/uart/rx_asynch.rs new file mode 100644 index 0000000..cd64a9b --- /dev/null +++ b/va416xx-hal/src/uart/rx_asynch.rs @@ -0,0 +1,440 @@ +//! # Async UART reception functionality for the VA416xx family. +//! +//! This module provides the [RxAsync] and [RxAsyncOverwriting] struct which both implement the +//! [embedded_io_async::Read] trait. +//! This trait allows for asynchronous reception of data streams. Please note that this module does +//! not specify/declare the interrupt handlers which must be provided for async support to work. +//! However, it provides two interrupt handlers: +//! +//! - [on_interrupt_rx] +//! - [on_interrupt_rx_overwriting] +//! +//! The first two are used for the [RxAsync] struct, while the latter two are used with the +//! [RxAsyncOverwriting] struct. The later two will overwrite old values in the used ring buffer. +//! +//! Error handling is performed in the user interrupt handler by checking the [AsyncUartErrors] +//! structure returned by the interrupt handlers. +//! +//! # Example +//! +//! - [Async UART RX example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/embassy/src/bin/async-uart-rx.rs) +use core::{cell::RefCell, convert::Infallible, future::Future, sync::atomic::Ordering}; + +use critical_section::Mutex; +use embassy_sync::waitqueue::AtomicWaker; +use embedded_io::ErrorType; +use portable_atomic::AtomicBool; +use va416xx::uart0 as uart_base; + +use super::{Bank, Instance, Rx, RxError, UartErrors}; + +static UART_RX_WAKERS: [AtomicWaker; 3] = [const { AtomicWaker::new() }; 3]; +static RX_READ_ACTIVE: [AtomicBool; 3] = [const { AtomicBool::new(false) }; 3]; +static RX_HAS_DATA: [AtomicBool; 3] = [const { AtomicBool::new(false) }; 3]; + +struct RxFuture { + uart_idx: usize, +} + +impl RxFuture { + pub fn new(_rx: &mut Rx) -> Self { + RX_READ_ACTIVE[Uart::IDX as usize].store(true, Ordering::Relaxed); + Self { + uart_idx: Uart::IDX as usize, + } + } +} + +impl Future for RxFuture { + type Output = Result<(), RxError>; + + fn poll( + self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> core::task::Poll { + UART_RX_WAKERS[self.uart_idx].register(cx.waker()); + if RX_HAS_DATA[self.uart_idx].load(Ordering::Relaxed) { + return core::task::Poll::Ready(Ok(())); + } + core::task::Poll::Pending + } +} + +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct AsyncUartErrors { + /// Queue has overflowed, data might have been lost. + pub queue_overflow: bool, + /// UART errors. + pub uart_errors: UartErrors, +} + +fn on_interrupt_handle_rx_errors(uart: &'static uart_base::RegisterBlock) -> Option { + let rx_status = uart.rxstatus().read(); + if rx_status.rxovr().bit_is_set() + || rx_status.rxfrm().bit_is_set() + || rx_status.rxpar().bit_is_set() + { + let mut errors_val = UartErrors::default(); + + if rx_status.rxovr().bit_is_set() { + errors_val.overflow = true; + } + if rx_status.rxfrm().bit_is_set() { + errors_val.framing = true; + } + if rx_status.rxpar().bit_is_set() { + errors_val.parity = true; + } + return Some(errors_val); + } + None +} + +fn on_interrupt_rx_common_post_processing( + bank: Bank, + rx_enabled: bool, + read_some_data: bool, + irq_end: u32, +) -> Option { + let idx = bank as usize; + if read_some_data { + RX_HAS_DATA[idx].store(true, Ordering::Relaxed); + if RX_READ_ACTIVE[idx].load(Ordering::Relaxed) { + UART_RX_WAKERS[idx].wake(); + } + } + + let mut errors = None; + let uart_regs = unsafe { bank.reg_block() }; + // Check for RX errors + if rx_enabled { + errors = on_interrupt_handle_rx_errors(uart_regs); + } + + // Clear the interrupt status bits + uart_regs.irq_clr().write(|w| unsafe { w.bits(irq_end) }); + errors +} + +/// Interrupt handler with overwriting behaviour when the ring buffer is full. +/// +/// Should be called in the user interrupt handler to enable +/// asynchronous reception. This variant will overwrite old data in the ring buffer in case +/// the ring buffer is full. +pub fn on_interrupt_rx_overwriting( + bank: Bank, + prod: &mut heapless::spsc::Producer, + shared_consumer: &Mutex>>>, +) -> Result<(), AsyncUartErrors> { + on_interrupt_rx_async_heapless_queue_overwriting(bank, prod, shared_consumer) +} + +pub fn on_interrupt_rx_async_heapless_queue_overwriting( + bank: Bank, + prod: &mut heapless::spsc::Producer, + shared_consumer: &Mutex>>>, +) -> Result<(), AsyncUartErrors> { + let uart_regs = unsafe { bank.reg_block() }; + let irq_end = uart_regs.irq_end().read(); + let enb_status = uart_regs.enable().read(); + let rx_enabled = enb_status.rxenable().bit_is_set(); + let mut read_some_data = false; + let mut queue_overflow = false; + + // Half-Full interrupt. We have a guaranteed amount of data we can read. + if irq_end.irq_rx().bit_is_set() { + let available_bytes = uart_regs.rxfifoirqtrg().read().bits() as usize; + + // If this interrupt bit is set, the trigger level is available at the very least. + // Read everything as fast as possible + for _ in 0..available_bytes { + let byte = uart_regs.data().read().bits(); + if !prod.ready() { + queue_overflow = true; + critical_section::with(|cs| { + let mut cons_ref = shared_consumer.borrow(cs).borrow_mut(); + cons_ref.as_mut().unwrap().dequeue(); + }); + } + prod.enqueue(byte as u8).ok(); + } + read_some_data = true; + } + + // Timeout, empty the FIFO completely. + if irq_end.irq_rx_to().bit_is_set() { + while uart_regs.rxstatus().read().rdavl().bit_is_set() { + // While there is data in the FIFO, write it into the reception buffer + let byte = uart_regs.data().read().bits(); + if !prod.ready() { + queue_overflow = true; + critical_section::with(|cs| { + let mut cons_ref = shared_consumer.borrow(cs).borrow_mut(); + cons_ref.as_mut().unwrap().dequeue(); + }); + } + prod.enqueue(byte as u8).ok(); + } + read_some_data = true; + } + + let uart_errors = + on_interrupt_rx_common_post_processing(bank, rx_enabled, read_some_data, irq_end.bits()); + if uart_errors.is_some() || queue_overflow { + return Err(AsyncUartErrors { + queue_overflow, + uart_errors: uart_errors.unwrap_or_default(), + }); + } + Ok(()) +} + +/// Interrupt handler for asynchronous RX operations. +/// +/// Should be called in the user interrupt handler to enable asynchronous reception. +pub fn on_interrupt_rx( + bank: Bank, + prod: &mut heapless::spsc::Producer<'_, u8, N>, +) -> Result<(), AsyncUartErrors> { + on_interrupt_rx_async_heapless_queue(bank, prod) +} + +pub fn on_interrupt_rx_async_heapless_queue( + bank: Bank, + prod: &mut heapless::spsc::Producer<'_, u8, N>, +) -> Result<(), AsyncUartErrors> { + let uart = unsafe { bank.reg_block() }; + let irq_end = uart.irq_end().read(); + let enb_status = uart.enable().read(); + let rx_enabled = enb_status.rxenable().bit_is_set(); + let mut read_some_data = false; + let mut queue_overflow = false; + + // Half-Full interrupt. We have a guaranteed amount of data we can read. + if irq_end.irq_rx().bit_is_set() { + let available_bytes = uart.rxfifoirqtrg().read().bits() as usize; + + // If this interrupt bit is set, the trigger level is available at the very least. + // Read everything as fast as possible + for _ in 0..available_bytes { + let byte = uart.data().read().bits(); + if !prod.ready() { + queue_overflow = true; + } + prod.enqueue(byte as u8).ok(); + } + read_some_data = true; + } + + // Timeout, empty the FIFO completely. + if irq_end.irq_rx_to().bit_is_set() { + while uart.rxstatus().read().rdavl().bit_is_set() { + // While there is data in the FIFO, write it into the reception buffer + let byte = uart.data().read().bits(); + if !prod.ready() { + queue_overflow = true; + } + prod.enqueue(byte as u8).ok(); + } + read_some_data = true; + } + + let uart_errors = + on_interrupt_rx_common_post_processing(bank, rx_enabled, read_some_data, irq_end.bits()); + if uart_errors.is_some() || queue_overflow { + return Err(AsyncUartErrors { + queue_overflow, + uart_errors: uart_errors.unwrap_or_default(), + }); + } + Ok(()) +} + +struct ActiveReadGuard(usize); + +impl Drop for ActiveReadGuard { + fn drop(&mut self) { + RX_READ_ACTIVE[self.0].store(false, Ordering::Relaxed); + } +} + +struct RxAsyncInner { + rx: Rx, + pub queue: heapless::spsc::Consumer<'static, u8, N>, +} + +/// Core data structure to allow asynchronous UART reception. +/// +/// If the ring buffer becomes full, data will be lost. +pub struct RxAsync(Option>); + +impl ErrorType for RxAsync { + /// Error reporting is done using the result of the interrupt functions. + type Error = Infallible; +} + +fn stop_async_rx(rx: &mut Rx) { + rx.disable_interrupts(); + rx.disable(); + rx.clear_fifo(); +} + +impl RxAsync { + /// Create a new asynchronous receiver. + /// + /// The passed [heapless::spsc::Consumer] will be used to asynchronously receive data which + /// is filled by the interrupt handler [on_interrupt_rx]. + pub fn new(mut rx: Rx, queue: heapless::spsc::Consumer<'static, u8, N>) -> Self { + rx.disable_interrupts(); + rx.disable(); + rx.clear_fifo(); + // Enable those together. + critical_section::with(|_| { + rx.enable_interrupts(); + rx.enable(); + }); + Self(Some(RxAsyncInner { rx, queue })) + } + + pub fn stop(&mut self) { + stop_async_rx(&mut self.0.as_mut().unwrap().rx); + } + + pub fn release(mut self) -> (Rx, heapless::spsc::Consumer<'static, u8, N>) { + self.stop(); + let inner = self.0.take().unwrap(); + (inner.rx, inner.queue) + } +} + +impl Drop for RxAsync { + fn drop(&mut self) { + self.stop(); + } +} + +impl embedded_io_async::Read for RxAsync { + async fn read(&mut self, buf: &mut [u8]) -> Result { + // Need to wait for the IRQ to read data and set this flag. If the queue is not + // empty, we can read data immediately. + if self.0.as_ref().unwrap().queue.len() == 0 { + RX_HAS_DATA[Uart::IDX as usize].store(false, Ordering::Relaxed); + } + let _guard = ActiveReadGuard(Uart::IDX as usize); + let mut handle_data_in_queue = |consumer: &mut heapless::spsc::Consumer<'static, u8, N>| { + let data_to_read = consumer.len().min(buf.len()); + for byte in buf.iter_mut().take(data_to_read) { + // We own the consumer and we checked that the amount of data is guaranteed to be available. + *byte = unsafe { consumer.dequeue_unchecked() }; + } + data_to_read + }; + let mut_ref = self.0.as_mut().unwrap(); + let fut = RxFuture::new(&mut mut_ref.rx); + // Data is available, so read that data immediately. + let read_data = handle_data_in_queue(&mut mut_ref.queue); + if read_data > 0 { + return Ok(read_data); + } + // Await data. + let _ = fut.await; + Ok(handle_data_in_queue(&mut mut_ref.queue)) + } +} + +struct RxAsyncOverwritingInner { + rx: Rx, + pub shared_consumer: &'static Mutex>>>, +} + +/// Core data structure to allow asynchronous UART reception. +/// +/// If the ring buffer becomes full, the oldest data will be overwritten when using the +/// [on_interrupt_rx_overwriting] interrupt handlers. +pub struct RxAsyncOverwriting( + Option>, +); + +impl ErrorType for RxAsyncOverwriting { + /// Error reporting is done using the result of the interrupt functions. + type Error = Infallible; +} + +impl RxAsyncOverwriting { + /// Create a new asynchronous receiver. + /// + /// The passed shared [heapless::spsc::Consumer] will be used to asynchronously receive data + /// which is filled by the interrupt handler. The shared property allows using it in the + /// interrupt handler to overwrite old data. + pub fn new( + mut rx: Rx, + shared_consumer: &'static Mutex>>>, + ) -> Self { + rx.disable_interrupts(); + rx.disable(); + rx.clear_fifo(); + // Enable those together. + critical_section::with(|_| { + rx.enable_interrupts(); + rx.enable(); + }); + Self(Some(RxAsyncOverwritingInner { + rx, + shared_consumer, + })) + } + + pub fn stop(&mut self) { + stop_async_rx(&mut self.0.as_mut().unwrap().rx); + } + + pub fn release(mut self) -> Rx { + self.stop(); + let inner = self.0.take().unwrap(); + inner.rx + } +} + +impl Drop for RxAsyncOverwriting { + fn drop(&mut self) { + self.stop(); + } +} + +impl embedded_io_async::Read for RxAsyncOverwriting { + async fn read(&mut self, buf: &mut [u8]) -> Result { + // Need to wait for the IRQ to read data and set this flag. If the queue is not + // empty, we can read data immediately. + + critical_section::with(|cs| { + let queue = self.0.as_ref().unwrap().shared_consumer.borrow(cs); + if queue.borrow().as_ref().unwrap().len() == 0 { + RX_HAS_DATA[Uart::IDX as usize].store(false, Ordering::Relaxed); + } + }); + let _guard = ActiveReadGuard(Uart::IDX as usize); + let mut handle_data_in_queue = |inner: &mut RxAsyncOverwritingInner| { + critical_section::with(|cs| { + let mut consumer_ref = inner.shared_consumer.borrow(cs).borrow_mut(); + let consumer = consumer_ref.as_mut().unwrap(); + let data_to_read = consumer.len().min(buf.len()); + for byte in buf.iter_mut().take(data_to_read) { + // We own the consumer and we checked that the amount of data is guaranteed to be available. + *byte = unsafe { consumer.dequeue_unchecked() }; + } + data_to_read + }) + }; + let fut = RxFuture::new(&mut self.0.as_mut().unwrap().rx); + // Data is available, so read that data immediately. + let read_data = handle_data_in_queue(self.0.as_mut().unwrap()); + if read_data > 0 { + return Ok(read_data); + } + // Await data. + let _ = fut.await; + let read_data = handle_data_in_queue(self.0.as_mut().unwrap()); + Ok(read_data) + } +} diff --git a/va416xx-hal/src/uart/tx_asynch.rs b/va416xx-hal/src/uart/tx_asynch.rs new file mode 100644 index 0000000..13980ae --- /dev/null +++ b/va416xx-hal/src/uart/tx_asynch.rs @@ -0,0 +1,255 @@ +//! # Async UART transmission functionality for the VA416xx family. +//! +//! This module provides the [TxAsync] struct which implements the [embedded_io_async::Write] trait. +//! This trait allows for asynchronous sending of data streams. Please note that this module does +//! not specify/declare the interrupt handlers which must be provided for async support to work. +//! However, it the [on_interrupt_tx] interrupt handler. +//! +//! This handler should be called in ALL user interrupt handlers which handle UART TX interrupts +//! for a given UART bank. +//! +//! # Example +//! +//! - [Async UART TX example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/embassy/src/bin/async-uart-tx.rs) +use core::{cell::RefCell, future::Future}; + +use critical_section::Mutex; +use embassy_sync::waitqueue::AtomicWaker; +use embedded_io_async::Write; +use portable_atomic::AtomicBool; + +use super::*; + +static UART_TX_WAKERS: [AtomicWaker; 3] = [const { AtomicWaker::new() }; 3]; +static TX_CONTEXTS: [Mutex>; 3] = + [const { Mutex::new(RefCell::new(TxContext::new())) }; 3]; +// Completion flag. Kept outside of the context structure as an atomic to avoid +// critical section. +static TX_DONE: [AtomicBool; 3] = [const { AtomicBool::new(false) }; 3]; + +/// This is a generic interrupt handler to handle asynchronous UART TX operations for a given +/// UART bank. +/// +/// The user has to call this once in the interrupt handler responsible for the TX interrupts on +/// the given UART bank. +pub fn on_interrupt_tx(bank: Bank) { + let uart = unsafe { bank.reg_block() }; + let idx = bank as usize; + let irq_enb = uart.irq_enb().read(); + // IRQ is not related to TX. + if irq_enb.irq_tx().bit_is_clear() || irq_enb.irq_tx_empty().bit_is_clear() { + return; + } + + let tx_status = uart.txstatus().read(); + let unexpected_overrun = tx_status.wrlost().bit_is_set(); + let mut context = critical_section::with(|cs| { + let context_ref = TX_CONTEXTS[idx].borrow(cs); + *context_ref.borrow() + }); + context.tx_overrun = unexpected_overrun; + if context.progress >= context.slice.len && !tx_status.wrbusy().bit_is_set() { + uart.irq_enb().modify(|_, w| { + w.irq_tx().clear_bit(); + w.irq_tx_empty().clear_bit(); + w.irq_tx_status().clear_bit() + }); + uart.enable().modify(|_, w| w.txenable().clear_bit()); + // Write back updated context structure. + critical_section::with(|cs| { + let context_ref = TX_CONTEXTS[idx].borrow(cs); + *context_ref.borrow_mut() = context; + }); + // Transfer is done. + TX_DONE[idx].store(true, core::sync::atomic::Ordering::Relaxed); + UART_TX_WAKERS[idx].wake(); + return; + } + // Safety: We documented that the user provided slice must outlive the future, so we convert + // the raw pointer back to the slice here. + let slice = unsafe { core::slice::from_raw_parts(context.slice.data, context.slice.len) }; + while context.progress < context.slice.len { + let wrrdy = uart.txstatus().read().wrrdy().bit_is_set(); + if !wrrdy { + break; + } + // Safety: TX structure is owned by the future which does not write into the the data + // register, so we can assume we are the only one writing to the data register. + uart.data() + .write(|w| unsafe { w.bits(slice[context.progress] as u32) }); + context.progress += 1; + } + + // Write back updated context structure. + critical_section::with(|cs| { + let context_ref = TX_CONTEXTS[idx].borrow(cs); + *context_ref.borrow_mut() = context; + }); +} + +#[derive(Debug, Copy, Clone)] +pub struct TxContext { + progress: usize, + tx_overrun: bool, + slice: RawBufSlice, +} + +#[allow(clippy::new_without_default)] +impl TxContext { + pub const fn new() -> Self { + Self { + progress: 0, + tx_overrun: false, + slice: RawBufSlice::new_empty(), + } + } +} + +#[derive(Debug, Copy, Clone)] +struct RawBufSlice { + data: *const u8, + len: usize, +} + +/// Safety: This type MUST be used with mutex to ensure concurrent access is valid. +unsafe impl Send for RawBufSlice {} + +impl RawBufSlice { + /// # Safety + /// + /// This function stores the raw pointer of the passed data slice. The user MUST ensure + /// that the slice outlives the data structure. + #[allow(dead_code)] + const unsafe fn new(data: &[u8]) -> Self { + Self { + data: data.as_ptr(), + len: data.len(), + } + } + + const fn new_empty() -> Self { + Self { + data: core::ptr::null(), + len: 0, + } + } + + /// # Safety + /// + /// This function stores the raw pointer of the passed data slice. The user MUST ensure + /// that the slice outlives the data structure. + pub unsafe fn set(&mut self, data: &[u8]) { + self.data = data.as_ptr(); + self.len = data.len(); + } +} + +pub struct TxFuture { + uart_idx: usize, +} + +impl TxFuture { + /// # Safety + /// + /// This function stores the raw pointer of the passed data slice. The user MUST ensure + /// that the slice outlives the data structure. + pub unsafe fn new(tx: &mut Tx, data: &[u8]) -> Self { + TX_DONE[Uart::IDX as usize].store(false, core::sync::atomic::Ordering::Relaxed); + tx.disable_interrupts(); + tx.disable(); + tx.clear_fifo(); + + let uart_tx = unsafe { tx.uart() }; + let init_fill_count = core::cmp::min(data.len(), 16); + // We fill the FIFO. + for data in data.iter().take(init_fill_count) { + uart_tx.data().write(|w| unsafe { w.bits(*data as u32) }); + } + critical_section::with(|cs| { + let context_ref = TX_CONTEXTS[Uart::IDX as usize].borrow(cs); + let mut context = context_ref.borrow_mut(); + context.slice.set(data); + context.progress = init_fill_count; + + // Ensure those are enabled inside a critical section at the same time. Can lead to + // weird glitches otherwise. + tx.enable_interrupts(); + tx.enable(); + }); + Self { + uart_idx: Uart::IDX as usize, + } + } +} + +impl Future for TxFuture { + type Output = Result; + + fn poll( + self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> core::task::Poll { + UART_TX_WAKERS[self.uart_idx].register(cx.waker()); + if TX_DONE[self.uart_idx].swap(false, core::sync::atomic::Ordering::Relaxed) { + let progress = critical_section::with(|cs| { + TX_CONTEXTS[self.uart_idx].borrow(cs).borrow().progress + }); + return core::task::Poll::Ready(Ok(progress)); + } + core::task::Poll::Pending + } +} + +impl Drop for TxFuture { + fn drop(&mut self) { + let reg_block = match self.uart_idx { + 0 => unsafe { pac::Uart0::reg_block() }, + 1 => unsafe { pac::Uart1::reg_block() }, + 2 => unsafe { pac::Uart2::reg_block() }, + _ => unreachable!(), + }; + + disable_tx_interrupts(reg_block); + disable_tx(reg_block); + } +} + +pub struct TxAsync { + tx: Tx, +} + +impl TxAsync { + pub fn new(tx: Tx) -> Self { + Self { tx } + } + + pub fn release(self) -> Tx { + self.tx + } +} + +#[derive(Debug, thiserror::Error)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[error("TX overrun error")] +pub struct TxOverrunError; + +impl embedded_io_async::Error for TxOverrunError { + fn kind(&self) -> embedded_io_async::ErrorKind { + embedded_io_async::ErrorKind::Other + } +} + +impl embedded_io::ErrorType for TxAsync { + type Error = TxOverrunError; +} + +impl Write for TxAsync { + /// Write a buffer asynchronously. + /// + /// This implementation is not side effect free, and a started future might have already + /// written part of the passed buffer. + async fn write(&mut self, buf: &[u8]) -> Result { + let fut = unsafe { TxFuture::new(&mut self.tx, buf) }; + fut.await + } +}