From 417f5b7f675bf5c06b38ea23882db551dd624c21 Mon Sep 17 00:00:00 2001
From: Robin Mueller <robin.mueller.m@gmail.com>
Date: Wed, 12 Feb 2025 21:13:53 +0100
Subject: [PATCH] completed async RX support as well

---
 examples/embassy/Cargo.toml                   |   3 +
 examples/embassy/src/bin/async-uart-rx.rs     | 171 +++++++
 .../bin/{async-uart.rs => async-uart-tx.rs}   |  12 +-
 examples/simple/examples/uart.rs              |   6 +-
 va108xx-hal/CHANGELOG.md                      |   2 +
 va108xx-hal/Cargo.toml                        |   2 +
 va108xx-hal/src/uart/mod.rs                   |  83 +++-
 va108xx-hal/src/uart/rx_asynch.rs             | 419 ++++++++++++++++++
 .../src/uart/{asynch.rs => tx_asynch.rs}      |  10 +-
 vscode/launch.json                            |  32 +-
 vscode/tasks.json                             |  16 +-
 11 files changed, 727 insertions(+), 29 deletions(-)
 create mode 100644 examples/embassy/src/bin/async-uart-rx.rs
 rename examples/embassy/src/bin/{async-uart.rs => async-uart-tx.rs} (82%)
 create mode 100644 va108xx-hal/src/uart/rx_asynch.rs
 rename va108xx-hal/src/uart/{asynch.rs => tx_asynch.rs} (95%)

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<Queue<u8, 256>> =
+    static_cell::ConstStaticCell::new(Queue::new());
+static PRODUCER_UART_A: Mutex<RefCell<Option<Producer<u8, 256>>>> = Mutex::new(RefCell::new(None));
+
+static QUEUE_UART_B: static_cell::ConstStaticCell<Queue<u8, 256>> =
+    static_cell::ConstStaticCell::new(Queue::new());
+static PRODUCER_UART_B: Mutex<RefCell<Option<Producer<u8, 256>>>> = Mutex::new(RefCell::new(None));
+static CONSUMER_UART_B: Mutex<RefCell<Option<Consumer<u8, 256>>>> = 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<pac::Uartb, 256>, mut tx: Tx<pac::Uartb>) {
+    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<IrqUartError>,
+    pub errors: Option<UartErrors>,
 }
 
 /// 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<IrqUartError>,
+    pub errors: Option<UartErrors>,
     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<Uart> {
 }
 
 impl<Uart: Instance> Rx<Uart> {
+    #[inline(always)]
     fn new(uart: Uart) -> Self {
         Self { uart }
     }
@@ -768,6 +804,7 @@ impl<Uart: Instance> Rx<Uart> {
     /// # 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<Uart: Instance> Rx<Uart> {
         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<Uart: Instance> Rx<Uart> {
         RxWithInterrupt::new(self)
     }
 
+    #[inline(always)]
     pub fn release(self) -> Uart {
         self.uart
     }
@@ -876,14 +923,17 @@ impl<Uart: Instance> embedded_io::Read for Rx<Uart> {
     }
 }
 
+#[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<Uart: Instance> Tx<Uart> {
     /// # 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<Uart: Instance> Tx<Uart> {
     /// # 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<Uart: Instance> RxWithInterrupt<Uart> {
 
     fn read_handler(
         &self,
-        errors: &mut Option<IrqUartError>,
+        errors: &mut Option<UartErrors>,
         read_res: &nb::Result<u8, RxError>,
     ) -> Option<u8> {
         match read_res {
@@ -1273,7 +1327,7 @@ impl<Uart: Instance> RxWithInterrupt<Uart> {
             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<Uart: Instance> RxWithInterrupt<Uart> {
         }
     }
 
-    fn check_for_errors(&self, errors: &mut Option<IrqUartError>) {
+    fn check_for_errors(&self, errors: &mut Option<UartErrors>) {
         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<Uart: Instance> RxWithInterrupt<Uart> {
     }
 }
 
-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<Uart: Instance>(_rx: &mut Rx<Uart>) -> 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<Self::Output> {
+        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: Instance>(uart: &Uart) -> Option<UartErrors> {
+    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: Instance>(
+    uart: &Uart,
+    rx_enabled: bool,
+    read_some_data: bool,
+    irq_end: u32,
+) -> Option<UartErrors> {
+    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<const N: usize>(
+    prod: &mut heapless::spsc::Producer<u8, N>,
+    shared_consumer: &Mutex<RefCell<Option<heapless::spsc::Consumer<'static, u8, N>>>>,
+) -> 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<const N: usize>(
+    prod: &mut heapless::spsc::Producer<u8, N>,
+    shared_consumer: &Mutex<RefCell<Option<heapless::spsc::Consumer<'static, u8, N>>>>,
+) -> 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: Instance, const N: usize>(
+    uart: Uart,
+    prod: &mut heapless::spsc::Producer<u8, N>,
+    shared_consumer: &Mutex<RefCell<Option<heapless::spsc::Consumer<'static, u8, N>>>>,
+) -> 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<const N: usize>(
+    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<const N: usize>(
+    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: Instance, const N: usize>(
+    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<Uart: Instance, const N: usize> {
+    rx: Rx<Uart>,
+    pub queue: heapless::spsc::Consumer<'static, u8, N>,
+}
+
+impl<Uart: Instance, const N: usize> ErrorType for RxAsync<Uart, N> {
+    /// Error reporting is done using atomic booleans and the [get_and_clear_errors] function.
+    type Error = Infallible;
+}
+
+impl<Uart: Instance, const N: usize> RxAsync<Uart, N> {
+    /// 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<Uart>, 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<Uart: Instance, const N: usize> embedded_io_async::Read for RxAsync<Uart, N> {
+    async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
+        // 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<Uart: Instance, const N: usize> {
+    rx: Rx<Uart>,
+    queue: &'static Mutex<RefCell<Option<Consumer<'static, u8, N>>>>,
+}
+
+impl<Uart: Instance, const N: usize> ErrorType for RxAsyncSharedConsumer<Uart, N> {
+    /// Error reporting is done using atomic booleans and the [get_and_clear_errors] function.
+    type Error = Infallible;
+}
+
+impl<Uart: Instance, const N: usize> RxAsyncSharedConsumer<Uart, N> {
+    /// 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<Uart>,
+        queue: &'static Mutex<RefCell<Option<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<Uart: Instance, const N: usize> embedded_io_async::Read for RxAsyncSharedConsumer<Uart, N> {
+    async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
+        // 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<RefCell<TxContext>>; 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: Instance>(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<Self::Output> {
-        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
+}
-- 
2.43.0