diff --git a/examples/embassy/Cargo.toml b/examples/embassy/Cargo.toml index d00303a..21cb83f 100644 --- a/examples/embassy/Cargo.toml +++ b/examples/embassy/Cargo.toml @@ -9,7 +9,10 @@ cortex-m = { version = "0.7", features = ["critical-section-single-core"] } cortex-m-rt = "0.7" embedded-hal = "1" embedded-hal-async = "1" +embedded-io = "0.6" embedded-io-async = "0.6" +heapless = "0.8" +static_cell = "2" rtt-target = "0.6" panic-rtt-target = "0.2" 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..e37eaab --- /dev/null +++ b/examples/embassy/src/bin/async-uart-rx.rs @@ -0,0 +1,171 @@ +//! Asynchronous UART reception example application. +//! +//! This application receives data on two UARTs permanently using a ring buffer. +//! The ring buffer are read them asynchronously. UART A is received on ports PA8 and PA9. +//! UART B is received on ports PA2 and PA3. +//! +//! Instructions: +//! +//! 1. Tie a USB to UART converter with RX to PA9 and TX to PA8 for UART A. +//! Tie a USB to UART converter with RX to PA3 and TX to PA2 for UART B. +//! 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_executor::Spawner; +use embassy_time::Instant; +use embedded_hal::digital::StatefulOutputPin; +use embedded_io::Write; +use embedded_io_async::Read; +use heapless::spsc::{Consumer, Producer, Queue}; +use panic_rtt_target as _; +use rtt_target::{rprintln, rtt_init_print}; +use va108xx_embassy::embassy; +use va108xx_hal::{ + gpio::PinsA, + pac::{self, interrupt}, + prelude::*, + uart::{ + self, on_interrupt_uart_b_overwriting, + rx_asynch::{on_interrupt_uart_a, RxAsync}, + RxAsyncSharedConsumer, Tx, + }, + InterruptConfig, +}; + +const SYSCLK_FREQ: Hertz = Hertz::from_raw(50_000_000); + +static QUEUE_UART_A: static_cell::ConstStaticCell> = + static_cell::ConstStaticCell::new(Queue::new()); +static PRODUCER_UART_A: Mutex>>> = Mutex::new(RefCell::new(None)); + +static QUEUE_UART_B: static_cell::ConstStaticCell> = + static_cell::ConstStaticCell::new(Queue::new()); +static PRODUCER_UART_B: Mutex>>> = Mutex::new(RefCell::new(None)); +static CONSUMER_UART_B: 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(); + + // Safety: Only called once here. + unsafe { + embassy::init( + &mut dp.sysconfig, + &dp.irqsel, + SYSCLK_FREQ, + dp.tim23, + dp.tim22, + ); + } + + 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(); + + let tx_uart_a = porta.pa9.into_funsel_2(); + let rx_uart_a = porta.pa8.into_funsel_2(); + + let uarta = uart::Uart::new_with_interrupt( + &mut dp.sysconfig, + 50.MHz(), + dp.uarta, + (tx_uart_a, rx_uart_a), + 115200.Hz(), + InterruptConfig::new(pac::Interrupt::OC2, true, true), + ); + + let tx_uart_b = porta.pa3.into_funsel_2(); + let rx_uart_b = porta.pa2.into_funsel_2(); + + let uartb = uart::Uart::new_with_interrupt( + &mut dp.sysconfig, + 50.MHz(), + dp.uartb, + (tx_uart_b, rx_uart_b), + 115200.Hz(), + InterruptConfig::new(pac::Interrupt::OC3, true, true), + ); + let (mut tx_uart_a, rx_uart_a) = uarta.split(); + let (tx_uart_b, rx_uart_b) = uartb.split(); + let (prod_uart_a, cons_uart_a) = QUEUE_UART_A.take().split(); + // Pass the producer to the interrupt handler. + let (prod_uart_b, cons_uart_b) = QUEUE_UART_B.take().split(); + critical_section::with(|cs| { + *PRODUCER_UART_A.borrow(cs).borrow_mut() = Some(prod_uart_a); + *PRODUCER_UART_B.borrow(cs).borrow_mut() = Some(prod_uart_b); + *CONSUMER_UART_B.borrow(cs).borrow_mut() = Some(cons_uart_b); + }); + let mut async_rx_uart_a = RxAsync::new(rx_uart_a, cons_uart_a); + let async_rx_uart_b = RxAsyncSharedConsumer::new(rx_uart_b, &CONSUMER_UART_B); + spawner + .spawn(uart_b_task(async_rx_uart_b, tx_uart_b)) + .unwrap(); + let mut buf = [0u8; 256]; + loop { + rprintln!("Current time UART A: {}", Instant::now().as_secs()); + led0.toggle().ok(); + led1.toggle().ok(); + led2.toggle().ok(); + let read_bytes = async_rx_uart_a.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(); + } +} + +#[embassy_executor::task] +async fn uart_b_task(mut async_rx: RxAsyncSharedConsumer, mut tx: Tx) { + let mut buf = [0u8; 256]; + loop { + rprintln!("Current time UART B: {}", Instant::now().as_secs()); + // Infallible asynchronous operation. + let read_bytes = async_rx.read(&mut buf).await.unwrap(); + let read_str = core::str::from_utf8(&buf[..read_bytes]).unwrap(); + rprintln!( + "Read {} bytes asynchronously on UART B: {:?}", + read_bytes, + read_str + ); + tx.write_all(read_str.as_bytes()).unwrap(); + } +} + +#[interrupt] +#[allow(non_snake_case)] +fn OC2() { + let mut prod = + critical_section::with(|cs| PRODUCER_UART_A.borrow(cs).borrow_mut().take().unwrap()); + let errors = on_interrupt_uart_a(&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); + } +} + +#[interrupt] +#[allow(non_snake_case)] +fn OC3() { + let mut prod = + critical_section::with(|cs| PRODUCER_UART_B.borrow(cs).borrow_mut().take().unwrap()); + let errors = on_interrupt_uart_b_overwriting(&mut prod, &CONSUMER_UART_B); + critical_section::with(|cs| *PRODUCER_UART_B.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 B errors: {:?}", errors); + } +} diff --git a/examples/embassy/src/bin/async-uart.rs b/examples/embassy/src/bin/async-uart-tx.rs similarity index 82% rename from examples/embassy/src/bin/async-uart.rs rename to examples/embassy/src/bin/async-uart-tx.rs index b84ba33..0c8345b 100644 --- a/examples/embassy/src/bin/async-uart.rs +++ b/examples/embassy/src/bin/async-uart-tx.rs @@ -1,3 +1,13 @@ +//! Asynchronous UART transmission example application. +//! +//! This application receives sends 4 strings with different sizes permanently using UART A. +//! Ports PA8 and PA9 are used for this. +//! +//! Instructions: +//! +//! 1. Tie a USB to UART converter with RX to PA9 and TX to PA8 for UART A. +//! 2. Connect to the serial interface by using an application like Putty or picocom. You can +//! can verify the correctness of the sent strings. #![no_std] #![no_main] use embassy_executor::Spawner; @@ -28,7 +38,7 @@ const STR_LIST: &[&str] = &[ #[embassy_executor::main] async fn main(_spawner: Spawner) { rtt_init_print!(); - rprintln!("-- VA108xx Embassy Demo --"); + rprintln!("-- VA108xx Async UART TX Demo --"); let mut dp = pac::Peripherals::take().unwrap(); diff --git a/examples/simple/examples/uart.rs b/examples/simple/examples/uart.rs index 72119bb..0df10c0 100644 --- a/examples/simple/examples/uart.rs +++ b/examples/simple/examples/uart.rs @@ -27,15 +27,15 @@ fn main() -> ! { let gpioa = PinsA::new(&mut dp.sysconfig, dp.porta); let tx = gpioa.pa9.into_funsel_2(); let rx = gpioa.pa8.into_funsel_2(); - - let uarta = uart::Uart::new_without_interrupt( + let uart = uart::Uart::new_without_interrupt( &mut dp.sysconfig, 50.MHz(), dp.uarta, (tx, rx), 115200.Hz(), ); - let (mut tx, mut rx) = uarta.split(); + + let (mut tx, mut rx) = uart.split(); writeln!(tx, "Hello World\r").unwrap(); loop { // Echo what is received on the serial link. diff --git a/va108xx-hal/CHANGELOG.md b/va108xx-hal/CHANGELOG.md index 5bcf884..d8f35fb 100644 --- a/va108xx-hal/CHANGELOG.md +++ b/va108xx-hal/CHANGELOG.md @@ -42,6 +42,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - `enable_interrupt` and `disable_interrupt` renamed to `enable_nvic_interrupt` and `disable_nvic_interrupt` to distinguish them from peripheral interrupts more clearly. - `port_mux` renamed to `port_function_select` +- Renamed `IrqUartErrors` to `UartErrors`. ## Added @@ -49,6 +50,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). methods. - Asynchronous GPIO support. - Asynchronous UART TX support. +- Asynchronous UART RX support. - Add new `get_tim_raw` unsafe method to retrieve TIM peripheral blocks. - `Uart::with_with_interrupt` and `Uart::new_without_interrupt` diff --git a/va108xx-hal/Cargo.toml b/va108xx-hal/Cargo.toml index 25fa484..26ea489 100644 --- a/va108xx-hal/Cargo.toml +++ b/va108xx-hal/Cargo.toml @@ -24,6 +24,8 @@ fugit = "0.3" typenum = "1" critical-section = "1" delegate = ">=0.12, <=0.13" +heapless = "0.8" +static_cell = "2" thiserror = { version = "2", default-features = false } void = { version = "1", default-features = false } once_cell = {version = "1", default-features = false } diff --git a/va108xx-hal/src/uart/mod.rs b/va108xx-hal/src/uart/mod.rs index aec8798..137c9db 100644 --- a/va108xx-hal/src/uart/mod.rs +++ b/va108xx-hal/src/uart/mod.rs @@ -22,6 +22,12 @@ use crate::{ }; use embedded_hal_nb::serial::Read; +#[derive(Debug)] +pub enum Bank { + A = 0, + B = 1, +} + //================================================================================================== // Type-Level support //================================================================================================== @@ -258,7 +264,7 @@ impl IrqContextTimeoutOrMaxSize { #[derive(Debug, Default)] pub struct IrqResult { pub bytes_read: usize, - pub errors: Option, + pub errors: Option, } /// This struct is used to return the default IRQ handler result to the user @@ -266,7 +272,7 @@ pub struct IrqResult { pub struct IrqResultMaxSizeOrTimeout { complete: bool, timeout: bool, - pub errors: Option, + pub errors: Option, pub bytes_read: usize, } @@ -319,14 +325,15 @@ enum IrqReceptionMode { } #[derive(Default, Debug, Copy, Clone)] -pub struct IrqUartError { +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct UartErrors { overflow: bool, framing: bool, parity: bool, other: bool, } -impl IrqUartError { +impl UartErrors { #[inline(always)] pub fn overflow(&self) -> bool { self.overflow @@ -348,7 +355,7 @@ impl IrqUartError { } } -impl IrqUartError { +impl UartErrors { #[inline(always)] pub fn error(&self) -> bool { self.overflow || self.framing || self.parity @@ -751,6 +758,34 @@ where } } +#[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. @@ -759,6 +794,7 @@ pub struct Rx { } impl Rx { + #[inline(always)] fn new(uart: Uart) -> Self { Self { uart } } @@ -768,6 +804,7 @@ impl Rx { /// # Safety /// /// You must ensure that only registers related to the operation of the RX side are used. + #[inline(always)] pub unsafe fn uart(&self) -> &Uart { &self.uart } @@ -777,14 +814,23 @@ impl Rx { self.uart.fifo_clr().write(|w| w.rxfifo().set_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() }); + } + #[inline] pub fn enable(&mut self) { - self.uart.enable().modify(|_, w| w.rxenable().set_bit()); + enable_rx(unsafe { Uart::reg_block() }); } #[inline] pub fn disable(&mut self) { - self.uart.enable().modify(|_, w| w.rxenable().clear_bit()); + disable_rx(unsafe { Uart::reg_block() }); } /// Low level function to read a word from the UART FIFO. @@ -818,6 +864,7 @@ impl Rx { RxWithInterrupt::new(self) } + #[inline(always)] pub fn release(self) -> Uart { self.uart } @@ -876,14 +923,17 @@ 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(); @@ -892,6 +942,7 @@ pub fn enable_tx_interrupts(uart: &uart_base::RegisterBlock) { }); } +#[inline(always)] pub fn disable_tx_interrupts(uart: &uart_base::RegisterBlock) { uart.irq_enb().modify(|_, w| { w.irq_tx().clear_bit(); @@ -913,12 +964,14 @@ impl Tx { /// # Safety /// /// Circumvents the HAL safety guarantees. + #[inline(always)] pub unsafe fn steal() -> Self { Self { uart: Uart::steal(), } } + #[inline(always)] fn new(uart: Uart) -> Self { Self { uart } } @@ -928,6 +981,7 @@ impl Tx { /// # Safety /// /// You must ensure that only registers related to the operation of the TX side are used. + #[inline(always)] pub unsafe fn uart(&self) -> &Uart { &self.uart } @@ -1265,7 +1319,7 @@ impl RxWithInterrupt { fn read_handler( &self, - errors: &mut Option, + errors: &mut Option, read_res: &nb::Result, ) -> Option { match read_res { @@ -1273,7 +1327,7 @@ impl RxWithInterrupt { Err(nb::Error::WouldBlock) => None, Err(nb::Error::Other(e)) => { // Ensure `errors` is Some(IrqUartError), initializing if it's None - let err = errors.get_or_insert(IrqUartError::default()); + let err = errors.get_or_insert(UartErrors::default()); // Now we can safely modify fields inside `err` match e { @@ -1286,14 +1340,14 @@ impl RxWithInterrupt { } } - fn check_for_errors(&self, errors: &mut Option) { + fn check_for_errors(&self, errors: &mut Option) { let rx_status = self.uart().rxstatus().read(); if rx_status.rxovr().bit_is_set() || rx_status.rxfrm().bit_is_set() || rx_status.rxpar().bit_is_set() { - let err = errors.get_or_insert(IrqUartError::default()); + let err = errors.get_or_insert(UartErrors::default()); if rx_status.rxovr().bit_is_set() { err.overflow = true; @@ -1330,5 +1384,8 @@ impl RxWithInterrupt { } } -pub mod asynch; -pub use asynch::*; +pub mod tx_asynch; +pub use tx_asynch::*; + +pub mod rx_asynch; +pub use rx_asynch::*; diff --git a/va108xx-hal/src/uart/rx_asynch.rs b/va108xx-hal/src/uart/rx_asynch.rs new file mode 100644 index 0000000..a93ecd7 --- /dev/null +++ b/va108xx-hal/src/uart/rx_asynch.rs @@ -0,0 +1,419 @@ +//! # Async UART reception functionality for the VA108xx family. +//! +//! This module provides the [RxAsync] and [RxAsyncSharedConsumer] 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 four interrupt handlers: +//! +//! - [on_interrupt_uart_a] +//! - [on_interrupt_uart_b] +//! - [on_interrupt_uart_a_overwriting] +//! - [on_interrupt_uart_b_overwriting] +//! +//! The first two are used for the [RxAsync] struct, while the latter two are used with the +//! [RxAsyncSharedConsumer] 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 heapless::spsc::Consumer; +use portable_atomic::AtomicBool; +use va108xx as pac; + +use super::{Instance, Rx, RxError, UartErrors}; + +static UART_RX_WAKERS: [AtomicWaker; 2] = [const { AtomicWaker::new() }; 2]; +static RX_READ_ACTIVE: [AtomicBool; 2] = [const { AtomicBool::new(false) }; 2]; +static RX_HAS_DATA: [AtomicBool; 2] = [const { AtomicBool::new(false) }; 2]; + +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: &Uart) -> 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( + uart: &Uart, + rx_enabled: bool, + read_some_data: bool, + irq_end: u32, +) -> Option { + if read_some_data { + RX_HAS_DATA[Uart::IDX as usize].store(true, Ordering::Relaxed); + if RX_READ_ACTIVE[Uart::IDX as usize].load(Ordering::Relaxed) { + UART_RX_WAKERS[Uart::IDX as usize].wake(); + } + } + + let mut errors = None; + // Check for RX errors + if rx_enabled { + errors = on_interrupt_handle_rx_errors(uart); + } + + // Clear the interrupt status bits + uart.irq_clr().write(|w| unsafe { w.bits(irq_end) }); + errors +} + +/// Interrupt handler for UART A. +/// +/// 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_uart_a_overwriting( + prod: &mut heapless::spsc::Producer, + shared_consumer: &Mutex>>>, +) -> Result<(), AsyncUartErrors> { + on_interrupt_rx_async_heapless_queue_overwriting( + unsafe { pac::Uarta::steal() }, + prod, + shared_consumer, + ) +} + +/// Interrupt handler for UART B. +/// +/// 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_uart_b_overwriting( + prod: &mut heapless::spsc::Producer, + shared_consumer: &Mutex>>>, +) -> Result<(), AsyncUartErrors> { + on_interrupt_rx_async_heapless_queue_overwriting( + unsafe { pac::Uartb::steal() }, + prod, + shared_consumer, + ) +} + +pub fn on_interrupt_rx_async_heapless_queue_overwriting( + uart: Uart, + prod: &mut heapless::spsc::Producer, + shared_consumer: &Mutex>>>, +) -> Result<(), AsyncUartErrors> { + 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; + 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.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; + 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(&uart, 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 UART A. +/// +/// Should be called in the user interrupt handler to enable asynchronous reception. +pub fn on_interrupt_uart_a( + prod: &mut heapless::spsc::Producer<'_, u8, N>, +) -> Result<(), AsyncUartErrors> { + on_interrupt_rx_async_heapless_queue(unsafe { pac::Uarta::steal() }, prod) +} + +/// Interrupt handler for UART B. +/// +/// Should be called in the user interrupt handler to enable asynchronous reception. +pub fn on_interrupt_uart_b( + prod: &mut heapless::spsc::Producer<'_, u8, N>, +) -> Result<(), AsyncUartErrors> { + on_interrupt_rx_async_heapless_queue(unsafe { pac::Uartb::steal() }, prod) +} + +pub fn on_interrupt_rx_async_heapless_queue( + uart: Uart, + prod: &mut heapless::spsc::Producer<'_, u8, N>, +) -> Result<(), AsyncUartErrors> { + //let uart = unsafe { Uart::steal() }; + 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(&uart, 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); + } +} + +/// Core data structure to allow asynchrnous UART reception. +/// +/// If the ring buffer becomes full, data will be lost. +pub struct RxAsync { + rx: Rx, + pub queue: heapless::spsc::Consumer<'static, u8, N>, +} + +impl ErrorType for RxAsync { + /// Error reporting is done using atomic booleans and the [get_and_clear_errors] function. + type Error = Infallible; +} + +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. + 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 { rx, queue } + } +} + +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.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 fut = RxFuture::new(&mut self.rx); + // Data is available, so read that data immediately. + let read_data = handle_data_in_queue(&mut self.queue); + if read_data > 0 { + return Ok(read_data); + } + // Await data. + let _ = fut.await; + Ok(handle_data_in_queue(&mut self.queue)) + } +} + +/// Core data structure to allow asynchrnous UART reception. +/// +/// If the ring buffer becomes full, the oldest data will be overwritten when using the +/// [on_interrupt_uart_a_overwriting] and [on_interrupt_uart_b_overwriting] interrupt handlers. +pub struct RxAsyncSharedConsumer { + rx: Rx, + queue: &'static Mutex>>>, +} + +impl ErrorType for RxAsyncSharedConsumer { + /// Error reporting is done using atomic booleans and the [get_and_clear_errors] function. + type Error = Infallible; +} + +impl RxAsyncSharedConsumer { + /// 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, + queue: &'static Mutex>>>, + ) -> Self { + rx.disable_interrupts(); + rx.disable(); + rx.clear_fifo(); + // Enable those together. + critical_section::with(|_| { + rx.enable_interrupts(); + rx.enable(); + }); + Self { rx, queue } + } +} + +impl embedded_io_async::Read for RxAsyncSharedConsumer { + 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.queue.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 = || { + critical_section::with(|cs| { + let mut consumer_ref = self.queue.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.rx); + // Data is available, so read that data immediately. + let read_data = handle_data_in_queue(); + if read_data > 0 { + return Ok(read_data); + } + // Await data. + let _ = fut.await; + let read_data = handle_data_in_queue(); + Ok(read_data) + } +} diff --git a/va108xx-hal/src/uart/asynch.rs b/va108xx-hal/src/uart/tx_asynch.rs similarity index 95% rename from va108xx-hal/src/uart/asynch.rs rename to va108xx-hal/src/uart/tx_asynch.rs index 97c24f7..6b97db3 100644 --- a/va108xx-hal/src/uart/asynch.rs +++ b/va108xx-hal/src/uart/tx_asynch.rs @@ -1,4 +1,4 @@ -//! # Async GPIO functionality for the VA108xx family. +//! # Async UART transmission functionality for the VA108xx 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 @@ -13,7 +13,7 @@ //! //! # Example //! -//! - [Async UART example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/async-gpio/examples/embassy/src/bin/async-uart.rs) +//! - [Async UART TX example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/embassy/src/bin/async-uart-tx.rs) use core::{cell::RefCell, future::Future}; use critical_section::Mutex; @@ -23,7 +23,7 @@ use portable_atomic::AtomicBool; use super::*; -static UART_WAKERS: [AtomicWaker; 2] = [const { AtomicWaker::new() }; 2]; +static UART_TX_WAKERS: [AtomicWaker; 2] = [const { AtomicWaker::new() }; 2]; static TX_CONTEXTS: [Mutex>; 2] = [const { Mutex::new(RefCell::new(TxContext::new())) }; 2]; // Completion flag. Kept outside of the context structure as an atomic to avoid @@ -72,7 +72,7 @@ fn on_interrupt_uart_tx(uart: Uart) { }); // Transfer is done. TX_DONE[Uart::IDX as usize].store(true, core::sync::atomic::Ordering::Relaxed); - UART_WAKERS[Uart::IDX as usize].wake(); + UART_TX_WAKERS[Uart::IDX as usize].wake(); return; } // Safety: We documented that the user provided slice must outlive the future, so we convert @@ -199,7 +199,7 @@ impl Future for TxFuture { self: core::pin::Pin<&mut Self>, cx: &mut core::task::Context<'_>, ) -> core::task::Poll { - UART_WAKERS[self.uart_idx].register(cx.waker()); + 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 diff --git a/vscode/launch.json b/vscode/launch.json index 20edd1a..01362f9 100644 --- a/vscode/launch.json +++ b/vscode/launch.json @@ -526,13 +526,37 @@ { "type": "cortex-debug", "request": "launch", - "name": "Async UART", + "name": "Async UART TX", "servertype": "jlink", "cwd": "${workspaceRoot}", "device": "Cortex-M0", "svdFile": "./va108xx/svd/va108xx.svd.patched", - "preLaunchTask": "async-uart", - "executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/async-uart", + "preLaunchTask": "async-uart-tx", + "executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/async-uart-tx", + "interface": "jtag", + "runToEntryPoint": "main", + "rttConfig": { + "enabled": true, + "address": "auto", + "decoders": [ + { + "port": 0, + "timestamp": true, + "type": "console" + } + ] + } + }, + { + "type": "cortex-debug", + "request": "launch", + "name": "Async UART RX", + "servertype": "jlink", + "cwd": "${workspaceRoot}", + "device": "Cortex-M0", + "svdFile": "./va108xx/svd/va108xx.svd.patched", + "preLaunchTask": "async-uart-rx", + "executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/async-uart-rx", "interface": "jtag", "runToEntryPoint": "main", "rttConfig": { @@ -548,4 +572,4 @@ } }, ] -} \ No newline at end of file +} diff --git a/vscode/tasks.json b/vscode/tasks.json index 671efe2..595a42c 100644 --- a/vscode/tasks.json +++ b/vscode/tasks.json @@ -277,13 +277,23 @@ ] }, { - "label": "async-uart", + "label": "async-uart-tx", "type": "shell", "command": "~/.cargo/bin/cargo", // note: full path to the cargo "args": [ "build", "--bin", - "async-uart" + "async-uart-tx" + ] + }, + { + "label": "async-uart-rx", + "type": "shell", + "command": "~/.cargo/bin/cargo", // note: full path to the cargo + "args": [ + "build", + "--bin", + "async-uart-rx" ] }, { @@ -309,4 +319,4 @@ ] } ] -} \ No newline at end of file +}