completed async RX support as well
This commit is contained in:
parent
3e796ef22b
commit
417f5b7f67
@ -9,7 +9,10 @@ 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 = "0.6"
|
||||||
embedded-io-async = "0.6"
|
embedded-io-async = "0.6"
|
||||||
|
heapless = "0.8"
|
||||||
|
static_cell = "2"
|
||||||
|
|
||||||
rtt-target = "0.6"
|
rtt-target = "0.6"
|
||||||
panic-rtt-target = "0.2"
|
panic-rtt-target = "0.2"
|
||||||
|
171
examples/embassy/src/bin/async-uart-rx.rs
Normal file
171
examples/embassy/src/bin/async-uart-rx.rs
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
@ -28,7 +38,7 @@ const STR_LIST: &[&str] = &[
|
|||||||
#[embassy_executor::main]
|
#[embassy_executor::main]
|
||||||
async fn main(_spawner: Spawner) {
|
async fn main(_spawner: Spawner) {
|
||||||
rtt_init_print!();
|
rtt_init_print!();
|
||||||
rprintln!("-- VA108xx Embassy Demo --");
|
rprintln!("-- VA108xx Async UART TX Demo --");
|
||||||
|
|
||||||
let mut dp = pac::Peripherals::take().unwrap();
|
let mut dp = pac::Peripherals::take().unwrap();
|
||||||
|
|
@ -27,15 +27,15 @@ fn main() -> ! {
|
|||||||
let gpioa = PinsA::new(&mut dp.sysconfig, dp.porta);
|
let gpioa = PinsA::new(&mut dp.sysconfig, dp.porta);
|
||||||
let tx = gpioa.pa9.into_funsel_2();
|
let tx = gpioa.pa9.into_funsel_2();
|
||||||
let rx = gpioa.pa8.into_funsel_2();
|
let rx = gpioa.pa8.into_funsel_2();
|
||||||
|
let uart = uart::Uart::new_without_interrupt(
|
||||||
let uarta = uart::Uart::new_without_interrupt(
|
|
||||||
&mut dp.sysconfig,
|
&mut dp.sysconfig,
|
||||||
50.MHz(),
|
50.MHz(),
|
||||||
dp.uarta,
|
dp.uarta,
|
||||||
(tx, rx),
|
(tx, rx),
|
||||||
115200.Hz(),
|
115200.Hz(),
|
||||||
);
|
);
|
||||||
let (mut tx, mut rx) = uarta.split();
|
|
||||||
|
let (mut tx, mut rx) = uart.split();
|
||||||
writeln!(tx, "Hello World\r").unwrap();
|
writeln!(tx, "Hello World\r").unwrap();
|
||||||
loop {
|
loop {
|
||||||
// Echo what is received on the serial link.
|
// Echo what is received on the serial link.
|
||||||
|
@ -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
|
- `enable_interrupt` and `disable_interrupt` renamed to `enable_nvic_interrupt` and
|
||||||
`disable_nvic_interrupt` to distinguish them from peripheral interrupts more clearly.
|
`disable_nvic_interrupt` to distinguish them from peripheral interrupts more clearly.
|
||||||
- `port_mux` renamed to `port_function_select`
|
- `port_mux` renamed to `port_function_select`
|
||||||
|
- Renamed `IrqUartErrors` to `UartErrors`.
|
||||||
|
|
||||||
## Added
|
## Added
|
||||||
|
|
||||||
@ -49,6 +50,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
methods.
|
methods.
|
||||||
- Asynchronous GPIO support.
|
- Asynchronous GPIO support.
|
||||||
- Asynchronous UART TX support.
|
- Asynchronous UART TX support.
|
||||||
|
- Asynchronous UART RX support.
|
||||||
- Add new `get_tim_raw` unsafe method to retrieve TIM peripheral blocks.
|
- Add new `get_tim_raw` unsafe method to retrieve TIM peripheral blocks.
|
||||||
- `Uart::with_with_interrupt` and `Uart::new_without_interrupt`
|
- `Uart::with_with_interrupt` and `Uart::new_without_interrupt`
|
||||||
|
|
||||||
|
@ -24,6 +24,8 @@ fugit = "0.3"
|
|||||||
typenum = "1"
|
typenum = "1"
|
||||||
critical-section = "1"
|
critical-section = "1"
|
||||||
delegate = ">=0.12, <=0.13"
|
delegate = ">=0.12, <=0.13"
|
||||||
|
heapless = "0.8"
|
||||||
|
static_cell = "2"
|
||||||
thiserror = { version = "2", default-features = false }
|
thiserror = { version = "2", default-features = false }
|
||||||
void = { version = "1", default-features = false }
|
void = { version = "1", default-features = false }
|
||||||
once_cell = {version = "1", default-features = false }
|
once_cell = {version = "1", default-features = false }
|
||||||
|
@ -22,6 +22,12 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use embedded_hal_nb::serial::Read;
|
use embedded_hal_nb::serial::Read;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Bank {
|
||||||
|
A = 0,
|
||||||
|
B = 1,
|
||||||
|
}
|
||||||
|
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
// Type-Level support
|
// Type-Level support
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
@ -258,7 +264,7 @@ impl IrqContextTimeoutOrMaxSize {
|
|||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct IrqResult {
|
pub struct IrqResult {
|
||||||
pub bytes_read: usize,
|
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
|
/// This struct is used to return the default IRQ handler result to the user
|
||||||
@ -266,7 +272,7 @@ pub struct IrqResult {
|
|||||||
pub struct IrqResultMaxSizeOrTimeout {
|
pub struct IrqResultMaxSizeOrTimeout {
|
||||||
complete: bool,
|
complete: bool,
|
||||||
timeout: bool,
|
timeout: bool,
|
||||||
pub errors: Option<IrqUartError>,
|
pub errors: Option<UartErrors>,
|
||||||
pub bytes_read: usize,
|
pub bytes_read: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -319,14 +325,15 @@ enum IrqReceptionMode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Copy, Clone)]
|
#[derive(Default, Debug, Copy, Clone)]
|
||||||
pub struct IrqUartError {
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub struct UartErrors {
|
||||||
overflow: bool,
|
overflow: bool,
|
||||||
framing: bool,
|
framing: bool,
|
||||||
parity: bool,
|
parity: bool,
|
||||||
other: bool,
|
other: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IrqUartError {
|
impl UartErrors {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn overflow(&self) -> bool {
|
pub fn overflow(&self) -> bool {
|
||||||
self.overflow
|
self.overflow
|
||||||
@ -348,7 +355,7 @@ impl IrqUartError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IrqUartError {
|
impl UartErrors {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn error(&self) -> bool {
|
pub fn error(&self) -> bool {
|
||||||
self.overflow || self.framing || self.parity
|
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.
|
/// Serial receiver.
|
||||||
///
|
///
|
||||||
/// Can be created by using the [Uart::split] or [UartBase::split] API.
|
/// 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> {
|
impl<Uart: Instance> Rx<Uart> {
|
||||||
|
#[inline(always)]
|
||||||
fn new(uart: Uart) -> Self {
|
fn new(uart: Uart) -> Self {
|
||||||
Self { uart }
|
Self { uart }
|
||||||
}
|
}
|
||||||
@ -768,6 +804,7 @@ impl<Uart: Instance> Rx<Uart> {
|
|||||||
/// # Safety
|
/// # Safety
|
||||||
///
|
///
|
||||||
/// You must ensure that only registers related to the operation of the RX side are used.
|
/// You must ensure that only registers related to the operation of the RX side are used.
|
||||||
|
#[inline(always)]
|
||||||
pub unsafe fn uart(&self) -> &Uart {
|
pub unsafe fn uart(&self) -> &Uart {
|
||||||
&self.uart
|
&self.uart
|
||||||
}
|
}
|
||||||
@ -777,14 +814,23 @@ impl<Uart: Instance> Rx<Uart> {
|
|||||||
self.uart.fifo_clr().write(|w| w.rxfifo().set_bit());
|
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]
|
#[inline]
|
||||||
pub fn enable(&mut self) {
|
pub fn enable(&mut self) {
|
||||||
self.uart.enable().modify(|_, w| w.rxenable().set_bit());
|
enable_rx(unsafe { Uart::reg_block() });
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn disable(&mut self) {
|
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.
|
/// Low level function to read a word from the UART FIFO.
|
||||||
@ -818,6 +864,7 @@ impl<Uart: Instance> Rx<Uart> {
|
|||||||
RxWithInterrupt::new(self)
|
RxWithInterrupt::new(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
pub fn release(self) -> Uart {
|
pub fn release(self) -> Uart {
|
||||||
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) {
|
pub fn enable_tx(uart: &uart_base::RegisterBlock) {
|
||||||
uart.enable().modify(|_, w| w.txenable().set_bit());
|
uart.enable().modify(|_, w| w.txenable().set_bit());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
pub fn disable_tx(uart: &uart_base::RegisterBlock) {
|
pub fn disable_tx(uart: &uart_base::RegisterBlock) {
|
||||||
uart.enable().modify(|_, w| w.txenable().clear_bit());
|
uart.enable().modify(|_, w| w.txenable().clear_bit());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
pub fn enable_tx_interrupts(uart: &uart_base::RegisterBlock) {
|
pub fn enable_tx_interrupts(uart: &uart_base::RegisterBlock) {
|
||||||
uart.irq_enb().modify(|_, w| {
|
uart.irq_enb().modify(|_, w| {
|
||||||
w.irq_tx().set_bit();
|
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) {
|
pub fn disable_tx_interrupts(uart: &uart_base::RegisterBlock) {
|
||||||
uart.irq_enb().modify(|_, w| {
|
uart.irq_enb().modify(|_, w| {
|
||||||
w.irq_tx().clear_bit();
|
w.irq_tx().clear_bit();
|
||||||
@ -913,12 +964,14 @@ impl<Uart: Instance> Tx<Uart> {
|
|||||||
/// # Safety
|
/// # Safety
|
||||||
///
|
///
|
||||||
/// Circumvents the HAL safety guarantees.
|
/// Circumvents the HAL safety guarantees.
|
||||||
|
#[inline(always)]
|
||||||
pub unsafe fn steal() -> Self {
|
pub unsafe fn steal() -> Self {
|
||||||
Self {
|
Self {
|
||||||
uart: Uart::steal(),
|
uart: Uart::steal(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
fn new(uart: Uart) -> Self {
|
fn new(uart: Uart) -> Self {
|
||||||
Self { uart }
|
Self { uart }
|
||||||
}
|
}
|
||||||
@ -928,6 +981,7 @@ impl<Uart: Instance> Tx<Uart> {
|
|||||||
/// # Safety
|
/// # Safety
|
||||||
///
|
///
|
||||||
/// You must ensure that only registers related to the operation of the TX side are used.
|
/// You must ensure that only registers related to the operation of the TX side are used.
|
||||||
|
#[inline(always)]
|
||||||
pub unsafe fn uart(&self) -> &Uart {
|
pub unsafe fn uart(&self) -> &Uart {
|
||||||
&self.uart
|
&self.uart
|
||||||
}
|
}
|
||||||
@ -1265,7 +1319,7 @@ impl<Uart: Instance> RxWithInterrupt<Uart> {
|
|||||||
|
|
||||||
fn read_handler(
|
fn read_handler(
|
||||||
&self,
|
&self,
|
||||||
errors: &mut Option<IrqUartError>,
|
errors: &mut Option<UartErrors>,
|
||||||
read_res: &nb::Result<u8, RxError>,
|
read_res: &nb::Result<u8, RxError>,
|
||||||
) -> Option<u8> {
|
) -> Option<u8> {
|
||||||
match read_res {
|
match read_res {
|
||||||
@ -1273,7 +1327,7 @@ impl<Uart: Instance> RxWithInterrupt<Uart> {
|
|||||||
Err(nb::Error::WouldBlock) => None,
|
Err(nb::Error::WouldBlock) => None,
|
||||||
Err(nb::Error::Other(e)) => {
|
Err(nb::Error::Other(e)) => {
|
||||||
// Ensure `errors` is Some(IrqUartError), initializing if it's None
|
// 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`
|
// Now we can safely modify fields inside `err`
|
||||||
match e {
|
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();
|
let rx_status = self.uart().rxstatus().read();
|
||||||
|
|
||||||
if rx_status.rxovr().bit_is_set()
|
if rx_status.rxovr().bit_is_set()
|
||||||
|| rx_status.rxfrm().bit_is_set()
|
|| rx_status.rxfrm().bit_is_set()
|
||||||
|| rx_status.rxpar().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() {
|
if rx_status.rxovr().bit_is_set() {
|
||||||
err.overflow = true;
|
err.overflow = true;
|
||||||
@ -1330,5 +1384,8 @@ impl<Uart: Instance> RxWithInterrupt<Uart> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod asynch;
|
pub mod tx_asynch;
|
||||||
pub use asynch::*;
|
pub use tx_asynch::*;
|
||||||
|
|
||||||
|
pub mod rx_asynch;
|
||||||
|
pub use rx_asynch::*;
|
||||||
|
419
va108xx-hal/src/uart/rx_asynch.rs
Normal file
419
va108xx-hal/src/uart/rx_asynch.rs
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -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 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
|
//! This trait allows for asynchronous sending of data streams. Please note that this module does
|
||||||
@ -13,7 +13,7 @@
|
|||||||
//!
|
//!
|
||||||
//! # Example
|
//! # 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 core::{cell::RefCell, future::Future};
|
||||||
|
|
||||||
use critical_section::Mutex;
|
use critical_section::Mutex;
|
||||||
@ -23,7 +23,7 @@ use portable_atomic::AtomicBool;
|
|||||||
|
|
||||||
use super::*;
|
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] =
|
static TX_CONTEXTS: [Mutex<RefCell<TxContext>>; 2] =
|
||||||
[const { Mutex::new(RefCell::new(TxContext::new())) }; 2];
|
[const { Mutex::new(RefCell::new(TxContext::new())) }; 2];
|
||||||
// Completion flag. Kept outside of the context structure as an atomic to avoid
|
// 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.
|
// Transfer is done.
|
||||||
TX_DONE[Uart::IDX as usize].store(true, core::sync::atomic::Ordering::Relaxed);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
// Safety: We documented that the user provided slice must outlive the future, so we convert
|
// 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>,
|
self: core::pin::Pin<&mut Self>,
|
||||||
cx: &mut core::task::Context<'_>,
|
cx: &mut core::task::Context<'_>,
|
||||||
) -> core::task::Poll<Self::Output> {
|
) -> 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) {
|
if TX_DONE[self.uart_idx].swap(false, core::sync::atomic::Ordering::Relaxed) {
|
||||||
let progress = critical_section::with(|cs| {
|
let progress = critical_section::with(|cs| {
|
||||||
TX_CONTEXTS[self.uart_idx].borrow(cs).borrow().progress
|
TX_CONTEXTS[self.uart_idx].borrow(cs).borrow().progress
|
@ -526,13 +526,37 @@
|
|||||||
{
|
{
|
||||||
"type": "cortex-debug",
|
"type": "cortex-debug",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"name": "Async UART",
|
"name": "Async UART TX",
|
||||||
"servertype": "jlink",
|
"servertype": "jlink",
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
"device": "Cortex-M0",
|
"device": "Cortex-M0",
|
||||||
"svdFile": "./va108xx/svd/va108xx.svd.patched",
|
"svdFile": "./va108xx/svd/va108xx.svd.patched",
|
||||||
"preLaunchTask": "async-uart",
|
"preLaunchTask": "async-uart-tx",
|
||||||
"executable": "${workspaceFolder}/target/thumbv6m-none-eabi/debug/async-uart",
|
"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",
|
"interface": "jtag",
|
||||||
"runToEntryPoint": "main",
|
"runToEntryPoint": "main",
|
||||||
"rttConfig": {
|
"rttConfig": {
|
||||||
@ -548,4 +572,4 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -277,13 +277,23 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "async-uart",
|
"label": "async-uart-tx",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "~/.cargo/bin/cargo", // note: full path to the cargo
|
"command": "~/.cargo/bin/cargo", // note: full path to the cargo
|
||||||
"args": [
|
"args": [
|
||||||
"build",
|
"build",
|
||||||
"--bin",
|
"--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 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user