continue async uart tx

This commit is contained in:
Robin Müller 2025-02-11 10:18:32 +01:00
parent f781505ec5
commit 4da270346e
3 changed files with 253 additions and 0 deletions

View File

@ -9,6 +9,7 @@ cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
cortex-m-rt = "0.7" cortex-m-rt = "0.7"
embedded-hal = "1" embedded-hal = "1"
embedded-hal-async = "1" embedded-hal-async = "1"
embedded-io-async = "0.6"
rtt-target = "0.6" rtt-target = "0.6"
panic-rtt-target = "0.2" panic-rtt-target = "0.2"

View File

@ -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<RefCell<TxContext>> = 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<Cell<SliceWithRawPtr>> = Mutex::new(Cell::new(SliceWithRawPtr::new_empty()));
pub struct TxFuture;
impl TxFuture {
pub unsafe fn new<Uart: Instance>(tx: &mut Tx<Uart>, 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<usize, TxOverrunError>;
fn poll(
self: core::pin::Pin<&mut Self>,
cx: &mut core::task::Context<'_>,
) -> core::task::Poll<Self::Output> {
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<Uart: Instance> {
tx: Tx<Uart>,
}
#[derive(Debug)]
pub struct TxOverrunError;
impl<Uart: Instance> ErrorType for TxUartAsync<Uart> {
type Error = TxOverrunError;
}
impl<Uart: Instance> Write for TxUartAsync<Uart> {
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
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();
}
}

View File

@ -845,6 +845,33 @@ impl<Uart: Instance> Tx<Uart> {
self.0.enable().modify(|_, w| w.txenable().clear_bit()); 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. /// Low level function to write a word to the UART FIFO.
/// ///
/// Uses the [nb] API to allow usage in blocking and non-blocking contexts. /// Uses the [nb] API to allow usage in blocking and non-blocking contexts.