continue async uart tx
This commit is contained in:
parent
f781505ec5
commit
4da270346e
@ -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"
|
||||||
|
225
examples/embassy/src/bin/async-uart.rs
Normal file
225
examples/embassy/src/bin/async-uart.rs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
@ -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.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user