init commit

This commit is contained in:
2025-04-22 13:45:36 +02:00
commit ba8d817a73
36 changed files with 10348 additions and 0 deletions
+1306
View File
File diff suppressed because it is too large Load Diff
+112
View File
@@ -0,0 +1,112 @@
// UART A pins
use crate::{
FunSel,
pins::{
Pa2, Pa3, Pa8, Pa9, Pa16, Pa17, Pa18, Pa19, Pa26, Pa27, Pa30, Pa31, Pb6, Pb7, Pb8, Pb9,
Pb18, Pb19, Pb20, Pb21, Pb22, Pb23, Pin,
},
};
use super::{Bank, RxPin, TxPin};
impl TxPin for Pin<Pa9> {
const BANK: Bank = Bank::Uart0;
const FUN_SEL: FunSel = FunSel::Sel2;
}
impl RxPin for Pin<Pa8> {
const BANK: Bank = Bank::Uart0;
const FUN_SEL: FunSel = FunSel::Sel2;
}
impl TxPin for Pin<Pa17> {
const BANK: Bank = Bank::Uart0;
const FUN_SEL: FunSel = FunSel::Sel3;
}
impl RxPin for Pin<Pa16> {
const BANK: Bank = Bank::Uart0;
const FUN_SEL: FunSel = FunSel::Sel3;
}
impl TxPin for Pin<Pa31> {
const BANK: Bank = Bank::Uart0;
const FUN_SEL: FunSel = FunSel::Sel3;
}
impl RxPin for Pin<Pa30> {
const BANK: Bank = Bank::Uart0;
const FUN_SEL: FunSel = FunSel::Sel3;
}
impl TxPin for Pin<Pb9> {
const BANK: Bank = Bank::Uart0;
const FUN_SEL: FunSel = FunSel::Sel1;
}
impl RxPin for Pin<Pb8> {
const BANK: Bank = Bank::Uart0;
const FUN_SEL: FunSel = FunSel::Sel1;
}
impl TxPin for Pin<Pb23> {
const BANK: Bank = Bank::Uart0;
const FUN_SEL: FunSel = FunSel::Sel1;
}
impl RxPin for Pin<Pb22> {
const BANK: Bank = Bank::Uart0;
const FUN_SEL: FunSel = FunSel::Sel1;
}
// UART B pins
impl TxPin for Pin<Pa3> {
const BANK: Bank = Bank::Uart1;
const FUN_SEL: FunSel = FunSel::Sel2;
}
impl RxPin for Pin<Pa2> {
const BANK: Bank = Bank::Uart1;
const FUN_SEL: FunSel = FunSel::Sel2;
}
impl TxPin for Pin<Pa19> {
const BANK: Bank = Bank::Uart1;
const FUN_SEL: FunSel = FunSel::Sel3;
}
impl RxPin for Pin<Pa18> {
const BANK: Bank = Bank::Uart1;
const FUN_SEL: FunSel = FunSel::Sel3;
}
impl TxPin for Pin<Pa27> {
const BANK: Bank = Bank::Uart1;
const FUN_SEL: FunSel = FunSel::Sel3;
}
impl RxPin for Pin<Pa26> {
const BANK: Bank = Bank::Uart1;
const FUN_SEL: FunSel = FunSel::Sel3;
}
impl TxPin for Pin<Pb7> {
const BANK: Bank = Bank::Uart1;
const FUN_SEL: FunSel = FunSel::Sel1;
}
impl RxPin for Pin<Pb6> {
const BANK: Bank = Bank::Uart1;
const FUN_SEL: FunSel = FunSel::Sel1;
}
impl TxPin for Pin<Pb19> {
const BANK: Bank = Bank::Uart1;
const FUN_SEL: FunSel = FunSel::Sel2;
}
impl RxPin for Pin<Pb18> {
const BANK: Bank = Bank::Uart1;
const FUN_SEL: FunSel = FunSel::Sel2;
}
impl TxPin for Pin<Pb21> {
const BANK: Bank = Bank::Uart1;
const FUN_SEL: FunSel = FunSel::Sel1;
}
impl RxPin for Pin<Pb20> {
const BANK: Bank = Bank::Uart1;
const FUN_SEL: FunSel = FunSel::Sel1;
}
+98
View File
@@ -0,0 +1,98 @@
#[cfg(not(feature = "va41628"))]
use crate::pins::{Pc15, Pf8};
use crate::{
FunSel,
gpio::Pin,
pins::{Pa2, Pa3, Pb14, Pb15, Pc4, Pc5, Pc14, Pd11, Pd12, Pe2, Pe3, Pf9, Pf12, Pf13, Pg0, Pg1},
};
use super::{Bank, RxPin, TxPin};
// UART 0 pins
impl TxPin for Pin<Pa2> {
const BANK: Bank = Bank::Uart0;
const FUN_SEL: FunSel = FunSel::Sel3;
}
impl RxPin for Pin<Pa3> {
const BANK: Bank = Bank::Uart0;
const FUN_SEL: FunSel = FunSel::Sel3;
}
impl TxPin for Pin<Pc4> {
const BANK: Bank = Bank::Uart0;
const FUN_SEL: FunSel = FunSel::Sel2;
}
impl RxPin for Pin<Pc5> {
const BANK: Bank = Bank::Uart0;
const FUN_SEL: FunSel = FunSel::Sel2;
}
impl TxPin for Pin<Pe2> {
const BANK: Bank = Bank::Uart0;
const FUN_SEL: FunSel = FunSel::Sel3;
}
impl RxPin for Pin<Pe3> {
const BANK: Bank = Bank::Uart0;
const FUN_SEL: FunSel = FunSel::Sel3;
}
impl TxPin for Pin<Pg0> {
const BANK: Bank = Bank::Uart0;
const FUN_SEL: FunSel = FunSel::Sel1;
}
impl RxPin for Pin<Pg1> {
const BANK: Bank = Bank::Uart0;
const FUN_SEL: FunSel = FunSel::Sel1;
}
// UART 1 pins
impl TxPin for Pin<Pb14> {
const BANK: Bank = Bank::Uart1;
const FUN_SEL: FunSel = FunSel::Sel3;
}
impl RxPin for Pin<Pb15> {
const BANK: Bank = Bank::Uart1;
const FUN_SEL: FunSel = FunSel::Sel3;
}
impl TxPin for Pin<Pd11> {
const BANK: Bank = Bank::Uart1;
const FUN_SEL: FunSel = FunSel::Sel3;
}
impl RxPin for Pin<Pd12> {
const BANK: Bank = Bank::Uart1;
const FUN_SEL: FunSel = FunSel::Sel3;
}
impl TxPin for Pin<Pf12> {
const BANK: Bank = Bank::Uart1;
const FUN_SEL: FunSel = FunSel::Sel1;
}
impl RxPin for Pin<Pf13> {
const BANK: Bank = Bank::Uart1;
const FUN_SEL: FunSel = FunSel::Sel1;
}
// UART 2 pins
impl TxPin for Pin<Pc14> {
const BANK: Bank = Bank::Uart2;
const FUN_SEL: FunSel = FunSel::Sel2;
}
#[cfg(not(feature = "va41628"))]
impl RxPin for Pin<Pc15> {
const BANK: Bank = Bank::Uart2;
const FUN_SEL: FunSel = FunSel::Sel2;
}
#[cfg(not(feature = "va41628"))]
impl TxPin for Pin<Pf8> {
const BANK: Bank = Bank::Uart2;
const FUN_SEL: FunSel = FunSel::Sel1;
}
impl RxPin for Pin<Pf9> {
const BANK: Bank = Bank::Uart2;
const FUN_SEL: FunSel = FunSel::Sel1;
}
+322
View File
@@ -0,0 +1,322 @@
use core::marker::PhantomData;
use arbitrary_int::{u5, u6, u18};
cfg_if::cfg_if! {
if #[cfg(feature = "vor1x")] {
/// UART A base address
pub const BASE_ADDR_0: usize = 0x4004_0000;
/// UART B base address
pub const BASE_ADDR_1: usize = 0x4004_1000;
} else if #[cfg(feature = "vor4x")] {
/// UART 0 base address
pub const BASE_ADDR_0: usize = 0x4002_4000;
/// UART 1 base address
pub const BASE_ADDR_1: usize = 0x4002_5000;
/// UART 2 base address
pub const BASE_ADDR_2: usize = 0x4001_7000;
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Bank {
Uart0 = 0,
Uart1 = 1,
#[cfg(feature = "vor4x")]
Uart2 = 2,
}
impl Bank {
/// Unsafely steal the GPIO peripheral block for the given port.
///
/// # Safety
///
/// Circumvents ownership and safety guarantees by the HAL.
pub unsafe fn steal_regs(&self) -> MmioUart<'static> {
Uart::new_mmio(*self)
}
#[cfg(feature = "vor4x")]
pub const fn interrupt_id_tx(&self) -> va416xx::Interrupt {
match self {
Bank::Uart0 => va416xx::Interrupt::UART0_TX,
Bank::Uart1 => va416xx::Interrupt::UART1_TX,
Bank::Uart2 => va416xx::Interrupt::UART2_TX,
}
}
#[cfg(feature = "vor4x")]
pub const fn interrupt_id_rx(&self) -> va416xx::Interrupt {
match self {
Bank::Uart0 => va416xx::Interrupt::UART0_RX,
Bank::Uart1 => va416xx::Interrupt::UART1_RX,
Bank::Uart2 => va416xx::Interrupt::UART2_RX,
}
}
}
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
pub struct Data {
#[bit(15, rw)]
dparity: bool,
#[bits(0..=7, rw)]
value: u8,
}
#[bitbybit::bitfield(u32, default = 0x0)]
#[derive(Debug)]
pub struct Enable {
#[bit(1, rw)]
tx: bool,
#[bit(0, rw)]
rx: bool,
}
#[bitbybit::bitenum(u1, exhaustive = true)]
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Stopbits {
One = 0,
Two = 1,
}
#[bitbybit::bitenum(u2, exhaustive = true)]
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum WordSize {
Five = 0b00,
Six = 0b01,
Seven = 0b10,
Eight = 0b11,
}
#[bitbybit::bitfield(u32, default = 0x0)]
#[derive(Debug)]
pub struct Control {
#[bit(11, rw)]
baud8: bool,
#[bit(10, rw)]
auto_rts: bool,
#[bit(9, rw)]
def_rts: bool,
#[bit(8, rw)]
auto_cts: bool,
#[bit(7, rw)]
loopback_block: bool,
#[bit(6, rw)]
loopback: bool,
#[bits(4..=5, rw)]
wordsize: WordSize,
#[bit(3, rw)]
stopbits: Stopbits,
#[bit(2, rw)]
parity_manual: bool,
#[bit(1, rw)]
parity_even: bool,
#[bit(0, rw)]
parity_enable: bool,
}
#[bitbybit::bitfield(u32, default = 0x0)]
#[derive(Debug)]
pub struct ClkScale {
#[bits(6..=23, rw)]
int: u18,
#[bits(0..=5, rw)]
frac: u6,
}
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
pub struct RxStatus {
#[bit(15, r)]
rx_rtsn: bool,
#[bit(9, r)]
rx_addr9: bool,
#[bit(8, r)]
busy_break: bool,
#[bit(7, r)]
break_error: bool,
#[bit(6, r)]
parity_error: bool,
#[bit(5, r)]
framing_error: bool,
#[bit(4, r)]
overrun_error: bool,
#[bit(3, r)]
timeout: bool,
#[bit(2, r)]
busy: bool,
#[bit(1, r)]
not_full: bool,
#[bit(0, r)]
data_available: bool,
}
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
pub struct TxStatus {
#[bit(15, r)]
tx_ctsn: bool,
#[bit(3, r)]
wr_lost: bool,
#[bit(2, r)]
tx_busy: bool,
#[bit(1, r)]
write_busy: bool,
/// There is space in the FIFO to write data.
#[bit(0, r)]
ready: bool,
}
#[bitbybit::bitfield(u32, default = 0x0)]
#[derive(Debug)]
pub struct FifoClear {
#[bit(1, w)]
tx: bool,
#[bit(0, w)]
rx: bool,
}
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
pub struct InterruptControl {
/// Generate an interrrupt when the RX FIFO is at least half-full (FIFO count >= trigger level)
#[bit(0, rw)]
rx: bool,
/// Interrupts for status conditions (overrun, framing, parity and break)
#[bit(1, rw)]
rx_status: bool,
/// Interrupt on timeout conditions.
#[bit(2, rw)]
rx_timeout: bool,
/// Generates an interrupt when the TX FIFO is at least half-empty (FIFO count < trigger level)
#[bit(4, rw)]
tx: bool,
/// Generates an interrupt on TX FIFO overflow.
#[bit(5, rw)]
tx_status: bool,
/// Generates an interrupt when the transmit FIFO is empty and TXBUSY is 0.
#[bit(6, rw)]
tx_empty: bool,
#[bit(7, rw)]
tx_cts: bool,
}
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
pub struct InterruptStatus {
/// Generate an interrrupt when the RX FIFO is at least half-full (FIFO count >= trigger level)
#[bit(0, r)]
rx: bool,
/// Interrupts for status conditions (overrun, framing, parity and break)
#[bit(1, r)]
rx_status: bool,
/// Interrupt on timeout conditions.
#[bit(2, r)]
rx_timeout: bool,
/// Generates an interrupt when the TX FIFO is at least half-empty (FIFO count < trigger level)
#[bit(4, r)]
tx: bool,
/// Generates an interrupt on TX FIFO overflow.
#[bit(5, r)]
tx_status: bool,
/// Generates an interrupt when the transmit FIFO is empty and TXBUSY is 0.
#[bit(6, r)]
tx_empty: bool,
#[bit(7, r)]
tx_cts: bool,
}
/// As specified in the VA416x0 Programmers Guide, only the RX overflow bit can be cleared.
#[bitbybit::bitfield(u32, default = 0x0)]
#[derive(Debug)]
pub struct InterruptClear {
#[bit(1, w)]
rx_overrun: bool,
/// Not sure if this does anything, the programmer guides are not consistent on this..
#[bit(5, w)]
tx_overrun: bool,
}
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
pub struct FifoTrigger {
#[bits(0..=4, rw)]
level: u5,
}
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
pub struct State {
#[bits(0..=7, r)]
rx_state: u8,
/// Data count.
#[bits(8..=12, r)]
rx_fifo: u5,
#[bits(16..=23, r)]
tx_state: u8,
/// Data count.
#[bits(24..=28, r)]
tx_fifo: u5,
}
#[derive(derive_mmio::Mmio)]
#[mmio(no_ctors)]
#[repr(C)]
pub struct Uart {
data: Data,
enable: Enable,
ctrl: Control,
clkscale: ClkScale,
#[mmio(PureRead)]
rx_status: RxStatus,
#[mmio(PureRead)]
tx_status: TxStatus,
#[mmio(Write)]
fifo_clr: FifoClear,
#[mmio(Write)]
txbreak: u32,
addr9: u32,
addr9mask: u32,
irq_enabled: InterruptControl,
#[mmio(PureRead)]
irq_raw: InterruptStatus,
#[mmio(PureRead)]
irq_status: InterruptStatus,
#[mmio(Write)]
irq_clr: InterruptClear,
rx_fifo_trigger: FifoTrigger,
tx_fifo_trigger: FifoTrigger,
rx_fifo_rts_trigger: u32,
#[mmio(PureRead)]
state: State,
_reserved: [u32; 0x3ED],
/// Vorago 1x value: 0x0112_07E1. Vorago 4x value: 0x0212_07E9
#[mmio(PureRead)]
perid: u32,
}
static_assertions::const_assert_eq!(core::mem::size_of::<Uart>(), 0x1000);
impl Uart {
fn new_mmio_at(base: usize) -> MmioUart<'static> {
MmioUart {
ptr: base as *mut _,
phantom: PhantomData,
}
}
pub fn new_mmio(bank: Bank) -> MmioUart<'static> {
match bank {
Bank::Uart0 => Self::new_mmio_at(BASE_ADDR_0),
Bank::Uart1 => Self::new_mmio_at(BASE_ADDR_1),
#[cfg(feature = "vor4x")]
Bank::Uart2 => Self::new_mmio_at(BASE_ADDR_2),
}
}
}
+443
View File
@@ -0,0 +1,443 @@
//! # Async UART reception functionality.
//!
//! 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.
use core::{cell::RefCell, convert::Infallible, future::Future, sync::atomic::Ordering};
use arbitrary_int::Number;
use critical_section::Mutex;
use embassy_sync::waitqueue::AtomicWaker;
use embedded_io::ErrorType;
use portable_atomic::AtomicBool;
use super::{
Bank, Rx, UartErrors,
regs::{InterruptClear, MmioUart},
};
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 {
id: Bank,
}
impl RxFuture {
pub fn new(rx: &mut Rx) -> Self {
RX_READ_ACTIVE[rx.id as usize].store(true, Ordering::Relaxed);
Self { id: rx.id }
}
}
impl Future for RxFuture {
type Output = Result<(), Infallible>;
fn poll(
self: core::pin::Pin<&mut Self>,
cx: &mut core::task::Context<'_>,
) -> core::task::Poll<Self::Output> {
UART_RX_WAKERS[self.id as usize].register(cx.waker());
if RX_HAS_DATA[self.id as usize].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: &mut MmioUart<'static>) -> Option<UartErrors> {
let rx_status = uart.read_rx_status();
if rx_status.overrun_error() || rx_status.framing_error() || rx_status.parity_error() {
let mut errors_val = UartErrors::default();
if rx_status.overrun_error() {
errors_val.overflow = true;
}
if rx_status.framing_error() {
errors_val.framing = true;
}
if rx_status.parity_error() {
errors_val.parity = true;
}
return Some(errors_val);
}
None
}
fn on_interrupt_rx_common_post_processing(
id: Bank,
rx_enabled: bool,
read_some_data: bool,
) -> Option<UartErrors> {
let idx = id 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 mut uart_regs = unsafe { id.steal_regs() };
// Check for RX errors
if rx_enabled {
errors = on_interrupt_handle_rx_errors(&mut uart_regs);
}
// Clear the interrupt status bits
uart_regs.write_irq_clr(
InterruptClear::builder()
.with_rx_overrun(true)
.with_tx_overrun(false)
.build(),
);
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.steal_regs() };
let irq_status = uart_regs.read_irq_status();
let irq_enabled = uart_regs.read_irq_enabled();
let rx_enabled = irq_enabled.rx();
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_status.rx() {
let available_bytes = uart_regs.read_rx_fifo_trigger().level().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.read_data().value();
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).ok();
}
read_some_data = true;
}
// Timeout, empty the FIFO completely.
if irq_status.rx_timeout() {
while uart_regs.read_rx_status().data_available() {
// While there is data in the FIFO, write it into the reception buffer
let byte = uart_regs.read_data().value();
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).ok();
}
read_some_data = true;
}
let uart_errors = on_interrupt_rx_common_post_processing(bank, rx_enabled, read_some_data);
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_regs = unsafe { bank.steal_regs() };
let irq_status = uart_regs.read_irq_status();
let irq_enabled = uart_regs.read_irq_enabled();
let rx_enabled = irq_enabled.rx();
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_status.rx() {
let available_bytes = uart_regs.read_rx_fifo_trigger().level().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.read_data().value();
if !prod.ready() {
queue_overflow = true;
}
prod.enqueue(byte).ok();
}
read_some_data = true;
}
// Timeout, empty the FIFO completely.
if irq_status.rx_timeout() {
while uart_regs.read_rx_status().data_available() {
// While there is data in the FIFO, write it into the reception buffer
let byte = uart_regs.read_data().value();
if !prod.ready() {
queue_overflow = true;
}
prod.enqueue(byte).ok();
}
read_some_data = true;
}
let uart_errors = on_interrupt_rx_common_post_processing(bank, rx_enabled, read_some_data);
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<const N: usize> {
rx: Rx,
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<const N: usize>(Option<RxAsyncInner<N>>);
impl<const N: usize> ErrorType for RxAsync<N> {
/// Error reporting is done using the result of the interrupt functions.
type Error = Infallible;
}
fn stop_async_rx(rx: &mut Rx) {
rx.disable_interrupts();
rx.disable();
rx.clear_fifo();
}
impl<const N: usize> RxAsync<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, queue: heapless::spsc::Consumer<'static, u8, N>) -> Self {
rx.disable_interrupts();
rx.disable();
rx.clear_fifo();
// Enable those together.
critical_section::with(|_| {
#[cfg(feature = "vor1x")]
rx.enable_interrupts(true);
#[cfg(feature = "vor4x")]
rx.enable_interrupts(true, true);
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, heapless::spsc::Consumer<'static, u8, N>) {
self.stop();
let inner = self.0.take().unwrap();
(inner.rx, inner.queue)
}
}
impl<const N: usize> Drop for RxAsync<N> {
fn drop(&mut self) {
self.stop();
}
}
impl<const N: usize> embedded_io_async::Read for RxAsync<N> {
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
let inner = self.0.as_ref().unwrap();
// 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 inner.queue.len() == 0 {
RX_HAS_DATA[inner.rx.id as usize].store(false, Ordering::Relaxed);
}
let _guard = ActiveReadGuard(inner.rx.id 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<const N: usize> {
rx: Rx,
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<const N: usize>(Option<RxAsyncOverwritingInner<N>>);
impl<const N: usize> ErrorType for RxAsyncOverwriting<N> {
/// Error reporting is done using the result of the interrupt functions.
type Error = Infallible;
}
impl<const N: usize> RxAsyncOverwriting<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,
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(|_| {
#[cfg(feature = "vor4x")]
rx.enable_interrupts(true, true);
#[cfg(feature = "vor1x")]
rx.enable_interrupts(true);
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 {
self.stop();
let inner = self.0.take().unwrap();
inner.rx
}
}
impl<const N: usize> Drop for RxAsyncOverwriting<N> {
fn drop(&mut self) {
self.stop();
}
}
impl<const N: usize> embedded_io_async::Read for RxAsyncOverwriting<N> {
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
let inner = self.0.as_ref().unwrap();
let id = inner.rx.id as usize;
// 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 = inner.shared_consumer.borrow(cs);
if queue.borrow().as_ref().unwrap().len() == 0 {
RX_HAS_DATA[id].store(false, Ordering::Relaxed);
}
});
let _guard = ActiveReadGuard(id);
let mut handle_data_in_queue = |inner: &mut RxAsyncOverwritingInner<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)
}
}
+208
View File
@@ -0,0 +1,208 @@
//! # Async UART transmission functionality.
//!
//! 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.
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 raw_slice::RawBufSlice;
use super::*;
static UART_TX_WAKERS: [AtomicWaker; 2] = [const { AtomicWaker::new() }; 2];
static TX_CONTEXTS: [Mutex<RefCell<TxContext>>; 2] =
[const { Mutex::new(RefCell::new(TxContext::new())) }; 2];
// Completion flag. Kept outside of the context structure as an atomic to avoid
// critical section.
static TX_DONE: [AtomicBool; 2] = [const { AtomicBool::new(false) }; 2];
/// 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 mut uart = unsafe { bank.steal_regs() };
let idx = bank as usize;
let irq_enabled = uart.read_irq_enabled();
// IRQ is not related to TX.
if !irq_enabled.tx() && !irq_enabled.tx_empty() {
return;
}
let tx_status = uart.read_tx_status();
let unexpected_overrun = tx_status.wr_lost();
let mut context = critical_section::with(|cs| {
let context_ref = TX_CONTEXTS[idx].borrow(cs);
*context_ref.borrow()
});
context.tx_overrun = unexpected_overrun;
// 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 { context.slice.get().unwrap() };
if context.progress >= slice.len() && !tx_status.tx_busy() {
uart.modify_irq_enabled(|mut value| {
value.set_tx(false);
value.set_tx_empty(false);
value.set_tx_status(false);
value
});
uart.modify_enable(|mut value| {
value.set_tx(false);
value
});
// 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;
}
while context.progress < slice.len() {
if !uart.read_tx_status().ready() {
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.write_data(Data::new_with_raw_value(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_nulled(),
}
}
}
pub struct TxFuture {
id: Bank,
}
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(tx: &mut Tx, data: &[u8]) -> Self {
TX_DONE[tx.id as usize].store(false, core::sync::atomic::Ordering::Relaxed);
tx.disable_interrupts();
tx.disable();
tx.clear_fifo();
let init_fill_count = core::cmp::min(data.len(), 16);
// We fill the FIFO.
for data in data.iter().take(init_fill_count) {
tx.regs.write_data(Data::new_with_raw_value(*data as u32));
}
critical_section::with(|cs| {
let context_ref = TX_CONTEXTS[tx.id as usize].borrow(cs);
let mut context = context_ref.borrow_mut();
unsafe { 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(
#[cfg(feature = "vor4x")]
true,
);
tx.enable();
});
Self { id: tx.id }
}
}
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.id as usize].register(cx.waker());
if TX_DONE[self.id as usize].swap(false, core::sync::atomic::Ordering::Relaxed) {
let progress = critical_section::with(|cs| {
TX_CONTEXTS[self.id as usize].borrow(cs).borrow().progress
});
return core::task::Poll::Ready(Ok(progress));
}
core::task::Poll::Pending
}
}
impl Drop for TxFuture {
fn drop(&mut self) {
let mut reg_block = unsafe { self.id.steal_regs() };
disable_tx_interrupts(&mut reg_block);
disable_tx(&mut reg_block);
}
}
pub struct TxAsync(Tx);
impl TxAsync {
pub fn new(tx: Tx) -> Self {
Self(tx)
}
pub fn release(self) -> Tx {
self.0
}
}
#[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 embedded_io::ErrorType for TxAsync {
type Error = TxOverrunError;
}
impl Write for TxAsync {
/// 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.0, buf) };
fut.await
}
}