diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index db3853e23a..dc39ea46a5 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -1174,4 +1174,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [0.4.0]: https://github.com/esp-rs/esp-hal/compare/v0.3.0...v0.4.0 [0.3.0]: https://github.com/esp-rs/esp-hal/compare/v0.2.0...v0.3.0 [0.2.0]: https://github.com/esp-rs/esp-hal/compare/v0.1.0...v0.2.0 -[0.1.0]: https://github.com/esp-rs/esp-hal/releases/tag/v0.1.0 +[0.1.0]: https://github.com/esp-rs/esp-hal/releases/tag/v0.1.0 \ No newline at end of file diff --git a/esp-hal/src/uart.rs b/esp-hal/src/uart.rs index ff56451ee9..854fc94067 100644 --- a/esp-hal/src/uart.rs +++ b/esp-hal/src/uart.rs @@ -904,6 +904,24 @@ where Ok(to_read) } + /// Busy waits for a break condition to be detected on the RX + /// line. Condition is met when the receiver detects a NULL character + /// (i.e. logic 0 for one NULL character transmission) after stop bits. + /// + /// Clears the break detection interrupt before returning. + #[instability::unstable] + pub fn wait_for_break(&mut self) { + // Enable the break detection interrupt + self.regs().int_ena().write(|w| w.brk_det().bit(true)); + + while !self.regs().int_raw().read().brk_det().bit() { + // Just busy waiting + } + + // Clear the break detection interrupt + self.regs().int_clr().write(|w| w.brk_det().bit(true)); + } + #[allow(clippy::useless_conversion)] fn rx_fifo_count(&self) -> u16 { let fifo_cnt: u16 = self.regs().status().read().rxfifo_cnt().bits().into(); @@ -1101,6 +1119,11 @@ pub enum UartInterrupt { /// The transmitter has finished sending out all data from the FIFO. TxDone, + /// Break condition has been detected. + /// Triggered when the receiver detects a NULL character (i.e. logic 0 for + /// one NULL character transmission) after stop bits. + RxBreakDetected, + /// The receiver has received more data than what /// [`RxConfig::fifo_full_threshold`] specifies. RxFifoFull, @@ -1273,6 +1296,11 @@ where sync_regs(self.regs()); } + /// Busy waits for a break condition to be detected on the RX line. + pub fn wait_for_break(&mut self) { + self.rx.wait_for_break(); + } + /// Flush the transmit buffer of the UART pub fn flush(&mut self) -> Result<(), TxError> { self.tx.flush() @@ -1683,6 +1711,7 @@ pub(crate) enum TxEvent { #[derive(Debug, EnumSetType)] pub(crate) enum RxEvent { FifoFull, + BreakDetected, CmdCharDetected, FifoOvf, FifoTout, @@ -1698,7 +1727,10 @@ fn rx_event_check_for_error(events: EnumSet) -> Result<(), RxError> { RxEvent::GlitchDetected => return Err(RxError::GlitchOccurred), RxEvent::FrameError => return Err(RxError::FrameFormatViolated), RxEvent::ParityError => return Err(RxError::ParityMismatch), - RxEvent::FifoFull | RxEvent::CmdCharDetected | RxEvent::FifoTout => continue, + RxEvent::FifoFull + | RxEvent::BreakDetected + | RxEvent::CmdCharDetected + | RxEvent::FifoTout => continue, } } @@ -1835,23 +1867,11 @@ impl Uart<'_, Async> { self.rx.read_async(buf).await } - /// Fill buffer asynchronously. - /// - /// This function reads data from the UART receive buffer into the - /// provided buffer. If the buffer is empty, the function waits - /// asynchronously for data to become available, or for an error to occur. - /// - /// Note that this function may ignore the `rx_fifo_full_threshold` setting - /// to ensure that it does not wait for more data than the buffer can hold. - /// - /// ## Cancellation - /// - /// This function is **not** cancellation safe. If the future is dropped - /// before it resolves, or if an error occurs during the read operation, - /// previously read data may be lost. - #[instability::unstable] - pub async fn read_exact_async(&mut self, buf: &mut [u8]) -> Result<(), RxError> { - self.rx.read_exact_async(buf).await + /// Asynchronously waits for a break condition on the RX line. + /// Condition is met when the receiver detects a NULL character (i.e. logic + /// 0 for one NULL character transmission) after stop bits. + pub async fn wait_for_break_async(&mut self) { + self.rx.wait_for_break_async().await; } /// Write data into the TX buffer. @@ -1971,6 +1991,7 @@ impl UartRx<'_, Async> { // Wait for space or event let mut events = RxEvent::FifoFull + | RxEvent::BreakDetected | RxEvent::FifoOvf | RxEvent::FrameError | RxEvent::GlitchDetected @@ -2054,6 +2075,13 @@ impl UartRx<'_, Async> { Ok(()) } + + /// Interrupt-driven wait for a break condition on the RX line. + /// Condition is met when the receiver detects a NULL character (i.e. logic + /// 0 for one NULL character transmission) after stop bits. + pub async fn wait_for_break_async(&mut self) { + UartRxFuture::new(self.uart.reborrow(), RxEvent::BreakDetected).await; + } } #[instability::unstable] @@ -2063,7 +2091,8 @@ impl embedded_io_async::Read for Uart<'_, Async> { } async fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), ReadExactError> { - self.read_exact_async(buf) + self.rx + .read_exact_async(buf) .await .map_err(|e| ReadExactError::Other(IoError::Rx(e))) } @@ -2444,6 +2473,7 @@ impl Info { UartInterrupt::AtCmd => w.at_cmd_char_det().bit(enable), UartInterrupt::TxDone => w.tx_done().bit(enable), UartInterrupt::RxFifoFull => w.rxfifo_full().bit(enable), + UartInterrupt::RxBreakDetected => w.brk_det().bit(enable), }; } w @@ -2465,6 +2495,9 @@ impl Info { if ints.rxfifo_full().bit_is_set() { res.insert(UartInterrupt::RxFifoFull); } + if ints.brk_det().bit_is_set() { + res.insert(UartInterrupt::RxBreakDetected); + } res } @@ -2478,6 +2511,7 @@ impl Info { UartInterrupt::AtCmd => w.at_cmd_char_det().clear_bit_by_one(), UartInterrupt::TxDone => w.tx_done().clear_bit_by_one(), UartInterrupt::RxFifoFull => w.rxfifo_full().clear_bit_by_one(), + UartInterrupt::RxBreakDetected => w.brk_det().clear_bit_by_one(), }; } w @@ -2552,6 +2586,7 @@ impl Info { for event in events { match event { RxEvent::FifoFull => w.rxfifo_full().bit(enable), + RxEvent::BreakDetected => w.brk_det().bit(enable), RxEvent::CmdCharDetected => w.at_cmd_char_det().bit(enable), RxEvent::FifoOvf => w.rxfifo_ovf().bit(enable), @@ -2572,6 +2607,9 @@ impl Info { if pending_interrupts.rxfifo_full().bit_is_set() { active_events |= RxEvent::FifoFull; } + if pending_interrupts.brk_det().bit_is_set() { + active_events |= RxEvent::BreakDetected; + } if pending_interrupts.at_cmd_char_det().bit_is_set() { active_events |= RxEvent::CmdCharDetected; } @@ -2600,6 +2638,7 @@ impl Info { for event in events { match event { RxEvent::FifoFull => w.rxfifo_full().clear_bit_by_one(), + RxEvent::BreakDetected => w.brk_det().clear_bit_by_one(), RxEvent::CmdCharDetected => w.at_cmd_char_det().clear_bit_by_one(), RxEvent::FifoOvf => w.rxfifo_ovf().clear_bit_by_one(), diff --git a/examples/src/bin/uart_break_detection.rs b/examples/src/bin/uart_break_detection.rs new file mode 100644 index 0000000000..5ff38757b8 --- /dev/null +++ b/examples/src/bin/uart_break_detection.rs @@ -0,0 +1,34 @@ +//! Blocking UART break detection example. +//! +//! The following wiring is assumed: +//! - RX => GPIO16 + +//% CHIPS: esp32 +//% FEATURES: esp-hal/unstable + +#![no_std] +#![no_main] + +use esp_backtrace as _; +use esp_hal::{ + main, + uart::{Config as UartConfig, DataBits, Parity, StopBits, Uart}, +}; + +#[main] +fn main() -> ! { + let peripherals = esp_hal::init(esp_hal::Config::default()); + let uart_config = UartConfig::default() + .with_baudrate(19200) + .with_data_bits(DataBits::_8) + .with_parity(Parity::None) + .with_stop_bits(StopBits::_1); + let mut uart = Uart::new(peripherals.UART1, uart_config) + .expect("Failed to initialize UART") + .with_rx(peripherals.GPIO16); + + loop { + uart.wait_for_break(); + esp_println::print!("\nBREAK"); + } +} diff --git a/examples/src/bin/uart_break_detection_async.rs b/examples/src/bin/uart_break_detection_async.rs new file mode 100644 index 0000000000..09305b899a --- /dev/null +++ b/examples/src/bin/uart_break_detection_async.rs @@ -0,0 +1,33 @@ +//! Async UART break detection example. +//! +//! The following wiring is assumed: +//! - RX => GPIO16 + +//% CHIPS: esp32 +//% FEATURES: embassy esp-hal/unstable + +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use esp_backtrace as _; +use esp_hal::uart::{Config as UartConfig, DataBits, Parity, StopBits, Uart}; + +#[esp_hal_embassy::main] +async fn main(_spawner: Spawner) { + let peripherals = esp_hal::init(esp_hal::Config::default()); + let uart_config = UartConfig::default() + .with_baudrate(19200) + .with_data_bits(DataBits::_8) + .with_parity(Parity::None) + .with_stop_bits(StopBits::_1); + let mut uart = Uart::new(peripherals.UART1, uart_config) + .expect("Failed to initialize UART") + .with_rx(peripherals.GPIO16) + .into_async(); + + loop { + uart.wait_for_break_async().await; + esp_println::print!("\nBREAK"); + } +} diff --git a/examples/src/bin/uart_interrupts.rs b/examples/src/bin/uart_interrupts.rs new file mode 100644 index 0000000000..c6591a28ae --- /dev/null +++ b/examples/src/bin/uart_interrupts.rs @@ -0,0 +1,68 @@ +//! Example of responding to UART interrupts. +//! +//! The following wiring is assumed: +//! - RX => GPIO16 + +//% CHIPS: esp32 +//% FEATURES: esp-hal/unstable + +#![no_std] +#![no_main] + +use core::cell::RefCell; + +use critical_section::Mutex; +use esp_backtrace as _; +use esp_hal::{ + handler, + main, + ram, + uart::{Config as UartConfig, DataBits, Parity, RxConfig, StopBits, Uart, UartInterrupt}, + Blocking, +}; + +static SERIAL: Mutex>>> = Mutex::new(RefCell::new(None)); + +#[main] +fn main() -> ! { + let peripherals = esp_hal::init(esp_hal::Config::default()); + let uart_config = UartConfig::default() + .with_baudrate(19200) + .with_data_bits(DataBits::_8) + .with_parity(Parity::None) + .with_stop_bits(StopBits::_1) + .with_rx(RxConfig::default().with_fifo_full_threshold(1)); + let mut uart = Uart::new(peripherals.UART1, uart_config) + .expect("Failed to initialize UART") + .with_rx(peripherals.GPIO16); + + uart.set_interrupt_handler(handler); + + critical_section::with(|cs| { + uart.clear_interrupts(UartInterrupt::RxBreakDetected | UartInterrupt::RxFifoFull); + uart.listen(UartInterrupt::RxBreakDetected | UartInterrupt::RxFifoFull); + SERIAL.borrow_ref_mut(cs).replace(uart); + }); + + loop {} +} + +#[handler] +#[ram] +fn handler() { + critical_section::with(|cs| { + let mut serial = SERIAL.borrow_ref_mut(cs); + let serial = serial.as_mut().unwrap(); + + if serial.interrupts().contains(UartInterrupt::RxBreakDetected) { + esp_println::print!("\nBREAK"); + } + if serial.interrupts().contains(UartInterrupt::RxFifoFull) { + let mut byte = [0u8; 1]; + serial.read(&mut byte).unwrap(); + esp_println::print!(" {:02X}", byte[0]); + } + + serial.clear_interrupts(UartInterrupt::RxBreakDetected | UartInterrupt::RxFifoFull); + }); +} diff --git a/hil-test/Cargo.toml b/hil-test/Cargo.toml index 57c83f5157..c39fcdbffd 100644 --- a/hil-test/Cargo.toml +++ b/hil-test/Cargo.toml @@ -160,6 +160,11 @@ name = "uart_async" harness = false required-features = ["embassy"] +[[test]] +name = "uart_brk_det" +harness = false +required-features = ["embassy"] + [[test]] name = "uart_regression" harness = false diff --git a/hil-test/tests/uart_brk_det.rs b/hil-test/tests/uart_brk_det.rs new file mode 100644 index 0000000000..92dfe167c8 --- /dev/null +++ b/hil-test/tests/uart_brk_det.rs @@ -0,0 +1,49 @@ +//! UART Break Detection test + +//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 +//% FEATURES: embassy + +#![no_std] +#![no_main] + +use esp_hal::{ + uart::{Config as UartConfig, Uart}, + Blocking, +}; +use hil_test as _; + +struct Context { + uart: Uart<'static, Blocking>, +} + +#[cfg(test)] +#[embedded_test::tests(default_timeout = 3, executor = esp_hal_embassy::Executor::new())] +mod tests { + use super::*; + + #[init] + fn init() -> Context { + let peripherals = esp_hal::init(esp_hal::Config::default()); + + let (_, pin) = hil_test::common_test_pins!(peripherals); + let (rx, tx) = pin.split(); + let uart = Uart::new(peripherals.UART1, UartConfig::default()) + .expect("Failed to initialize UART") + .with_rx(rx) + .with_tx(tx); + + Context { uart } + } + + #[test] + fn test_wait_for_break_blocking(mut ctx: Context) { + // TODO: Send (or simulate) a break signal, otherwise this will fail via timeout + ctx.uart.wait_for_break(); + } + + #[test] + async fn test_wait_for_break_async(ctx: Context) { + // TODO: Send (or simulate) a break signal, otherwise this will fail via timeout + ctx.uart.into_async().wait_for_break_async().await; + } +}