continue async uart example

This commit is contained in:
Robin Müller 2025-02-11 17:24:10 +01:00 committed by Robin Mueller
parent 4da270346e
commit beb56efca6

View File

@ -1,8 +1,7 @@
#![no_std]
#![no_main]
use core::{
cell::{Cell, RefCell, UnsafeCell},
convert::Infallible,
cell::RefCell,
future::Future,
ptr::{self},
};
@ -13,101 +12,117 @@ 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},
uart::{self, Instance, Tx},
};
const SYSCLK_FREQ: Hertz = Hertz::from_raw(50_000_000);
static UART_0_WAKER: AtomicWaker = AtomicWaker::new();
static UART_WAKERS: [AtomicWaker; 2] = [const { AtomicWaker::new() }; 2];
static TX_CONTEXTS: [Mutex<RefCell<TxContext>>; 2] =
[const { Mutex::new(RefCell::new(TxContext::new())) }; 2];
#[derive(Debug, Copy, Clone)]
pub struct TxContext {
progress: usize,
done: bool,
tx_overrun: bool,
data: SliceWithRawPtr,
slice: RawBufSlice,
}
#[allow(clippy::new_without_default)]
impl TxContext {
pub const fn new() -> Self {
Self {
progress: 0,
done: false,
tx_overrun: false,
data: SliceWithRawPtr::new_empty(),
slice: RawBufSlice::new_empty(),
}
}
}
static TX_CONTEXT: Mutex<RefCell<TxContext>> = Mutex::new(RefCell::new(TxContext::new()));
#[derive(Debug, Copy, Clone)]
pub struct SliceWithRawPtr {
struct RawBufSlice {
data: *const u8,
len: usize,
}
/// Safety: We use a mutex to ensure concurrent access is valid.
unsafe impl Send for SliceWithRawPtr {}
/// Safety: This type MUST be used with mutex to ensure concurrent access is valid.
unsafe impl Send for RawBufSlice {}
impl SliceWithRawPtr {
pub const unsafe fn new(data: &[u8]) -> Self {
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(),
}
}
pub const fn new_empty() -> Self {
const fn new_empty() -> Self {
Self {
data: 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();
}
}
//static TX_DATA: Mutex<Cell<SliceWithRawPtr>> = Mutex::new(Cell::new(SliceWithRawPtr::new_empty()));
pub struct TxFuture;
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<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 context_ref = TX_CONTEXTS[Uart::IDX as usize].borrow(cs);
let mut context = context_ref.borrow_mut();
context.data.set(data);
context.slice.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) });
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_CONTEXT.borrow(cs);
let context_ref = TX_CONTEXTS[Uart::IDX as usize].borrow(cs);
let mut context = context_ref.borrow_mut();
context.progress += init_fill_count;
});
tx.enable_interrupts();
tx.enable();
Self
Self {
uart_idx: Uart::IDX as usize,
}
}
}
@ -118,9 +133,9 @@ impl Future for TxFuture {
self: core::pin::Pin<&mut Self>,
cx: &mut core::task::Context<'_>,
) -> core::task::Poll<Self::Output> {
UART_0_WAKER.register(cx.waker());
UART_WAKERS[self.uart_idx].register(cx.waker());
let (done, progress) = critical_section::with(|cs| {
let context = TX_CONTEXT.borrow(cs).borrow();
let context = TX_CONTEXTS[self.uart_idx].borrow(cs).borrow();
(context.done, context.progress)
});
if done {
@ -130,14 +145,15 @@ impl Future for TxFuture {
}
}
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;
pub fn on_interrupt_uart_a_tx() {
on_interrupt_uart_tx(unsafe { pac::Uarta::steal() });
}
pub fn on_interrupt_uart_b_tx() {
on_interrupt_uart_tx(unsafe { pac::Uartb::steal() });
}
fn on_interrupt_uart_tx<Uart: Instance>(uart: Uart) {
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() {
@ -147,28 +163,39 @@ pub fn on_interrupt_uart_tx() {
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);
let context_ref = TX_CONTEXTS[Uart::IDX as usize].borrow(cs);
*context_ref.borrow()
});
context.tx_overrun = unexpected_overrun;
if context.progress >= context.data.len && !tx_status.wrbusy().bit_is_set() {
if context.progress >= context.slice.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);
let context_ref = TX_CONTEXTS[Uart::IDX as usize].borrow(cs);
*context_ref.borrow_mut() = context;
});
// TODO: Handle completion, disable TX interrupt and disable TX.
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();
// TODO: Write data into the FIFO as long as it is not full. Increment progress by written
// bytes.
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_CONTEXT.borrow(cs);
let context_ref = TX_CONTEXTS[Uart::IDX as usize].borrow(cs);
*context_ref.borrow_mut() = context;
});
}
@ -180,6 +207,12 @@ pub struct TxUartAsync<Uart: Instance> {
#[derive(Debug)]
pub struct TxOverrunError;
impl embedded_io_async::Error for TxOverrunError {
fn kind(&self) -> embedded_io_async::ErrorKind {
embedded_io_async::ErrorKind::Other
}
}
impl<Uart: Instance> ErrorType for TxUartAsync<Uart> {
type Error = TxOverrunError;
}
@ -187,7 +220,7 @@ impl<Uart: Instance> ErrorType for TxUartAsync<Uart> {
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?)
fut.await
}
}
@ -214,6 +247,15 @@ async fn main(_spawner: Spawner) {
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 = porta.pa9.into_funsel_2();
let rx = porta.pa8.into_funsel_2();
let uarta = uart::Uart::new(&mut dp.sysconfig, 50.MHz(), dp.uarta, (tx, rx), 115200.Hz());
let (tx, _rx) = uarta.split();
let mut async_tx = TxUartAsync { tx };
let send_data = b"Hello World\r\n";
async_tx.write(send_data).await.ok();
let mut ticker = Ticker::every(Duration::from_secs(1));
loop {
ticker.next().await;