added async support for UART
This commit is contained in:
parent
8fc9d12046
commit
7999b13c94
@ -13,7 +13,9 @@ embedded-hal-async = "1"
|
|||||||
embedded-io-async = "0.6"
|
embedded-io-async = "0.6"
|
||||||
|
|
||||||
rtt-target = { version = "0.5" }
|
rtt-target = { version = "0.5" }
|
||||||
|
heapless = "0.8"
|
||||||
panic-rtt-target = { version = "0.1" }
|
panic-rtt-target = { version = "0.1" }
|
||||||
|
static_cell = "2"
|
||||||
critical-section = "1"
|
critical-section = "1"
|
||||||
once_cell = { version = "1", default-features = false, features = ["critical-section"] }
|
once_cell = { version = "1", default-features = false, features = ["critical-section"] }
|
||||||
ringbuf = { version = "0.4", default-features = false }
|
ringbuf = { version = "0.4", default-features = false }
|
||||||
|
111
examples/embassy/src/bin/async-uart-rx.rs
Normal file
111
examples/embassy/src/bin/async-uart-rx.rs
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
//! Asynchronous UART reception example application.
|
||||||
|
//!
|
||||||
|
//! This application receives data on two UARTs permanently using a ring buffer.
|
||||||
|
//! The ring buffer are read them asynchronously.
|
||||||
|
//! It uses PORTG0 as TX pin and PORTG1 as RX pin, which is the UART0 on the PEB1 board.
|
||||||
|
//!
|
||||||
|
//! Instructions:
|
||||||
|
//!
|
||||||
|
//! 1. Tie a USB to UART converter with RX to PORTG0 and TX to PORTG1.
|
||||||
|
//! 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_example::EXTCLK_FREQ;
|
||||||
|
use embassy_executor::Spawner;
|
||||||
|
use embassy_time::Instant;
|
||||||
|
use embedded_io::Write;
|
||||||
|
use embedded_io_async::Read;
|
||||||
|
use heapless::spsc::{Producer, Queue};
|
||||||
|
use panic_rtt_target as _;
|
||||||
|
use rtt_target::{rprintln, rtt_init_print};
|
||||||
|
use va416xx_hal::{
|
||||||
|
gpio::PinsG,
|
||||||
|
pac::{self, interrupt},
|
||||||
|
prelude::*,
|
||||||
|
time::Hertz,
|
||||||
|
uart::{
|
||||||
|
self,
|
||||||
|
rx_asynch::{on_interrupt_rx, RxAsync},
|
||||||
|
Bank,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
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));
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
// Initialize the systick interrupt & obtain the token to prove that we did
|
||||||
|
// Use the external clock connected to XTAL_N.
|
||||||
|
let clocks = dp
|
||||||
|
.clkgen
|
||||||
|
.constrain()
|
||||||
|
.xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
|
||||||
|
.freeze(&mut dp.sysconfig)
|
||||||
|
.unwrap();
|
||||||
|
// Safety: Only called once here.
|
||||||
|
unsafe {
|
||||||
|
va416xx_embassy::init(
|
||||||
|
&mut dp.sysconfig,
|
||||||
|
&dp.irq_router,
|
||||||
|
dp.tim15,
|
||||||
|
dp.tim14,
|
||||||
|
&clocks,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let portg = PinsG::new(&mut dp.sysconfig, dp.portg);
|
||||||
|
let mut led = portg.pg5.into_readable_push_pull_output();
|
||||||
|
|
||||||
|
let tx = portg.pg0.into_funsel_1();
|
||||||
|
let rx = portg.pg1.into_funsel_1();
|
||||||
|
|
||||||
|
let uarta = uart::Uart::new(&mut dp.sysconfig, dp.uart0, (tx, rx), 115200.Hz(), &clocks);
|
||||||
|
|
||||||
|
let (mut tx_uart_a, rx_uart_a) = uarta.split();
|
||||||
|
let (prod_uart_a, cons_uart_a) = QUEUE_UART_A.take().split();
|
||||||
|
// Pass the producer to the interrupt handler.
|
||||||
|
critical_section::with(|cs| {
|
||||||
|
*PRODUCER_UART_A.borrow(cs).borrow_mut() = Some(prod_uart_a);
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut async_uart_rx = RxAsync::new(rx_uart_a, cons_uart_a);
|
||||||
|
let mut buf = [0u8; 256];
|
||||||
|
loop {
|
||||||
|
rprintln!("Current time UART A: {}", Instant::now().as_secs());
|
||||||
|
led.toggle();
|
||||||
|
let read_bytes = async_uart_rx.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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[interrupt]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn UART0_RX() {
|
||||||
|
let mut prod =
|
||||||
|
critical_section::with(|cs| PRODUCER_UART_A.borrow(cs).borrow_mut().take().unwrap());
|
||||||
|
let errors = on_interrupt_rx(Bank::Uart0, &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);
|
||||||
|
}
|
||||||
|
}
|
96
examples/embassy/src/bin/async-uart-tx.rs
Normal file
96
examples/embassy/src/bin/async-uart-tx.rs
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
//! Asynchronous UART transmission example application.
|
||||||
|
//!
|
||||||
|
//! This application receives sends 4 strings with different sizes permanently.
|
||||||
|
//! It uses PORTG0 as TX pin and PORTG1 as RX pin, which is the UART0 on the PEB1 board.
|
||||||
|
//!
|
||||||
|
//! Instructions:
|
||||||
|
//!
|
||||||
|
//! 1. Tie a USB to UART converter with RX to PORTG0 and TX to PORTG1.
|
||||||
|
//! 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 embassy_example::EXTCLK_FREQ;
|
||||||
|
use embassy_executor::Spawner;
|
||||||
|
use embassy_time::{Duration, Instant, Ticker};
|
||||||
|
use embedded_io_async::Write;
|
||||||
|
use panic_rtt_target as _;
|
||||||
|
use rtt_target::{rprintln, rtt_init_print};
|
||||||
|
use va416xx_hal::{
|
||||||
|
gpio::PinsG,
|
||||||
|
pac::{self, interrupt},
|
||||||
|
prelude::*,
|
||||||
|
time::Hertz,
|
||||||
|
uart::{
|
||||||
|
self,
|
||||||
|
tx_asynch::{on_interrupt_tx, TxAsync},
|
||||||
|
Bank,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const STR_LIST: &[&str] = &[
|
||||||
|
"Hello World\r\n",
|
||||||
|
"Smoll\r\n",
|
||||||
|
"A string which is larger than the FIFO size\r\n",
|
||||||
|
"A really large string which is significantly larger than the FIFO size\r\n",
|
||||||
|
];
|
||||||
|
|
||||||
|
// main is itself an async function.
|
||||||
|
#[embassy_executor::main]
|
||||||
|
async fn main(_spawner: Spawner) {
|
||||||
|
rtt_init_print!();
|
||||||
|
rprintln!("-- VA108xx Async UART TX Demo --");
|
||||||
|
|
||||||
|
let mut dp = pac::Peripherals::take().unwrap();
|
||||||
|
|
||||||
|
// Initialize the systick interrupt & obtain the token to prove that we did
|
||||||
|
// Use the external clock connected to XTAL_N.
|
||||||
|
let clocks = dp
|
||||||
|
.clkgen
|
||||||
|
.constrain()
|
||||||
|
.xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
|
||||||
|
.freeze(&mut dp.sysconfig)
|
||||||
|
.unwrap();
|
||||||
|
// Safety: Only called once here.
|
||||||
|
unsafe {
|
||||||
|
va416xx_embassy::init(
|
||||||
|
&mut dp.sysconfig,
|
||||||
|
&dp.irq_router,
|
||||||
|
dp.tim15,
|
||||||
|
dp.tim14,
|
||||||
|
&clocks,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let portg = PinsG::new(&mut dp.sysconfig, dp.portg);
|
||||||
|
let mut led = portg.pg5.into_readable_push_pull_output();
|
||||||
|
|
||||||
|
let tx = portg.pg0.into_funsel_1();
|
||||||
|
let rx = portg.pg1.into_funsel_1();
|
||||||
|
|
||||||
|
let uarta = uart::Uart::new(&mut dp.sysconfig, dp.uart0, (tx, rx), 115200.Hz(), &clocks);
|
||||||
|
let (tx, _rx) = uarta.split();
|
||||||
|
let mut async_tx = TxAsync::new(tx);
|
||||||
|
let mut ticker = Ticker::every(Duration::from_secs(1));
|
||||||
|
let mut idx = 0;
|
||||||
|
loop {
|
||||||
|
rprintln!("Current time: {}", Instant::now().as_secs());
|
||||||
|
led.toggle();
|
||||||
|
let _written = async_tx
|
||||||
|
.write(STR_LIST[idx].as_bytes())
|
||||||
|
.await
|
||||||
|
.expect("writing failed");
|
||||||
|
idx += 1;
|
||||||
|
if idx == STR_LIST.len() {
|
||||||
|
idx = 0;
|
||||||
|
}
|
||||||
|
ticker.next().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[interrupt]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn UART0_TX() {
|
||||||
|
on_interrupt_tx(Bank::Uart0);
|
||||||
|
}
|
@ -79,10 +79,10 @@ async fn main(spawner: Spawner) {
|
|||||||
let rx = portg.pg1.into_funsel_1();
|
let rx = portg.pg1.into_funsel_1();
|
||||||
|
|
||||||
let uart0 = uart::Uart::new(
|
let uart0 = uart::Uart::new(
|
||||||
|
&mut dp.sysconfig,
|
||||||
dp.uart0,
|
dp.uart0,
|
||||||
(tx, rx),
|
(tx, rx),
|
||||||
Hertz::from_raw(BAUDRATE),
|
Hertz::from_raw(BAUDRATE),
|
||||||
&mut dp.sysconfig,
|
|
||||||
&clocks,
|
&clocks,
|
||||||
);
|
);
|
||||||
let (mut tx, rx) = uart0.split();
|
let (mut tx, rx) = uart0.split();
|
||||||
|
@ -171,10 +171,10 @@ mod app {
|
|||||||
let rx = gpiog.pg1.into_funsel_1();
|
let rx = gpiog.pg1.into_funsel_1();
|
||||||
|
|
||||||
let uart0 = Uart::new(
|
let uart0 = Uart::new(
|
||||||
|
&mut cx.device.sysconfig,
|
||||||
cx.device.uart0,
|
cx.device.uart0,
|
||||||
(tx, rx),
|
(tx, rx),
|
||||||
Hertz::from_raw(UART_BAUDRATE),
|
Hertz::from_raw(UART_BAUDRATE),
|
||||||
&mut cx.device.sysconfig,
|
|
||||||
&clocks,
|
&clocks,
|
||||||
);
|
);
|
||||||
let (tx, rx) = uart0.split();
|
let (tx, rx) = uart0.split();
|
||||||
|
@ -19,12 +19,14 @@ embedded-hal-nb = "1"
|
|||||||
embedded-hal-async = "1"
|
embedded-hal-async = "1"
|
||||||
embedded-hal = "1"
|
embedded-hal = "1"
|
||||||
embedded-io = "0.6"
|
embedded-io = "0.6"
|
||||||
|
embedded-io-async = "0.6"
|
||||||
num_enum = { version = "0.7", default-features = false }
|
num_enum = { version = "0.7", default-features = false }
|
||||||
typenum = "1"
|
typenum = "1"
|
||||||
bitflags = "2"
|
bitflags = "2"
|
||||||
bitfield = "0.17"
|
bitfield = "0.17"
|
||||||
fugit = "0.3"
|
fugit = "0.3"
|
||||||
delegate = "0.12"
|
delegate = "0.12"
|
||||||
|
heapless = "0.8"
|
||||||
void = { version = "1", default-features = false }
|
void = { version = "1", default-features = false }
|
||||||
thiserror = { version = "2", default-features = false }
|
thiserror = { version = "2", default-features = false }
|
||||||
portable-atomic = "1"
|
portable-atomic = "1"
|
||||||
|
@ -30,6 +30,14 @@ use crate::{
|
|||||||
#[cfg(not(feature = "va41628"))]
|
#[cfg(not(feature = "va41628"))]
|
||||||
use crate::gpio::{PC15, PF8};
|
use crate::gpio::{PC15, PF8};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum Bank {
|
||||||
|
Uart0 = 0,
|
||||||
|
Uart1 = 1,
|
||||||
|
Uart2 = 2,
|
||||||
|
}
|
||||||
|
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
// Type-Level support
|
// Type-Level support
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
@ -391,6 +399,7 @@ pub struct BufferTooShortError {
|
|||||||
pub trait Instance: Deref<Target = uart_base::RegisterBlock> {
|
pub trait Instance: Deref<Target = uart_base::RegisterBlock> {
|
||||||
const IDX: u8;
|
const IDX: u8;
|
||||||
const PERIPH_SEL: PeripheralSelect;
|
const PERIPH_SEL: PeripheralSelect;
|
||||||
|
const PTR: *const uart_base::RegisterBlock;
|
||||||
const IRQ_RX: pac::Interrupt;
|
const IRQ_RX: pac::Interrupt;
|
||||||
const IRQ_TX: pac::Interrupt;
|
const IRQ_TX: pac::Interrupt;
|
||||||
|
|
||||||
@ -400,7 +409,21 @@ pub trait Instance: Deref<Target = uart_base::RegisterBlock> {
|
|||||||
///
|
///
|
||||||
/// This circumvents the safety guarantees of the HAL.
|
/// This circumvents the safety guarantees of the HAL.
|
||||||
unsafe fn steal() -> Self;
|
unsafe fn steal() -> Self;
|
||||||
fn ptr() -> *const uart_base::RegisterBlock;
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn ptr() -> *const uart_base::RegisterBlock {
|
||||||
|
Self::PTR
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve the type erased peripheral register block.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// This circumvents the safety guarantees of the HAL.
|
||||||
|
#[inline(always)]
|
||||||
|
unsafe fn reg_block() -> &'static uart_base::RegisterBlock {
|
||||||
|
unsafe { &(*Self::ptr()) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Instance for Uart0 {
|
impl Instance for Uart0 {
|
||||||
@ -408,6 +431,7 @@ impl Instance for Uart0 {
|
|||||||
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Uart0;
|
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Uart0;
|
||||||
const IRQ_RX: pac::Interrupt = pac::Interrupt::UART0_RX;
|
const IRQ_RX: pac::Interrupt = pac::Interrupt::UART0_RX;
|
||||||
const IRQ_TX: pac::Interrupt = pac::Interrupt::UART0_TX;
|
const IRQ_TX: pac::Interrupt = pac::Interrupt::UART0_TX;
|
||||||
|
const PTR: *const uart_base::RegisterBlock = Self::PTR;
|
||||||
|
|
||||||
unsafe fn steal() -> Self {
|
unsafe fn steal() -> Self {
|
||||||
Self::steal()
|
Self::steal()
|
||||||
@ -422,6 +446,7 @@ impl Instance for Uart1 {
|
|||||||
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Uart1;
|
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Uart1;
|
||||||
const IRQ_RX: pac::Interrupt = pac::Interrupt::UART1_RX;
|
const IRQ_RX: pac::Interrupt = pac::Interrupt::UART1_RX;
|
||||||
const IRQ_TX: pac::Interrupt = pac::Interrupt::UART1_TX;
|
const IRQ_TX: pac::Interrupt = pac::Interrupt::UART1_TX;
|
||||||
|
const PTR: *const uart_base::RegisterBlock = Self::PTR;
|
||||||
|
|
||||||
unsafe fn steal() -> Self {
|
unsafe fn steal() -> Self {
|
||||||
Self::steal()
|
Self::steal()
|
||||||
@ -436,6 +461,7 @@ impl Instance for Uart2 {
|
|||||||
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Uart2;
|
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Uart2;
|
||||||
const IRQ_RX: pac::Interrupt = pac::Interrupt::UART2_RX;
|
const IRQ_RX: pac::Interrupt = pac::Interrupt::UART2_RX;
|
||||||
const IRQ_TX: pac::Interrupt = pac::Interrupt::UART2_TX;
|
const IRQ_TX: pac::Interrupt = pac::Interrupt::UART2_TX;
|
||||||
|
const PTR: *const uart_base::RegisterBlock = Self::PTR;
|
||||||
|
|
||||||
unsafe fn steal() -> Self {
|
unsafe fn steal() -> Self {
|
||||||
Self::steal()
|
Self::steal()
|
||||||
@ -445,6 +471,21 @@ impl Instance for Uart2 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Bank {
|
||||||
|
/// Retrieve the peripheral register block.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// Circumvents the HAL safety guarantees.
|
||||||
|
pub unsafe fn reg_block(&self) -> &'static uart_base::RegisterBlock {
|
||||||
|
match self {
|
||||||
|
Bank::Uart0 => unsafe { pac::Uart0::reg_block() },
|
||||||
|
Bank::Uart1 => unsafe { pac::Uart1::reg_block() },
|
||||||
|
Bank::Uart2 => unsafe { pac::Uart2::reg_block() },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
// UART implementation
|
// UART implementation
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
@ -633,10 +674,10 @@ impl<TxPinInst: TxPin<UartInstance>, RxPinInst: RxPin<UartInstance>, UartInstanc
|
|||||||
Uart<UartInstance, (TxPinInst, RxPinInst)>
|
Uart<UartInstance, (TxPinInst, RxPinInst)>
|
||||||
{
|
{
|
||||||
pub fn new(
|
pub fn new(
|
||||||
|
syscfg: &mut va416xx::Sysconfig,
|
||||||
uart: UartInstance,
|
uart: UartInstance,
|
||||||
pins: (TxPinInst, RxPinInst),
|
pins: (TxPinInst, RxPinInst),
|
||||||
config: impl Into<Config>,
|
config: impl Into<Config>,
|
||||||
syscfg: &mut va416xx::Sysconfig,
|
|
||||||
clocks: &Clocks,
|
clocks: &Clocks,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
crate::clock::enable_peripheral_clock(syscfg, UartInstance::PERIPH_SEL);
|
crate::clock::enable_peripheral_clock(syscfg, UartInstance::PERIPH_SEL);
|
||||||
@ -654,10 +695,10 @@ impl<TxPinInst: TxPin<UartInstance>, RxPinInst: RxPin<UartInstance>, UartInstanc
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_with_clock_freq(
|
pub fn new_with_clock_freq(
|
||||||
|
syscfg: &mut va416xx::Sysconfig,
|
||||||
uart: UartInstance,
|
uart: UartInstance,
|
||||||
pins: (TxPinInst, RxPinInst),
|
pins: (TxPinInst, RxPinInst),
|
||||||
config: impl Into<Config>,
|
config: impl Into<Config>,
|
||||||
syscfg: &mut va416xx::Sysconfig,
|
|
||||||
clock: impl Into<Hertz>,
|
clock: impl Into<Hertz>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
crate::clock::enable_peripheral_clock(syscfg, UartInstance::PERIPH_SEL);
|
crate::clock::enable_peripheral_clock(syscfg, UartInstance::PERIPH_SEL);
|
||||||
@ -724,6 +765,34 @@ impl<TxPinInst: TxPin<UartInstance>, RxPinInst: RxPin<UartInstance>, UartInstanc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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.
|
||||||
@ -758,6 +827,15 @@ impl<Uart: Instance> Rx<Uart> {
|
|||||||
self.0.enable().modify(|_, w| w.rxenable().clear_bit());
|
self.0.enable().modify(|_, w| w.rxenable().clear_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() });
|
||||||
|
}
|
||||||
|
|
||||||
/// Low level function to read a word from the UART FIFO.
|
/// Low level function to read a word from 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.
|
||||||
@ -847,12 +925,51 @@ impl<Uart: Instance> embedded_io::Read for Rx<Uart> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn enable_tx(uart: &uart_base::RegisterBlock) {
|
||||||
|
uart.enable().modify(|_, w| w.txenable().set_bit());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn disable_tx(uart: &uart_base::RegisterBlock) {
|
||||||
|
uart.enable().modify(|_, w| w.txenable().clear_bit());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn enable_tx_interrupts(uart: &uart_base::RegisterBlock) {
|
||||||
|
uart.irq_enb().modify(|_, w| {
|
||||||
|
w.irq_tx().set_bit();
|
||||||
|
w.irq_tx_status().set_bit();
|
||||||
|
w.irq_tx_empty().set_bit()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn disable_tx_interrupts(uart: &uart_base::RegisterBlock) {
|
||||||
|
uart.irq_enb().modify(|_, w| {
|
||||||
|
w.irq_tx().clear_bit();
|
||||||
|
w.irq_tx_status().clear_bit();
|
||||||
|
w.irq_tx_empty().clear_bit()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// Serial transmitter
|
/// Serial transmitter
|
||||||
///
|
///
|
||||||
/// Can be created by using the [Uart::split] or [UartBase::split] API.
|
/// Can be created by using the [Uart::split] or [UartBase::split] API.
|
||||||
pub struct Tx<Uart>(Uart);
|
pub struct Tx<Uart>(Uart);
|
||||||
|
|
||||||
impl<Uart: Instance> Tx<Uart> {
|
impl<Uart: Instance> Tx<Uart> {
|
||||||
|
/// Retrieve a TX pin without expecting an explicit UART structure
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// Circumvents the HAL safety guarantees.
|
||||||
|
#[inline(always)]
|
||||||
|
pub unsafe fn steal() -> Self {
|
||||||
|
Self(Uart::steal())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
fn new(uart: Uart) -> Self {
|
fn new(uart: Uart) -> Self {
|
||||||
Self(uart)
|
Self(uart)
|
||||||
}
|
}
|
||||||
@ -862,7 +979,8 @@ 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.
|
||||||
pub unsafe fn uart(&self) -> &Uart {
|
#[inline(always)]
|
||||||
|
pub const unsafe fn uart(&self) -> &Uart {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -881,6 +999,27 @@ 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) {
|
||||||
|
// Safety: We own the UART structure
|
||||||
|
enable_tx_interrupts(unsafe { Uart::reg_block() });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disables the IRQ_TX, IRQ_TX_STATUS and IRQ_TX_EMPTY interrupts.
|
||||||
|
///
|
||||||
|
/// [Self::enable_interrupts] documents the interrupts.
|
||||||
|
#[inline]
|
||||||
|
pub fn disable_interrupts(&self) {
|
||||||
|
// Safety: We own the UART structure
|
||||||
|
disable_tx_interrupts(unsafe { Uart::reg_block() });
|
||||||
|
}
|
||||||
|
|
||||||
/// 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.
|
||||||
@ -906,6 +1045,11 @@ impl<Uart: Instance> Tx<Uart> {
|
|||||||
pub fn write_fifo_unchecked(&self, data: u32) {
|
pub fn write_fifo_unchecked(&self, data: u32) {
|
||||||
self.0.data().write(|w| unsafe { w.bits(data) });
|
self.0.data().write(|w| unsafe { w.bits(data) });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn into_async(self) -> TxAsync<Uart> {
|
||||||
|
TxAsync::new(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Uart> embedded_io::ErrorType for Tx<Uart> {
|
impl<Uart> embedded_io::ErrorType for Tx<Uart> {
|
||||||
@ -1233,3 +1377,9 @@ impl<Uart: Instance> RxWithInterrupt<Uart> {
|
|||||||
self.0.release()
|
self.0.release()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod tx_asynch;
|
||||||
|
pub use tx_asynch::*;
|
||||||
|
|
||||||
|
pub mod rx_asynch;
|
||||||
|
pub use rx_asynch::*;
|
440
va416xx-hal/src/uart/rx_asynch.rs
Normal file
440
va416xx-hal/src/uart/rx_asynch.rs
Normal file
@ -0,0 +1,440 @@
|
|||||||
|
//! # Async UART reception functionality for the VA416xx family.
|
||||||
|
//!
|
||||||
|
//! This module provides the [RxAsync] and [RxAsyncOverwriting] 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 two interrupt handlers:
|
||||||
|
//!
|
||||||
|
//! - [on_interrupt_rx]
|
||||||
|
//! - [on_interrupt_rx_overwriting]
|
||||||
|
//!
|
||||||
|
//! The first two are used for the [RxAsync] struct, while the latter two are used with the
|
||||||
|
//! [RxAsyncOverwriting] 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 portable_atomic::AtomicBool;
|
||||||
|
use va416xx::uart0 as uart_base;
|
||||||
|
|
||||||
|
use super::{Bank, Instance, Rx, RxError, UartErrors};
|
||||||
|
|
||||||
|
static UART_RX_WAKERS: [AtomicWaker; 3] = [const { AtomicWaker::new() }; 3];
|
||||||
|
static RX_READ_ACTIVE: [AtomicBool; 3] = [const { AtomicBool::new(false) }; 3];
|
||||||
|
static RX_HAS_DATA: [AtomicBool; 3] = [const { AtomicBool::new(false) }; 3];
|
||||||
|
|
||||||
|
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: &'static uart_base::RegisterBlock) -> 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(
|
||||||
|
bank: Bank,
|
||||||
|
rx_enabled: bool,
|
||||||
|
read_some_data: bool,
|
||||||
|
irq_end: u32,
|
||||||
|
) -> Option<UartErrors> {
|
||||||
|
let idx = bank as usize;
|
||||||
|
if read_some_data {
|
||||||
|
RX_HAS_DATA[idx].store(true, Ordering::Relaxed);
|
||||||
|
if RX_READ_ACTIVE[idx].load(Ordering::Relaxed) {
|
||||||
|
UART_RX_WAKERS[idx].wake();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut errors = None;
|
||||||
|
let uart_regs = unsafe { bank.reg_block() };
|
||||||
|
// Check for RX errors
|
||||||
|
if rx_enabled {
|
||||||
|
errors = on_interrupt_handle_rx_errors(uart_regs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the interrupt status bits
|
||||||
|
uart_regs.irq_clr().write(|w| unsafe { w.bits(irq_end) });
|
||||||
|
errors
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interrupt handler with overwriting behaviour when the ring buffer is full.
|
||||||
|
///
|
||||||
|
/// 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_rx_overwriting<const N: usize>(
|
||||||
|
bank: Bank,
|
||||||
|
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(bank, prod, shared_consumer)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_interrupt_rx_async_heapless_queue_overwriting<const N: usize>(
|
||||||
|
bank: Bank,
|
||||||
|
prod: &mut heapless::spsc::Producer<u8, N>,
|
||||||
|
shared_consumer: &Mutex<RefCell<Option<heapless::spsc::Consumer<'static, u8, N>>>>,
|
||||||
|
) -> Result<(), AsyncUartErrors> {
|
||||||
|
let uart_regs = unsafe { bank.reg_block() };
|
||||||
|
let irq_end = uart_regs.irq_end().read();
|
||||||
|
let enb_status = uart_regs.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_regs.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_regs.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_regs.rxstatus().read().rdavl().bit_is_set() {
|
||||||
|
// While there is data in the FIFO, write it into the reception buffer
|
||||||
|
let byte = uart_regs.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(bank, 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 asynchronous RX operations.
|
||||||
|
///
|
||||||
|
/// Should be called in the user interrupt handler to enable asynchronous reception.
|
||||||
|
pub fn on_interrupt_rx<const N: usize>(
|
||||||
|
bank: Bank,
|
||||||
|
prod: &mut heapless::spsc::Producer<'_, u8, N>,
|
||||||
|
) -> Result<(), AsyncUartErrors> {
|
||||||
|
on_interrupt_rx_async_heapless_queue(bank, prod)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_interrupt_rx_async_heapless_queue<const N: usize>(
|
||||||
|
bank: Bank,
|
||||||
|
prod: &mut heapless::spsc::Producer<'_, u8, N>,
|
||||||
|
) -> Result<(), AsyncUartErrors> {
|
||||||
|
let uart = unsafe { bank.reg_block() };
|
||||||
|
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(bank, 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RxAsyncInner<Uart: Instance, const N: usize> {
|
||||||
|
rx: Rx<Uart>,
|
||||||
|
pub queue: heapless::spsc::Consumer<'static, u8, N>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Core data structure to allow asynchronous UART reception.
|
||||||
|
///
|
||||||
|
/// If the ring buffer becomes full, data will be lost.
|
||||||
|
pub struct RxAsync<Uart: Instance, const N: usize>(Option<RxAsyncInner<Uart, N>>);
|
||||||
|
|
||||||
|
impl<Uart: Instance, const N: usize> ErrorType for RxAsync<Uart, N> {
|
||||||
|
/// Error reporting is done using the result of the interrupt functions.
|
||||||
|
type Error = Infallible;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stop_async_rx<Uart: Instance>(rx: &mut Rx<Uart>) {
|
||||||
|
rx.disable_interrupts();
|
||||||
|
rx.disable();
|
||||||
|
rx.clear_fifo();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 [on_interrupt_rx].
|
||||||
|
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(Some(RxAsyncInner { rx, queue }))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stop(&mut self) {
|
||||||
|
stop_async_rx(&mut self.0.as_mut().unwrap().rx);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn release(mut self) -> (Rx<Uart>, heapless::spsc::Consumer<'static, u8, N>) {
|
||||||
|
self.stop();
|
||||||
|
let inner = self.0.take().unwrap();
|
||||||
|
(inner.rx, inner.queue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Uart: Instance, const N: usize> Drop for RxAsync<Uart, N> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.0.as_ref().unwrap().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 mut_ref = self.0.as_mut().unwrap();
|
||||||
|
let fut = RxFuture::new(&mut mut_ref.rx);
|
||||||
|
// Data is available, so read that data immediately.
|
||||||
|
let read_data = handle_data_in_queue(&mut mut_ref.queue);
|
||||||
|
if read_data > 0 {
|
||||||
|
return Ok(read_data);
|
||||||
|
}
|
||||||
|
// Await data.
|
||||||
|
let _ = fut.await;
|
||||||
|
Ok(handle_data_in_queue(&mut mut_ref.queue))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RxAsyncOverwritingInner<Uart: Instance, const N: usize> {
|
||||||
|
rx: Rx<Uart>,
|
||||||
|
pub shared_consumer: &'static Mutex<RefCell<Option<heapless::spsc::Consumer<'static, u8, N>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Core data structure to allow asynchronous UART reception.
|
||||||
|
///
|
||||||
|
/// If the ring buffer becomes full, the oldest data will be overwritten when using the
|
||||||
|
/// [on_interrupt_rx_overwriting] interrupt handlers.
|
||||||
|
pub struct RxAsyncOverwriting<Uart: Instance, const N: usize>(
|
||||||
|
Option<RxAsyncOverwritingInner<Uart, N>>,
|
||||||
|
);
|
||||||
|
|
||||||
|
impl<Uart: Instance, const N: usize> ErrorType for RxAsyncOverwriting<Uart, N> {
|
||||||
|
/// Error reporting is done using the result of the interrupt functions.
|
||||||
|
type Error = Infallible;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Uart: Instance, const N: usize> RxAsyncOverwriting<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>,
|
||||||
|
shared_consumer: &'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(Some(RxAsyncOverwritingInner {
|
||||||
|
rx,
|
||||||
|
shared_consumer,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stop(&mut self) {
|
||||||
|
stop_async_rx(&mut self.0.as_mut().unwrap().rx);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn release(mut self) -> Rx<Uart> {
|
||||||
|
self.stop();
|
||||||
|
let inner = self.0.take().unwrap();
|
||||||
|
inner.rx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Uart: Instance, const N: usize> Drop for RxAsyncOverwriting<Uart, N> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Uart: Instance, const N: usize> embedded_io_async::Read for RxAsyncOverwriting<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.0.as_ref().unwrap().shared_consumer.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 = |inner: &mut RxAsyncOverwritingInner<Uart, N>| {
|
||||||
|
critical_section::with(|cs| {
|
||||||
|
let mut consumer_ref = inner.shared_consumer.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.0.as_mut().unwrap().rx);
|
||||||
|
// Data is available, so read that data immediately.
|
||||||
|
let read_data = handle_data_in_queue(self.0.as_mut().unwrap());
|
||||||
|
if read_data > 0 {
|
||||||
|
return Ok(read_data);
|
||||||
|
}
|
||||||
|
// Await data.
|
||||||
|
let _ = fut.await;
|
||||||
|
let read_data = handle_data_in_queue(self.0.as_mut().unwrap());
|
||||||
|
Ok(read_data)
|
||||||
|
}
|
||||||
|
}
|
255
va416xx-hal/src/uart/tx_asynch.rs
Normal file
255
va416xx-hal/src/uart/tx_asynch.rs
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
//! # Async UART transmission functionality for the VA416xx family.
|
||||||
|
//!
|
||||||
|
//! 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
|
||||||
|
//! not specify/declare the interrupt handlers which must be provided for async support to work.
|
||||||
|
//! However, it the [on_interrupt_tx] interrupt handler.
|
||||||
|
//!
|
||||||
|
//! This handler should be called in ALL user interrupt handlers which handle UART TX interrupts
|
||||||
|
//! for a given UART bank.
|
||||||
|
//!
|
||||||
|
//! # Example
|
||||||
|
//!
|
||||||
|
//! - [Async UART TX example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/embassy/src/bin/async-uart-tx.rs)
|
||||||
|
use core::{cell::RefCell, future::Future};
|
||||||
|
|
||||||
|
use critical_section::Mutex;
|
||||||
|
use embassy_sync::waitqueue::AtomicWaker;
|
||||||
|
use embedded_io_async::Write;
|
||||||
|
use portable_atomic::AtomicBool;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
static UART_TX_WAKERS: [AtomicWaker; 3] = [const { AtomicWaker::new() }; 3];
|
||||||
|
static TX_CONTEXTS: [Mutex<RefCell<TxContext>>; 3] =
|
||||||
|
[const { Mutex::new(RefCell::new(TxContext::new())) }; 3];
|
||||||
|
// Completion flag. Kept outside of the context structure as an atomic to avoid
|
||||||
|
// critical section.
|
||||||
|
static TX_DONE: [AtomicBool; 3] = [const { AtomicBool::new(false) }; 3];
|
||||||
|
|
||||||
|
/// This is a generic interrupt handler to handle asynchronous UART TX operations for a given
|
||||||
|
/// UART bank.
|
||||||
|
///
|
||||||
|
/// The user has to call this once in the interrupt handler responsible for the TX interrupts on
|
||||||
|
/// the given UART bank.
|
||||||
|
pub fn on_interrupt_tx(bank: Bank) {
|
||||||
|
let uart = unsafe { bank.reg_block() };
|
||||||
|
let idx = bank as usize;
|
||||||
|
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_CONTEXTS[idx].borrow(cs);
|
||||||
|
*context_ref.borrow()
|
||||||
|
});
|
||||||
|
context.tx_overrun = unexpected_overrun;
|
||||||
|
if context.progress >= context.slice.len && !tx_status.wrbusy().bit_is_set() {
|
||||||
|
uart.irq_enb().modify(|_, w| {
|
||||||
|
w.irq_tx().clear_bit();
|
||||||
|
w.irq_tx_empty().clear_bit();
|
||||||
|
w.irq_tx_status().clear_bit()
|
||||||
|
});
|
||||||
|
uart.enable().modify(|_, w| w.txenable().clear_bit());
|
||||||
|
// Write back updated context structure.
|
||||||
|
critical_section::with(|cs| {
|
||||||
|
let context_ref = TX_CONTEXTS[idx].borrow(cs);
|
||||||
|
*context_ref.borrow_mut() = context;
|
||||||
|
});
|
||||||
|
// Transfer is done.
|
||||||
|
TX_DONE[idx].store(true, core::sync::atomic::Ordering::Relaxed);
|
||||||
|
UART_TX_WAKERS[idx].wake();
|
||||||
|
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();
|
||||||
|
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_CONTEXTS[idx].borrow(cs);
|
||||||
|
*context_ref.borrow_mut() = context;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub struct TxContext {
|
||||||
|
progress: usize,
|
||||||
|
tx_overrun: bool,
|
||||||
|
slice: RawBufSlice,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::new_without_default)]
|
||||||
|
impl TxContext {
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
progress: 0,
|
||||||
|
tx_overrun: false,
|
||||||
|
slice: RawBufSlice::new_empty(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
struct RawBufSlice {
|
||||||
|
data: *const u8,
|
||||||
|
len: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Safety: This type MUST be used with mutex to ensure concurrent access is valid.
|
||||||
|
unsafe impl Send for RawBufSlice {}
|
||||||
|
|
||||||
|
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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn new_empty() -> Self {
|
||||||
|
Self {
|
||||||
|
data: core::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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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_DONE[Uart::IDX as usize].store(false, core::sync::atomic::Ordering::Relaxed);
|
||||||
|
tx.disable_interrupts();
|
||||||
|
tx.disable();
|
||||||
|
tx.clear_fifo();
|
||||||
|
|
||||||
|
let uart_tx = unsafe { tx.uart() };
|
||||||
|
let init_fill_count = core::cmp::min(data.len(), 16);
|
||||||
|
// We fill the FIFO.
|
||||||
|
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_CONTEXTS[Uart::IDX as usize].borrow(cs);
|
||||||
|
let mut context = context_ref.borrow_mut();
|
||||||
|
context.slice.set(data);
|
||||||
|
context.progress = init_fill_count;
|
||||||
|
|
||||||
|
// Ensure those are enabled inside a critical section at the same time. Can lead to
|
||||||
|
// weird glitches otherwise.
|
||||||
|
tx.enable_interrupts();
|
||||||
|
tx.enable();
|
||||||
|
});
|
||||||
|
Self {
|
||||||
|
uart_idx: Uart::IDX as usize,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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_TX_WAKERS[self.uart_idx].register(cx.waker());
|
||||||
|
if TX_DONE[self.uart_idx].swap(false, core::sync::atomic::Ordering::Relaxed) {
|
||||||
|
let progress = critical_section::with(|cs| {
|
||||||
|
TX_CONTEXTS[self.uart_idx].borrow(cs).borrow().progress
|
||||||
|
});
|
||||||
|
return core::task::Poll::Ready(Ok(progress));
|
||||||
|
}
|
||||||
|
core::task::Poll::Pending
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for TxFuture {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let reg_block = match self.uart_idx {
|
||||||
|
0 => unsafe { pac::Uart0::reg_block() },
|
||||||
|
1 => unsafe { pac::Uart1::reg_block() },
|
||||||
|
2 => unsafe { pac::Uart2::reg_block() },
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
disable_tx_interrupts(reg_block);
|
||||||
|
disable_tx(reg_block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TxAsync<Uart: Instance> {
|
||||||
|
tx: Tx<Uart>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Uart: Instance> TxAsync<Uart> {
|
||||||
|
pub fn new(tx: Tx<Uart>) -> Self {
|
||||||
|
Self { tx }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn release(self) -> Tx<Uart> {
|
||||||
|
self.tx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
#[error("TX overrun error")]
|
||||||
|
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> embedded_io::ErrorType for TxAsync<Uart> {
|
||||||
|
type Error = TxOverrunError;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Uart: Instance> Write for TxAsync<Uart> {
|
||||||
|
/// Write a buffer asynchronously.
|
||||||
|
///
|
||||||
|
/// This implementation is not side effect free, and a started future might have already
|
||||||
|
/// written part of the passed buffer.
|
||||||
|
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
|
||||||
|
let fut = unsafe { TxFuture::new(&mut self.tx, buf) };
|
||||||
|
fut.await
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user