use core::future::Future;
use embassy_executor::SendSpawner;
use embassy_sync::pipe::TryWriteError;
use embassy_sync::signal::Signal;
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, pipe::Pipe};
use esp_hal::Async;
use esp_hal::gpio::AnyPin;
#[cfg(feature = "bench-loopback")]
use esp_hal::gpio::Flex;
use esp_hal::peripherals::UART1;
use esp_hal::uart::{Config, DataBits, Parity, RxConfig, StopBits, Uart};
use portable_atomic::{AtomicUsize, Ordering};
use ssh_stamp::serial::BufferedSerial;
use ssh_stamp_hal::{Parity as LineParity, UartParams};
use static_cell::StaticCell;
const INWARD_BUF_SZ: usize = 512;
const OUTWARD_BUF_SZ: usize = 256;
const UART_BUF_SZ: usize = 64;
const MAX_BAUD: u32 = 5_000_000;
pub struct BufferedUart {
outward: Pipe<CriticalSectionRawMutex, OUTWARD_BUF_SZ>,
inward: Pipe<CriticalSectionRawMutex, INWARD_BUF_SZ>,
dropped_rx_bytes: AtomicUsize,
}
impl BufferedUart {
#[must_use]
pub fn new() -> Self {
BufferedUart {
outward: Pipe::new(),
inward: Pipe::new(),
dropped_rx_bytes: AtomicUsize::from(0),
}
}
pub async fn run(&self, uart: Uart<'_, Async>) {
let (mut uart_rx, mut uart_tx) = uart.split();
let mut rx_buf = [0u8; UART_BUF_SZ];
let mut tx_buf = [0u8; UART_BUF_SZ];
loop {
use embassy_futures::select::select;
let rd_from = async {
loop {
let Ok(n) = uart_rx.read_async(&mut rx_buf).await else {
continue;
};
let mut rx_slice = &rx_buf[..n];
while !rx_slice.is_empty() {
rx_slice = match self.inward.try_write(rx_slice) {
Ok(w) => &rx_slice[w..],
Err(TryWriteError::Full) => {
let mut drop_buf = [0u8; UART_BUF_SZ];
let dropped = self
.inward
.try_read(&mut drop_buf[..rx_slice.len()])
.unwrap_or(0);
let _ = self.dropped_rx_bytes.fetch_update(
Ordering::Relaxed,
Ordering::Relaxed,
|d| Some(d.saturating_add(dropped)),
);
rx_slice
}
};
}
}
};
let rd_to = async {
loop {
let n = self.outward.read(&mut tx_buf).await;
let mut tx_slice = &tx_buf[..n];
while !tx_slice.is_empty() {
let Ok(written) = uart_tx.write_async(tx_slice).await else {
break;
};
tx_slice = &tx_slice[written..];
}
}
};
select(rd_from, rd_to).await;
}
}
pub async fn read(&self, buf: &mut [u8]) -> usize {
self.inward.read(buf).await
}
pub async fn write(&self, buf: &[u8]) {
self.outward.write_all(buf).await;
}
pub fn check_dropped_bytes(&self) -> usize {
self.dropped_rx_bytes.swap(0, Ordering::Relaxed)
}
}
impl Default for BufferedUart {
fn default() -> Self {
Self::new()
}
}
impl BufferedSerial for BufferedUart {
fn read(&self, buf: &mut [u8]) -> impl Future<Output = usize> {
BufferedUart::read(self, buf)
}
fn write(&self, buf: &[u8]) -> impl Future<Output = ()> {
BufferedUart::write(self, buf)
}
fn check_dropped_bytes(&self) -> usize {
BufferedUart::check_dropped_bytes(self)
}
}
pub struct EspUartPins<'a> {
pub rx: AnyPin<'a>,
pub tx: AnyPin<'a>,
}
pub static UART_BUF: StaticCell<BufferedUart> = StaticCell::new();
pub static UART_SIGNAL: Signal<CriticalSectionRawMutex, u8> = Signal::new();
fn esp_uart_config(params: UartParams) -> Config {
let data_bits = match params.data_bits {
5 => DataBits::_5,
6 => DataBits::_6,
7 => DataBits::_7,
_ => DataBits::_8,
};
let parity = match params.parity {
LineParity::Even => Parity::Even,
LineParity::Odd => Parity::Odd,
LineParity::None => Parity::None,
};
let stop_bits = if params.stop_bits == 2 {
StopBits::_2
} else {
StopBits::_1
};
Config::default()
.with_baudrate(params.baud.clamp(1, MAX_BAUD))
.with_data_bits(data_bits)
.with_parity(parity)
.with_stop_bits(stop_bits)
}
#[embassy_executor::task]
pub async fn uart_task(
uart_buf: &'static BufferedUart,
uart1: UART1<'static>,
pins: EspUartPins<'static>,
params: UartParams,
) {
UART_SIGNAL.wait().await;
let uart_config = esp_uart_config(params).with_rx(
RxConfig::default()
.with_fifo_full_threshold(16)
.with_timeout(1),
);
let uart = Uart::new(uart1, uart_config).expect("UART config error");
#[cfg(feature = "bench-loopback")]
let uart = {
log::warn!("bench-loopback active, the TX is looped back to RX.");
let (rx_sig, tx_sig) = Flex::new(pins.tx).split();
uart.with_rx(rx_sig).with_tx(tx_sig).into_async()
};
#[cfg(not(feature = "bench-loopback"))]
let uart = uart.with_rx(pins.rx).with_tx(pins.tx).into_async();
uart_buf.run(uart).await;
}
pub fn spawn_uart(
spawner: SendSpawner,
uart1: UART1<'static>,
pins: EspUartPins<'static>,
params: UartParams,
) -> &'static BufferedUart {
let uart_buf = UART_BUF.init_with(BufferedUart::new);
spawner.spawn(uart_task(uart_buf, uart1, pins, params).expect("uart_task spawn failed"));
uart_buf
}