From f1d34b5e0130ef9bdd8656d0ec878ff2ca16a5c2 Mon Sep 17 00:00:00 2001 From: Zhouqi Jiang Date: Sat, 19 Oct 2024 23:01:57 +0800 Subject: [PATCH] hal: uart: add uart split feature Now we can split uart into transmit and receive halves. It can be used by bootloaders or firmware to create split stdin and stdout console halves. Signed-off-by: Zhouqi Jiang --- Cargo.toml | 1 + bouffalo-hal/src/uart.rs | 206 +++++++++++++++--- examples/peripherals/uart-cli-demo/Cargo.toml | 17 ++ examples/peripherals/uart-cli-demo/README.md | 8 + examples/peripherals/uart-cli-demo/build.rs | 3 + .../peripherals/uart-cli-demo/src/main.rs | 32 +++ 6 files changed, 239 insertions(+), 28 deletions(-) create mode 100644 examples/peripherals/uart-cli-demo/Cargo.toml create mode 100644 examples/peripherals/uart-cli-demo/README.md create mode 100644 examples/peripherals/uart-cli-demo/build.rs create mode 100644 examples/peripherals/uart-cli-demo/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 5acf4f3..b635a6d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ members = [ "examples/peripherals/pwm-demo", "examples/peripherals/spi-demo", "examples/peripherals/uart-demo", + "examples/peripherals/uart-cli-demo", "examples/peripherals/sdcard-demo", "examples/peripherals/sdcard-gpt-demo", ] diff --git a/bouffalo-hal/src/uart.rs b/bouffalo-hal/src/uart.rs index 7d995d7..18450b2 100644 --- a/bouffalo-hal/src/uart.rs +++ b/bouffalo-hal/src/uart.rs @@ -959,6 +959,21 @@ pub trait Pads { const TXD: bool; /// Checks if this pin configuration includes Receive feature. const RXD: bool; + /// Valid split configuration type for current pads and multiplexers. + type Split; + + fn split(self, uart: T) -> Self::Split; +} + +#[inline] +fn from_pads(uart: T, tx: TX, rx: RX) -> (TransmitHalf, ReceiveHalf) { + ( + TransmitHalf { + uart: unsafe { core::ptr::read_volatile(&uart) }, + _pads: tx, + }, + ReceiveHalf { uart, _pads: rx }, + ) } impl Pads @@ -971,6 +986,14 @@ where const CTS: bool = false; const TXD: bool = true; const RXD: bool = false; + type Split = ( + TransmitHalf, UartMux>)>, + ReceiveHalf, + ); + #[inline] + fn split(self, uart: T) -> Self::Split { + from_pads(uart, self, ()) + } } impl< @@ -998,6 +1021,14 @@ where const CTS: bool = false; const TXD: bool = true; const RXD: bool = true; + type Split = ( + TransmitHalf, UartMux>)>, + ReceiveHalf, UartMux>)>, + ); + #[inline] + fn split(self, uart: T) -> Self::Split { + from_pads(uart, self.0, self.1) + } } impl< @@ -1025,6 +1056,17 @@ where const CTS: bool = true; const TXD: bool = true; const RXD: bool = false; + type Split = TransmitHalf< + T, + ( + (Pad, UartMux>), + (Pad, UartMux>), + ), + >; + #[inline] + fn split(self, uart: T) -> Self::Split { + TransmitHalf { uart, _pads: self } + } } impl< @@ -1066,8 +1108,30 @@ where const CTS: bool = true; const TXD: bool = true; const RXD: bool = false; + type Split = ( + TransmitHalf< + T, + ( + (Pad, UartMux>), + (Pad, UartMux>), + ), + >, + ReceiveHalf< + T, + ( + (Pad, UartMux>), + (Pad, UartMux>), + ), + >, + ); + #[inline] + fn split(self, uart: T) -> Self::Split { + from_pads(uart, (self.0, self.3), (self.1, self.2)) + } } +// TODO: support split for MmUart pads. + impl Pads for Pad where A1: Deref, @@ -1077,6 +1141,12 @@ where const CTS: bool = { N % 4 == 3 }; const TXD: bool = { N % 4 == 0 }; const RXD: bool = { N % 4 == 1 }; + type Split = (); + #[inline] + fn split(self, uart: T) -> Self::Split { + let _ = uart; + () + } } impl Pads @@ -1091,6 +1161,12 @@ where const CTS: bool = { N1 % 4 == 3 || N2 % 4 == 3 }; const TXD: bool = { N1 % 4 == 0 || N2 % 4 == 0 }; const RXD: bool = { N1 % 4 == 1 || N2 % 4 == 1 }; + type Split = (); + #[inline] + fn split(self, uart: T) -> Self::Split { + let _ = uart; + () + } } impl Pads @@ -1111,6 +1187,12 @@ where const CTS: bool = { N1 % 4 == 3 || N2 % 4 == 3 || N3 % 4 == 3 }; const TXD: bool = { N1 % 4 == 0 || N2 % 4 == 0 || N3 % 4 == 0 }; const RXD: bool = { N1 % 4 == 1 || N2 % 4 == 1 || N3 % 4 == 1 }; + type Split = (); + #[inline] + fn split(self, uart: T) -> Self::Split { + let _ = uart; + () + } } impl< @@ -1144,6 +1226,12 @@ where const CTS: bool = { N1 % 4 == 3 || N2 % 4 == 3 || N3 % 4 == 3 || N4 % 4 == 3 }; const TXD: bool = { N1 % 4 == 0 || N2 % 4 == 0 || N3 % 4 == 0 || N4 % 4 == 0 }; const RXD: bool = { N1 % 4 == 1 || N2 % 4 == 1 || N3 % 4 == 1 || N4 % 4 == 1 }; + type Split = (); + #[inline] + fn split(self, uart: T) -> Self::Split { + let _ = uart; + () + } } /// Managed serial peripheral. @@ -1213,6 +1301,67 @@ impl, PADS> Serial { pub fn free(self) -> (UART, PADS) { (self.uart, self.pads) } + + /// Split serial instance into transmit and receive halves. + #[inline] + pub fn split(self) -> >::Split + where + PADS: Pads, + { + self.pads.split(self.uart) + } +} + +#[inline] +fn uart_write(uart: &RegisterBlock, buf: &[u8]) -> Result { + while uart.fifo_config_1.read().transmit_available_bytes() == 0 { + core::hint::spin_loop(); + } + let len = core::cmp::min( + uart.fifo_config_1.read().transmit_available_bytes() as usize, + buf.len(), + ); + buf.iter() + .take(len) + .for_each(|&word| unsafe { uart.fifo_write.write(word) }); + Ok(len) +} + +#[inline] +fn uart_flush(uart: &RegisterBlock) -> Result<(), Error> { + // There are maximum 32 bytes in transmit FIFO queue, wait until all bytes are available, + // meaning that all data in queue has been sent into UART bus. + while uart.fifo_config_1.read().transmit_available_bytes() != 32 { + core::hint::spin_loop(); + } + Ok(()) +} + +#[inline] +fn uart_read(uart: &RegisterBlock, buf: &mut [u8]) -> Result { + while uart.fifo_config_1.read().receive_available_bytes() == 0 { + core::hint::spin_loop(); + } + let len = core::cmp::min( + uart.fifo_config_1.read().receive_available_bytes() as usize, + buf.len(), + ); + buf.iter_mut() + .take(len) + .for_each(|slot| *slot = uart.fifo_read.read()); + Ok(len) +} + +/// Transmit half from splitted serial structure. +pub struct TransmitHalf { + uart: UART, + _pads: PADS, +} + +/// Receive half from splitted serial structure. +pub struct ReceiveHalf { + uart: UART, + _pads: PADS, } /// Extend constructor to owned UART register blocks. @@ -1256,46 +1405,47 @@ impl embedded_io::ErrorType for Serial { type Error = Error; } +impl embedded_io::ErrorType for TransmitHalf { + type Error = Error; +} + +impl embedded_io::ErrorType for ReceiveHalf { + type Error = Error; +} + impl, PADS> embedded_io::Write for Serial { #[inline] fn write(&mut self, buf: &[u8]) -> Result { - while self.uart.fifo_config_1.read().transmit_available_bytes() == 0 { - core::hint::spin_loop(); - } - let len = core::cmp::min( - self.uart.fifo_config_1.read().transmit_available_bytes() as usize, - buf.len(), - ); - buf.iter() - .take(len) - .for_each(|&word| unsafe { self.uart.fifo_write.write(word) }); - Ok(len) + uart_write(&self.uart, buf) } #[inline] fn flush(&mut self) -> Result<(), Self::Error> { - // There are maximum 32 bytes in transmit FIFO queue, wait until all bytes are available, - // meaning that all data in queue has been sent into UART bus. - while self.uart.fifo_config_1.read().transmit_available_bytes() != 32 { - core::hint::spin_loop(); - } - Ok(()) + uart_flush(&self.uart) } } impl, PADS> embedded_io::Read for Serial { #[inline] fn read(&mut self, buf: &mut [u8]) -> Result { - while self.uart.fifo_config_1.read().receive_available_bytes() == 0 { - core::hint::spin_loop(); - } - let len = core::cmp::min( - self.uart.fifo_config_1.read().receive_available_bytes() as usize, - buf.len(), - ); - buf.iter_mut() - .take(len) - .for_each(|slot| *slot = self.uart.fifo_read.read()); - Ok(len) + uart_read(&self.uart, buf) + } +} + +impl, PADS> embedded_io::Write for TransmitHalf { + #[inline] + fn write(&mut self, buf: &[u8]) -> Result { + uart_write(&self.uart, buf) + } + #[inline] + fn flush(&mut self) -> Result<(), Self::Error> { + uart_flush(&self.uart) + } +} + +impl, PADS> embedded_io::Read for ReceiveHalf { + #[inline] + fn read(&mut self, buf: &mut [u8]) -> Result { + uart_read(&self.uart, buf) } } diff --git a/examples/peripherals/uart-cli-demo/Cargo.toml b/examples/peripherals/uart-cli-demo/Cargo.toml new file mode 100644 index 0000000..462a285 --- /dev/null +++ b/examples/peripherals/uart-cli-demo/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "uart-cli-demo" +version = "0.1.0" +edition = "2021" +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bouffalo-hal = { path = "../../../bouffalo-hal", features = ["bl808"] } +bouffalo-rt = { path = "../../../bouffalo-rt", features = ["bl808-dsp"] } +panic-halt = "0.2.0" +embedded-time = "0.12.1" + +[[bin]] +name = "uart-cli-demo" +test = false diff --git a/examples/peripherals/uart-cli-demo/README.md b/examples/peripherals/uart-cli-demo/README.md new file mode 100644 index 0000000..3954c83 --- /dev/null +++ b/examples/peripherals/uart-cli-demo/README.md @@ -0,0 +1,8 @@ +UART peripheral demo with embedded-cli library + +Build this example with: + +``` +rustup target install riscv64imac-unknown-none-elf +cargo build --target riscv64imac-unknown-none-elf --release -p uart-cli-demo +``` diff --git a/examples/peripherals/uart-cli-demo/build.rs b/examples/peripherals/uart-cli-demo/build.rs new file mode 100644 index 0000000..569839a --- /dev/null +++ b/examples/peripherals/uart-cli-demo/build.rs @@ -0,0 +1,3 @@ +fn main() { + println!("cargo:rustc-link-arg=-Tbouffalo-rt.ld"); +} diff --git a/examples/peripherals/uart-cli-demo/src/main.rs b/examples/peripherals/uart-cli-demo/src/main.rs new file mode 100644 index 0000000..222dfd2 --- /dev/null +++ b/examples/peripherals/uart-cli-demo/src/main.rs @@ -0,0 +1,32 @@ +#![no_std] +#![no_main] + +use bouffalo_hal::prelude::*; +use bouffalo_rt::{entry, Clocks, Peripherals}; +use embedded_time::rate::*; +use panic_halt as _; + +#[entry] +fn main(p: Peripherals, c: Clocks) -> ! { + let tx = p.gpio.io14.into_uart(); + let rx = p.gpio.io15.into_uart(); + let sig2 = p.uart_muxes.sig2.into_transmit::<0>(); + let sig3 = p.uart_muxes.sig3.into_receive::<0>(); + + let config = Default::default(); + let serial = p + .uart0 + .freerun(config, 2000000.Bd(), ((tx, sig2), (rx, sig3)), &c); + + let (mut tx, mut rx) = serial.split(); + + let mut buf = [0u8; 1]; + + writeln!(tx, "Hello world!").ok(); + rx.read_exact(&mut buf).ok(); + writeln!(tx, "Character: {}", buf[0]).ok(); + + loop { + // TODO: use embedded-cli to build a serial console + } +}