2025-03-27 15:18:06 +01:00

187 lines
6.3 KiB
Rust

//! Pio backed uart drivers
use core::convert::Infallible;
use embedded_io_async::{ErrorType, Read, Write};
use fixed::traits::ToFixed;
use crate::clocks::clk_sys_freq;
use crate::gpio::Level;
use crate::pio::{
Common, Config, Direction as PioDirection, FifoJoin, Instance, LoadedProgram, PioPin, ShiftDirection, StateMachine,
};
use crate::Peri;
/// This struct represents a uart tx program loaded into pio instruction memory.
pub struct PioUartTxProgram<'d, PIO: Instance> {
prg: LoadedProgram<'d, PIO>,
}
impl<'d, PIO: Instance> PioUartTxProgram<'d, PIO> {
/// Load the uart tx program into the given pio
pub fn new(common: &mut Common<'d, PIO>) -> Self {
let prg = pio::pio_asm!(
r#"
.side_set 1 opt
; An 8n1 UART transmit program.
; OUT pin 0 and side-set pin 0 are both mapped to UART TX pin.
pull side 1 [7] ; Assert stop bit, or stall with line in idle state
set x, 7 side 0 [7] ; Preload bit counter, assert start bit for 8 clocks
bitloop: ; This loop will run 8 times (8n1 UART)
out pins, 1 ; Shift 1 bit from OSR to the first OUT pin
jmp x-- bitloop [6] ; Each loop iteration is 8 cycles.
"#
);
let prg = common.load_program(&prg.program);
Self { prg }
}
}
/// PIO backed Uart transmitter
pub struct PioUartTx<'d, PIO: Instance, const SM: usize> {
sm_tx: StateMachine<'d, PIO, SM>,
}
impl<'d, PIO: Instance, const SM: usize> PioUartTx<'d, PIO, SM> {
/// Configure a pio state machine to use the loaded tx program.
pub fn new(
baud: u32,
common: &mut Common<'d, PIO>,
mut sm_tx: StateMachine<'d, PIO, SM>,
tx_pin: Peri<'d, impl PioPin>,
program: &PioUartTxProgram<'d, PIO>,
) -> Self {
let tx_pin = common.make_pio_pin(tx_pin);
sm_tx.set_pins(Level::High, &[&tx_pin]);
sm_tx.set_pin_dirs(PioDirection::Out, &[&tx_pin]);
let mut cfg = Config::default();
cfg.set_out_pins(&[&tx_pin]);
cfg.use_program(&program.prg, &[&tx_pin]);
cfg.shift_out.auto_fill = false;
cfg.shift_out.direction = ShiftDirection::Right;
cfg.fifo_join = FifoJoin::TxOnly;
cfg.clock_divider = (clk_sys_freq() / (8 * baud)).to_fixed();
sm_tx.set_config(&cfg);
sm_tx.set_enable(true);
Self { sm_tx }
}
/// Write a single u8
pub async fn write_u8(&mut self, data: u8) {
self.sm_tx.tx().wait_push(data as u32).await;
}
}
impl<PIO: Instance, const SM: usize> ErrorType for PioUartTx<'_, PIO, SM> {
type Error = Infallible;
}
impl<PIO: Instance, const SM: usize> Write for PioUartTx<'_, PIO, SM> {
async fn write(&mut self, buf: &[u8]) -> Result<usize, Infallible> {
for byte in buf {
self.write_u8(*byte).await;
}
Ok(buf.len())
}
}
/// This struct represents a Uart Rx program loaded into pio instruction memory.
pub struct PioUartRxProgram<'d, PIO: Instance> {
prg: LoadedProgram<'d, PIO>,
}
impl<'d, PIO: Instance> PioUartRxProgram<'d, PIO> {
/// Load the uart rx program into the given pio
pub fn new(common: &mut Common<'d, PIO>) -> Self {
let prg = pio::pio_asm!(
r#"
; Slightly more fleshed-out 8n1 UART receiver which handles framing errors and
; break conditions more gracefully.
; IN pin 0 and JMP pin are both mapped to the GPIO used as UART RX.
start:
wait 0 pin 0 ; Stall until start bit is asserted
set x, 7 [10] ; Preload bit counter, then delay until halfway through
rx_bitloop: ; the first data bit (12 cycles incl wait, set).
in pins, 1 ; Shift data bit into ISR
jmp x-- rx_bitloop [6] ; Loop 8 times, each loop iteration is 8 cycles
jmp pin good_rx_stop ; Check stop bit (should be high)
irq 4 rel ; Either a framing error or a break. Set a sticky flag,
wait 1 pin 0 ; and wait for line to return to idle state.
jmp start ; Don't push data if we didn't see good framing.
good_rx_stop: ; No delay before returning to start; a little slack is
in null 24
push ; important in case the TX clock is slightly too fast.
"#
);
let prg = common.load_program(&prg.program);
Self { prg }
}
}
/// PIO backed Uart reciever
pub struct PioUartRx<'d, PIO: Instance, const SM: usize> {
sm_rx: StateMachine<'d, PIO, SM>,
}
impl<'d, PIO: Instance, const SM: usize> PioUartRx<'d, PIO, SM> {
/// Configure a pio state machine to use the loaded rx program.
pub fn new(
baud: u32,
common: &mut Common<'d, PIO>,
mut sm_rx: StateMachine<'d, PIO, SM>,
rx_pin: Peri<'d, impl PioPin>,
program: &PioUartRxProgram<'d, PIO>,
) -> Self {
let mut cfg = Config::default();
cfg.use_program(&program.prg, &[]);
let rx_pin = common.make_pio_pin(rx_pin);
sm_rx.set_pins(Level::High, &[&rx_pin]);
cfg.set_in_pins(&[&rx_pin]);
cfg.set_jmp_pin(&rx_pin);
sm_rx.set_pin_dirs(PioDirection::In, &[&rx_pin]);
cfg.clock_divider = (clk_sys_freq() / (8 * baud)).to_fixed();
cfg.shift_in.auto_fill = false;
cfg.shift_in.direction = ShiftDirection::Right;
cfg.shift_in.threshold = 32;
cfg.fifo_join = FifoJoin::RxOnly;
sm_rx.set_config(&cfg);
sm_rx.set_enable(true);
Self { sm_rx }
}
/// Wait for a single u8
pub async fn read_u8(&mut self) -> u8 {
self.sm_rx.rx().wait_pull().await as u8
}
}
impl<PIO: Instance, const SM: usize> ErrorType for PioUartRx<'_, PIO, SM> {
type Error = Infallible;
}
impl<PIO: Instance, const SM: usize> Read for PioUartRx<'_, PIO, SM> {
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Infallible> {
let mut i = 0;
while i < buf.len() {
buf[i] = self.read_u8().await;
i += 1;
}
Ok(i)
}
}