From 4da270346e1779e0f4af8288c0c1b2f51f7afa7e Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Tue, 11 Feb 2025 10:18:32 +0100 Subject: [PATCH] continue async uart tx --- examples/embassy/Cargo.toml | 1 + examples/embassy/src/bin/async-uart.rs | 225 +++++++++++++++++++++++++ va108xx-hal/src/uart.rs | 27 +++ 3 files changed, 253 insertions(+) create mode 100644 examples/embassy/src/bin/async-uart.rs diff --git a/examples/embassy/Cargo.toml b/examples/embassy/Cargo.toml index 717adab..d00303a 100644 --- a/examples/embassy/Cargo.toml +++ b/examples/embassy/Cargo.toml @@ -9,6 +9,7 @@ cortex-m = { version = "0.7", features = ["critical-section-single-core"] } cortex-m-rt = "0.7" embedded-hal = "1" embedded-hal-async = "1" +embedded-io-async = "0.6" rtt-target = "0.6" panic-rtt-target = "0.2" diff --git a/examples/embassy/src/bin/async-uart.rs b/examples/embassy/src/bin/async-uart.rs new file mode 100644 index 0000000..79a5519 --- /dev/null +++ b/examples/embassy/src/bin/async-uart.rs @@ -0,0 +1,225 @@ +#![no_std] +#![no_main] +use core::{ + cell::{Cell, RefCell, UnsafeCell}, + convert::Infallible, + future::Future, + ptr::{self}, +}; +use critical_section::Mutex; +use embassy_executor::Spawner; +use embassy_sync::waitqueue::AtomicWaker; +use embassy_time::{Duration, Instant, Ticker}; +use embedded_hal::digital::StatefulOutputPin; +use embedded_io_async::{ErrorType, Write}; +use panic_rtt_target as _; +use portable_atomic::{AtomicBool, AtomicUsize}; +use rtt_target::{rprintln, rtt_init_print}; +use va108xx_embassy::embassy; +use va108xx_hal::{ + gpio::PinsA, + pac, + prelude::*, + uart::{Instance, Tx}, +}; + +const SYSCLK_FREQ: Hertz = Hertz::from_raw(50_000_000); + +static UART_0_WAKER: AtomicWaker = AtomicWaker::new(); + +#[derive(Debug, Copy, Clone)] +pub struct TxContext { + progress: usize, + done: bool, + tx_overrun: bool, + data: SliceWithRawPtr, +} + +impl TxContext { + pub const fn new() -> Self { + Self { + progress: 0, + done: false, + tx_overrun: false, + data: SliceWithRawPtr::new_empty(), + } + } +} +static TX_CONTEXT: Mutex> = Mutex::new(RefCell::new(TxContext::new())); + +#[derive(Debug, Copy, Clone)] +pub struct SliceWithRawPtr { + data: *const u8, + len: usize, +} + +/// Safety: We use a mutex to ensure concurrent access is valid. +unsafe impl Send for SliceWithRawPtr {} + +impl SliceWithRawPtr { + pub const unsafe fn new(data: &[u8]) -> Self { + Self { + data: data.as_ptr(), + len: data.len(), + } + } + pub const fn new_empty() -> Self { + Self { + data: ptr::null(), + len: 0, + } + } + + pub unsafe fn set(&mut self, data: &[u8]) { + self.data = data.as_ptr(); + self.len = data.len(); + } +} +//static TX_DATA: Mutex> = Mutex::new(Cell::new(SliceWithRawPtr::new_empty())); + +pub struct TxFuture; + +impl TxFuture { + pub unsafe fn new(tx: &mut Tx, data: &[u8]) -> Self { + tx.disable_interrupts(); + tx.disable(); + tx.clear_fifo(); + critical_section::with(|cs| { + let context_ref = TX_CONTEXT.borrow(cs); + let mut context = context_ref.borrow_mut(); + context.data.set(data); + context.progress = 0; + context.done = false; + }); + let uart_tx = unsafe { tx.uart() }; + let init_fill_count = core::cmp::min(data.len(), 16); + // We fill the FIFO. + for idx in 0..init_fill_count { + uart_tx + .data() + .write(|w| unsafe { w.bits(data[idx] as u32) }); + } + + critical_section::with(|cs| { + let context_ref = TX_CONTEXT.borrow(cs); + let mut context = context_ref.borrow_mut(); + context.progress += init_fill_count; + }); + tx.enable_interrupts(); + tx.enable(); + Self + } +} + +impl Future for TxFuture { + type Output = Result; + + fn poll( + self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> core::task::Poll { + UART_0_WAKER.register(cx.waker()); + let (done, progress) = critical_section::with(|cs| { + let context = TX_CONTEXT.borrow(cs).borrow(); + (context.done, context.progress) + }); + if done { + return core::task::Poll::Ready(Ok(progress)); + } + core::task::Poll::Pending + } +} + +pub fn on_interrupt_uart_tx() { + //let tx_active = TX_ACTIVE.load(portable_atomic::Ordering::Relaxed); + //if !tx_active { + //return; + //} + // TODO: Check whether any TX IRQ is enabled first. + let periphs = unsafe { pac::Peripherals::steal() }; + let uart = periphs.uarta; + 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_CONTEXT.borrow(cs); + *context_ref.borrow() + }); + context.tx_overrun = unexpected_overrun; + if context.progress >= context.data.len && !tx_status.wrbusy().bit_is_set() { + // Transfer is done. + context.done = true; + // Write back updated context structure. + critical_section::with(|cs| { + let context_ref = TX_CONTEXT.borrow(cs); + *context_ref.borrow_mut() = context; + }); + // TODO: Handle completion, disable TX interrupt and disable TX. + return; + } + let wrrdy = uart.txstatus().read().wrrdy().bit_is_set(); + // TODO: Write data into the FIFO as long as it is not full. Increment progress by written + // bytes. + + // Write back updated context structure. + critical_section::with(|cs| { + let context_ref = TX_CONTEXT.borrow(cs); + *context_ref.borrow_mut() = context; + }); +} + +pub struct TxUartAsync { + tx: Tx, +} + +#[derive(Debug)] +pub struct TxOverrunError; + +impl ErrorType for TxUartAsync { + type Error = TxOverrunError; +} + +impl Write for TxUartAsync { + async fn write(&mut self, buf: &[u8]) -> Result { + let fut = unsafe { TxFuture::new(&mut self.tx, buf) }; + Ok(fut.await?) + } +} + +// main is itself an async function. +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + rtt_init_print!(); + rprintln!("-- VA108xx Embassy 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, Some(dp.ioconfig), 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 mut ticker = Ticker::every(Duration::from_secs(1)); + loop { + ticker.next().await; + rprintln!("Current time: {}", Instant::now().as_secs()); + led0.toggle().ok(); + led1.toggle().ok(); + led2.toggle().ok(); + } +} diff --git a/va108xx-hal/src/uart.rs b/va108xx-hal/src/uart.rs index fa6daa5..5e42c71 100644 --- a/va108xx-hal/src/uart.rs +++ b/va108xx-hal/src/uart.rs @@ -845,6 +845,33 @@ 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) { + self.0.irq_enb().modify(|_, w| { + w.irq_tx().set_bit(); + w.irq_tx_status().set_bit(); + w.irq_tx_empty().set_bit() + }); + } + + /// Disables the IRQ_TX, IRQ_TX_STATUS and IRQ_TX_EMPTY interrupts. + /// + /// [Self::enable_interrupts] documents the interrupts. + #[inline] + pub fn disable_interrupts(&self) { + self.0.irq_enb().modify(|_, w| { + w.irq_tx().clear_bit(); + w.irq_tx_empty().clear_bit(); + w.irq_tx_empty().clear_bit() + }); + } + /// Low level function to write a word to the UART FIFO. /// /// Uses the [nb] API to allow usage in blocking and non-blocking contexts.