pio_uart/
lib.rs

1//! # PioUart Crate
2//!
3//! This crate provides a UART implementation using the PIO hardware on the RP2040 microcontroller.
4//! It's designed to work with the `rp2040_hal` crate and provides a UART interface through the Programmable I/O (PIO) subsystem.
5//!
6//! ## Features
7//! - UART communication using PIO
8//! - Flexible pin assignment for RX and TX
9//! - Customizable baud rate and system frequency settings
10//! - Non-blocking read and write operations
11//!
12//! ## Usage
13//! To use this crate, ensure that you have `rp2040_hal` and `embedded-hal` as dependencies in your `Cargo.toml`.
14//! You'll need to configure the PIO and state machines to set up the UART interface.
15//!
16//! ## Example
17//! ```
18//! use pio_uart::PioUart;
19//! use embedded_io::{Read, Write};
20//! use fugit::ExtU32;
21//!
22//! fn main() {
23//!     // Normal system initialization
24//!     let mut pac = pac::Peripherals::take().unwrap();
25//!     let core = pac::CorePeripherals::take().unwrap();
26//!     let mut watchdog = hal::Watchdog::new(pac.WATCHDOG);
27//!     let clocks = hal::clocks::init_clocks_and_plls(
28//!         rp_pico::XOSC_CRYSTAL_FREQ, pac.XOSC, pac.CLOCKS,
29//!         pac.PLL_SYS, pac.PLL_USB, &mut pac.RESETS, &mut watchdog,
30//!     ).ok().unwrap();
31//!     let sio = hal::Sio::new(pac.SIO);
32//!     let pins = rp_pico::Pins::new(pac.IO_BANK0, pac.PADS_BANK0, sio.gpio_bank0, &mut pac.RESETS);
33//!
34//!     // Initialize software UART
35//!     let mut uart = pio_uart::PioUart::new(
36//!             pac.PIO0,
37//!             pins.gpio16.reconfigure(),
38//!             pins.gpio17.reconfigure(),
39//!             &mut pac.RESETS,
40//!             19200.Hz(),
41//!             125.MHz(),
42//!         );
43//!
44//!     uart.write(b"Hello, UART over PIO!");
45//!     let mut buffer = [0u8; 10];
46//!     uart.read(&mut buffer);
47//! }
48//! ```
49
50#![no_std]
51#![deny(missing_docs)]
52
53use rp2040_hal::{
54    gpio::{Pin, PinId, PullNone, PullUp},
55    pio::{
56        self, InstallError, InstalledProgram, PIOBuilder, PIOExt, ShiftDirection, StateMachine,
57        StateMachineIndex, UninitStateMachine,
58    },
59};
60
61/// Install the UART Rx program in a PIO instance
62pub fn install_rx_program<PIO: PIOExt>(
63    pio: &mut pio::PIO<PIO>,
64) -> Result<RxProgram<PIO>, InstallError> {
65    let program_with_defines = pio_proc::pio_file!("src/uart_rx.pio", select_program("uart_rx"));
66    let program = program_with_defines.program;
67    pio.install(&program).map(|program| RxProgram { program })
68}
69/// Install the UART Tx program in a PIO instance
70pub fn install_tx_program<PIO: PIOExt>(
71    pio: &mut pio::PIO<PIO>,
72) -> Result<TxProgram<PIO>, InstallError> {
73    let program_with_defines = pio_proc::pio_file!("src/uart_tx.pio",);
74    let program = program_with_defines.program;
75    pio.install(&program).map(|program| TxProgram { program })
76}
77
78/// Represents a UART interface using the RP2040's PIO hardware.
79///
80/// # Type Parameters
81/// - `RXID`: The PinId for the RX pin.
82/// - `TXID`: The PinId for the TX pin.
83/// - `PIO`:  The PIO instance, either pac::PIO0 or pac::PIO1.
84/// - `State`: The state of the UART interface, either `pio::Stopped` or `pio::Running`.
85pub struct PioUart<RXID: PinId, TXID: PinId, PIO: PIOExt, State> {
86    rx: PioUartRx<RXID, PIO, pio::SM0, State>,
87    tx: PioUartTx<TXID, PIO, pio::SM1, State>,
88    // The following fields are use to restore the original state in `free()`
89    _rx_program: RxProgram<PIO>,
90    _tx_program: TxProgram<PIO>,
91    _pio: pio::PIO<PIO>,
92    _sm2: UninitStateMachine<(PIO, pio::SM2)>,
93    _sm3: UninitStateMachine<(PIO, pio::SM3)>,
94}
95
96/// Represents the Rx part of a UART interface using the RP2040's PIO hardware.
97///
98/// # Type Parameters
99/// - `PinID`: The PinId for the RX pin.
100/// - `SM`:  The state machine to use.
101/// - `State`: The state of the UART interface, either `pio::Stopped` or `pio::Running`.
102pub struct PioUartRx<PinID: PinId, PIO: PIOExt, SM: StateMachineIndex, State> {
103    rx: pio::Rx<(PIO, SM)>,
104    sm: StateMachine<(PIO, SM), State>,
105    // The following fields are use to restore the original state in `free()`
106    _rx_pin: Pin<PinID, PIO::PinFunction, PullUp>,
107    _tx: pio::Tx<(PIO, SM)>,
108}
109/// Represents the Tx part of a UART interface using the RP2040's PIO hardware.
110///
111/// # Type Parameters
112/// - `PinID`: The PinId for the TX pin.
113/// - `SM`:  The state machine to use.
114/// - `State`: The state of the UART interface, either `pio::Stopped` or `pio::Running`.
115pub struct PioUartTx<PinID: PinId, PIO: PIOExt, SM: StateMachineIndex, State> {
116    tx: pio::Tx<(PIO, SM)>,
117    sm: StateMachine<(PIO, SM), State>,
118    // The following fields are use to restore the original state in `free()`
119    _tx_pin: Pin<PinID, PIO::PinFunction, PullNone>,
120    _rx: pio::Rx<(PIO, SM)>,
121}
122
123/// Token of the already installed UART Rx program. To be obtained with [`install_rx_program`].
124pub struct RxProgram<PIO: PIOExt> {
125    program: InstalledProgram<PIO>,
126}
127/// Token of the already installed UART Tx program. To be obtained with [`install_tx_program`].
128pub struct TxProgram<PIO: PIOExt> {
129    program: InstalledProgram<PIO>,
130}
131
132impl<PinID: PinId, PIO: PIOExt, SM: StateMachineIndex> PioUartRx<PinID, PIO, SM, pio::Stopped> {
133    /// Create a new [`PioUartRx`] instance.
134    /// Requires the [`RxProgram`] to be already installed (see [`install_rx_program`]).
135    ///
136    /// # Arguments
137    /// - `rx_pin`: The RX pin configured with `FunctionPioX` and `PullUp`. Use [`pin.gpioX.reconfigure()`](https://docs.rs/rp2040-hal/latest/rp2040_hal/gpio/struct.Pin.html#method.reconfigure).
138    /// - `sm`: A PIO state machine instance.
139    /// - `rx_program`: The installed Rx program.
140    /// - `baud`: Desired baud rate.
141    /// - `system_freq`: System frequency.
142    pub fn new(
143        rx_pin: Pin<PinID, PIO::PinFunction, PullUp>,
144        rx_sm: UninitStateMachine<(PIO, SM)>,
145        rx_program: &mut RxProgram<PIO>,
146        baud: fugit::HertzU32,
147        system_freq: fugit::HertzU32,
148    ) -> Self {
149        let div = system_freq.to_Hz() as f32 / (8f32 * baud.to_Hz() as f32);
150        let rx_id = rx_pin.id().num;
151
152        let (rx_sm, rx, tx) = Self::build_rx(rx_program, rx_id, rx_sm, div);
153
154        Self {
155            rx,
156            sm: rx_sm,
157            _rx_pin: rx_pin,
158            _tx: tx,
159        }
160    }
161    fn build_rx(
162        token: &mut RxProgram<PIO>,
163        rx_id: u8,
164        sm: UninitStateMachine<(PIO, SM)>,
165        div: f32,
166    ) -> (
167        StateMachine<(PIO, SM), pio::Stopped>,
168        pio::Rx<(PIO, SM)>,
169        pio::Tx<(PIO, SM)>,
170    ) {
171        // SAFETY: Program can not be uninstalled, because it can not be accessed
172        let program = unsafe { token.program.share() };
173        let builder = PIOBuilder::from_installed_program(program);
174        let (mut sm, rx, tx) = builder
175            .in_pin_base(rx_id)
176            .jmp_pin(rx_id)
177            .in_shift_direction(ShiftDirection::Right)
178            .autopush(false)
179            .push_threshold(32)
180            .buffers(pio::Buffers::OnlyRx)
181            .build(sm);
182        sm.set_pindirs([(rx_id, pio::PinDir::Input)].into_iter());
183        sm.set_clock_divisor(div);
184        (sm, rx, tx)
185    }
186    /// Enables the UART, transitioning it to the `Running` state.
187    ///
188    /// # Returns
189    /// An instance of `PioUartRx` in the `Running` state.
190    #[inline]
191    pub fn enable(self) -> PioUartRx<PinID, PIO, SM, pio::Running> {
192        PioUartRx {
193            sm: self.sm.start(),
194            rx: self.rx,
195            _rx_pin: self._rx_pin,
196            _tx: self._tx,
197        }
198    }
199    /// Frees the underlying resources, returning the SM instance and the pin.
200    ///
201    /// # Returns
202    /// A tuple containing the used SM and the RX pin.
203    pub fn free(
204        self,
205    ) -> (
206        UninitStateMachine<(PIO, SM)>,
207        Pin<PinID, PIO::PinFunction, PullUp>,
208    ) {
209        let (rx_sm, _) = self.sm.uninit(self.rx, self._tx);
210        (rx_sm, self._rx_pin)
211    }
212}
213
214impl<PinID: PinId, PIO: PIOExt, SM: StateMachineIndex> PioUartTx<PinID, PIO, SM, pio::Stopped> {
215    /// Create a new [`PioUartTx`] instance.
216    /// Requires the [`TxProgram`] to be already installed (see [`install_tx_program`]).
217    ///
218    /// # Arguments
219    /// - `tx_pin`: The TX pin configured with `FunctionPioX` and `PullNone`. Use [`pin.gpioX.reconfigure()`](https://docs.rs/rp2040-hal/latest/rp2040_hal/gpio/struct.Pin.html#method.reconfigure).
220    /// - `sm`: A PIO state machine instance.
221    /// - `tx_program`: The installed Tx program.
222    /// - `baud`: Desired baud rate.
223    /// - `system_freq`: System frequency.
224    pub fn new(
225        tx_pin: Pin<PinID, PIO::PinFunction, PullNone>,
226        sm: UninitStateMachine<(PIO, SM)>,
227        tx_program: &mut TxProgram<PIO>,
228        baud: fugit::HertzU32,
229        system_freq: fugit::HertzU32,
230    ) -> Self {
231        let div = system_freq.to_Hz() as f32 / (8f32 * baud.to_Hz() as f32);
232        let tx_id = tx_pin.id().num;
233
234        let (tx_sm, rx, tx) = Self::build_tx(tx_program, tx_id, sm, div);
235
236        Self {
237            tx,
238            sm: tx_sm,
239            _tx_pin: tx_pin,
240            _rx: rx,
241        }
242    }
243    fn build_tx(
244        token: &mut TxProgram<PIO>,
245        tx_id: u8,
246        sm: UninitStateMachine<(PIO, SM)>,
247        div: f32,
248    ) -> (
249        StateMachine<(PIO, SM), pio::Stopped>,
250        pio::Rx<(PIO, SM)>,
251        pio::Tx<(PIO, SM)>,
252    ) {
253        // SAFETY: Program can not be uninstalled, because it can not be accessed
254        let program = unsafe { token.program.share() };
255        let builder = PIOBuilder::from_installed_program(program);
256        let (mut sm, rx, tx) = builder
257            .out_shift_direction(ShiftDirection::Right)
258            .autopull(false)
259            .pull_threshold(32)
260            .buffers(pio::Buffers::OnlyTx)
261            .out_pins(tx_id, 1)
262            .side_set_pin_base(tx_id)
263            .build(sm);
264        sm.set_pindirs([(tx_id, pio::PinDir::Output)].into_iter());
265        sm.set_clock_divisor(div);
266        (sm, rx, tx)
267    }
268    /// Enables the UART, transitioning it to the `Running` state.
269    ///
270    /// # Returns
271    /// An instance of `PioUartRx` in the `Running` state.
272    #[inline]
273    pub fn enable(self) -> PioUartTx<PinID, PIO, SM, pio::Running> {
274        PioUartTx {
275            sm: self.sm.start(),
276            tx: self.tx,
277            _tx_pin: self._tx_pin,
278            _rx: self._rx,
279        }
280    }
281    /// Frees the underlying resources, returning the SM instance and the pin.
282    ///
283    /// # Returns
284    /// A tuple containing the used SM and the TX pin.
285    pub fn free(
286        self,
287    ) -> (
288        UninitStateMachine<(PIO, SM)>,
289        Pin<PinID, PIO::PinFunction, PullNone>,
290    ) {
291        let (tx_sm, _) = self.sm.uninit(self._rx, self.tx);
292        (tx_sm, self._tx_pin)
293    }
294}
295
296impl<RXID: PinId, TXID: PinId, PIO: PIOExt> PioUart<RXID, TXID, PIO, pio::Stopped> {
297    /// Create a new [`PioUart`] instance.
298    /// This method consumes the PIO instance and does not allow to use the other 2 state machines.
299    /// If more control is required, use [`PioUartRx`] and [`PioUartTx`] individually.
300    ///
301    /// # Arguments
302    /// - `pio`: A PIO instance from the RP2040, either pac::PIO0 or pac::PIO1.
303    /// - `rx_pin`: The RX pin configured with `FunctionPioX` and `PullUp`. Use [`pin.gpioX.reconfigure()`](https://docs.rs/rp2040-hal/latest/rp2040_hal/gpio/struct.Pin.html#method.reconfigure).
304    /// - `tx_pin`: The TX pin configured with `FunctionPioX` and `PullNone`. Use [`pin.gpioX.reconfigure()`](https://docs.rs/rp2040-hal/latest/rp2040_hal/gpio/struct.Pin.html#method.reconfigure).
305    /// - `resets`: A mutable reference to the RP2040 resets.
306    /// - `baud`: Desired baud rate.
307    /// - `system_freq`: System frequency.
308    pub fn new(
309        pio: PIO,
310        rx_pin: Pin<RXID, <PIO as PIOExt>::PinFunction, PullUp>,
311        tx_pin: Pin<TXID, <PIO as PIOExt>::PinFunction, PullNone>,
312        resets: &mut rp2040_hal::pac::RESETS,
313        baud: fugit::HertzU32,
314        system_freq: fugit::HertzU32,
315    ) -> Self {
316        let (mut pio, sm0, sm1, sm2, sm3) = pio.split(resets);
317        let mut rx_program = install_rx_program(&mut pio).ok().unwrap(); // Should never fail, because no program was loaded yet
318        let mut tx_program = install_tx_program(&mut pio).ok().unwrap(); // Should never fail, because no program was loaded yet
319        let rx = PioUartRx::new(rx_pin, sm0, &mut rx_program, baud, system_freq);
320        let tx = PioUartTx::new(tx_pin, sm1, &mut tx_program, baud, system_freq);
321        Self {
322            rx,
323            tx,
324            _rx_program: rx_program,
325            _tx_program: tx_program,
326            _pio: pio,
327            _sm2: sm2,
328            _sm3: sm3,
329        }
330    }
331
332    /// Enables the UART, transitioning it to the `Running` state.
333    ///
334    /// # Returns
335    /// An instance of `PioUart` in the `Running` state.
336    #[inline]
337    pub fn enable(self) -> PioUart<RXID, TXID, PIO, pio::Running> {
338        PioUart {
339            rx: self.rx.enable(),
340            tx: self.tx.enable(),
341            _rx_program: self._rx_program,
342            _tx_program: self._tx_program,
343            _pio: self._pio,
344            _sm2: self._sm2,
345            _sm3: self._sm3,
346        }
347    }
348    /// Frees the underlying resources, returning the PIO instance and pins.
349    /// Also uninstalls the UART programs.
350    ///
351    /// # Returns
352    /// A tuple containing the PIO, RX pin, and TX pin.
353    pub fn free(
354        mut self,
355    ) -> (
356        PIO,
357        Pin<RXID, <PIO as PIOExt>::PinFunction, PullUp>,
358        Pin<TXID, <PIO as PIOExt>::PinFunction, PullNone>,
359    ) {
360        let (tx_sm, tx_pin) = self.tx.free();
361        let (rx_sm, rx_pin) = self.rx.free();
362        self._pio.uninstall(self._rx_program.program);
363        self._pio.uninstall(self._tx_program.program);
364        let pio = self._pio.free(rx_sm, tx_sm, self._sm2, self._sm3);
365        (pio, rx_pin, tx_pin)
366    }
367}
368
369impl<PinID: PinId, PIO: PIOExt, SM: StateMachineIndex> PioUartRx<PinID, PIO, SM, pio::Running> {
370    /// Reads raw data into a buffer.
371    ///
372    /// # Arguments
373    /// - `buf`: A mutable slice of u8 to store the read data.
374    ///
375    /// # Returns
376    /// `Ok(usize)`: Number of bytes read.
377    /// `Err(())`: If an error occurs.
378    pub fn read_raw(&mut self, mut buf: &mut [u8]) -> Result<usize, ()> {
379        let buf_len = buf.len();
380        while let Some(b) = self.rx.read() {
381            buf[0] = (b >> 24) as u8;
382            buf = &mut buf[1..];
383            if buf.len() == 0 {
384                break;
385            }
386        }
387        Ok(buf_len - buf.len())
388    }
389    /// Stops the UART, transitioning it back to the `Stopped` state.
390    ///
391    /// # Returns
392    /// An instance of `PioUartRx` in the `Stopped` state.
393    #[inline]
394    pub fn stop(self) -> PioUartRx<PinID, PIO, SM, pio::Stopped> {
395        PioUartRx {
396            sm: self.sm.stop(),
397            rx: self.rx,
398            _rx_pin: self._rx_pin,
399            _tx: self._tx,
400        }
401    }
402}
403impl<PinID: PinId, PIO: PIOExt, SM: StateMachineIndex> PioUartTx<PinID, PIO, SM, pio::Running> {
404    /// Writes raw data from a buffer.
405    ///
406    /// # Arguments
407    /// - `buf`: A slice of u8 containing the data to write.
408    ///
409    /// # Returns
410    /// `Ok(())`: On success.
411    /// `Err(())`: If an error occurs.
412    pub fn write_raw(&mut self, buf: &[u8]) -> Result<(), ()> {
413        for b in buf {
414            while self.tx.is_full() {
415                core::hint::spin_loop()
416            }
417            self.tx.write(*b as u32);
418        }
419        Ok(())
420    }
421    /// Flushes the UART transmit buffer.
422    fn flush(&mut self) {
423        while !self.tx.is_empty() {
424            core::hint::spin_loop()
425        }
426        //FIXME This was found by trial and error
427        cortex_m::asm::delay(500 * 125);
428    }
429    /// Stops the UART, transitioning it back to the `Stopped` state.
430    ///
431    /// # Returns
432    /// An instance of `PioUartTx` in the `Stopped` state.
433    #[inline]
434    pub fn stop(self) -> PioUartTx<PinID, PIO, SM, pio::Stopped> {
435        PioUartTx {
436            sm: self.sm.stop(),
437            tx: self.tx,
438            _tx_pin: self._tx_pin,
439            _rx: self._rx,
440        }
441    }
442}
443
444/// Represents errors that can occur in the PIO UART.
445#[derive(core::fmt::Debug, defmt::Format)]
446#[non_exhaustive]
447pub enum PioSerialError {
448    /// General IO error
449    IO,
450}
451
452impl embedded_io::Error for PioSerialError {
453    fn kind(&self) -> embedded_io::ErrorKind {
454        embedded_io::ErrorKind::Other
455    }
456}
457impl<PinID: PinId, PIO: PIOExt, SM: StateMachineIndex> embedded_io::ErrorType
458    for PioUartRx<PinID, PIO, SM, pio::Running>
459{
460    type Error = PioSerialError;
461}
462impl<PinID: PinId, PIO: PIOExt, SM: StateMachineIndex> embedded_io::ErrorType
463    for PioUartTx<PinID, PIO, SM, pio::Running>
464{
465    type Error = PioSerialError;
466}
467impl<RXID: PinId, TXID: PinId, PIO: PIOExt> embedded_io::ErrorType
468    for PioUart<RXID, TXID, PIO, pio::Running>
469{
470    type Error = PioSerialError;
471}
472impl<PinID: PinId, PIO: PIOExt, SM: StateMachineIndex> embedded_io::Read
473    for PioUartRx<PinID, PIO, SM, pio::Running>
474{
475    fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
476        self.read_raw(buf).map_err(|_| PioSerialError::IO)
477    }
478}
479impl<PinID: PinId, PIO: PIOExt, SM: StateMachineIndex> embedded_io::Write
480    for PioUartTx<PinID, PIO, SM, pio::Running>
481{
482    fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
483        self.write_raw(buf)
484            .map(|_| buf.len())
485            .map_err(|_| PioSerialError::IO)
486    }
487    fn flush(&mut self) -> Result<(), Self::Error> {
488        self.flush();
489        Ok(())
490    }
491}
492
493impl<RXID: PinId, TXID: PinId, PIO: PIOExt> embedded_io::Read
494    for PioUart<RXID, TXID, PIO, pio::Running>
495{
496    fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
497        self.rx.read(buf)
498    }
499}
500impl<RXID: PinId, TXID: PinId, PIO: PIOExt> embedded_io::Write
501    for PioUart<RXID, TXID, PIO, pio::Running>
502{
503    fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
504        self.tx.write(buf)
505    }
506    fn flush(&mut self) -> Result<(), Self::Error> {
507        embedded_io::Write::flush(&mut self.tx)
508    }
509}