diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index 47ff2831a7..a0f57f3ce4 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - SPI: Added support for 3-wire SPI (#2919) - Add separate config for Rx and Tx (UART) #2965 +- Additional interrupt available in `esp_hal::uart::UartInterrupt` - as well as UartRx functions `wait_for_break()` and `wait_for_break_async().await` (#2858) ### Changed @@ -1134,4 +1135,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 06d97eeac8..eb82d0f44b 100644 --- a/esp-hal/src/uart.rs +++ b/esp-hal/src/uart.rs @@ -852,6 +852,23 @@ where Ok(count) } + /// 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. + 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)); + } + /// Read bytes from the RX FIFO without checking for errors. fn flush_buffer(&mut self, buf: &mut [u8]) -> usize { let mut count = 0; @@ -1047,6 +1064,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, @@ -1170,6 +1192,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) { self.tx.flush() @@ -1475,6 +1502,7 @@ pub(crate) enum TxEvent { #[derive(Debug, EnumSetType)] pub(crate) enum RxEvent { FifoFull, + BreakDetected, CmdCharDetected, FifoOvf, FifoTout, @@ -1490,7 +1518,10 @@ fn rx_event_check_for_error(events: EnumSet) -> Result<(), Error> { RxEvent::GlitchDetected => return Err(Error::GlitchOccurred), RxEvent::FrameError => return Err(Error::FrameFormatViolated), RxEvent::ParityError => return Err(Error::ParityMismatch), - RxEvent::FifoFull | RxEvent::CmdCharDetected | RxEvent::FifoTout => continue, + RxEvent::FifoFull + | RxEvent::BreakDetected + | RxEvent::CmdCharDetected + | RxEvent::FifoTout => continue, } } @@ -1633,6 +1664,13 @@ impl Uart<'_, Async> { self.rx.read_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; + } + /// Asynchronously writes data to the UART transmit buffer. pub async fn write_async(&mut self, words: &[u8]) -> Result { self.tx.write_async(words).await @@ -1757,6 +1795,13 @@ impl UartRx<'_, Async> { } } } + + /// 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; + } } #[cfg(any(doc, feature = "unstable"))] @@ -1815,6 +1860,7 @@ pub(super) fn intr_handler(uart: &Info, state: &State) { let rx_wake = interrupts.rxfifo_full().bit_is_set() || interrupts.rxfifo_ovf().bit_is_set() || interrupts.rxfifo_tout().bit_is_set() + || interrupts.brk_det().bit_is_set() || interrupts.at_cmd_char_det().bit_is_set() || interrupts.glitch_det().bit_is_set() || interrupts.frm_err().bit_is_set() @@ -2130,6 +2176,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 @@ -2151,6 +2198,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 } @@ -2164,6 +2214,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 @@ -2202,6 +2253,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), @@ -2222,6 +2274,7 @@ impl Info { for event in events { let event_triggered = match event { RxEvent::FifoFull => interrupts_enabled.rxfifo_full().bit_is_clear(), + RxEvent::BreakDetected => interrupts_enabled.brk_det().bit_is_clear(), RxEvent::CmdCharDetected => interrupts_enabled.at_cmd_char_det().bit_is_clear(), RxEvent::FifoOvf => interrupts_enabled.rxfifo_ovf().bit_is_clear(), @@ -2244,6 +2297,7 @@ impl Info { for event in events { let event_triggered = match event { RxEvent::FifoFull => interrupts_enabled.rxfifo_full().bit_is_set(), + RxEvent::BreakDetected => interrupts_enabled.brk_det().bit_is_set(), RxEvent::CmdCharDetected => interrupts_enabled.at_cmd_char_det().bit_is_set(), RxEvent::FifoOvf => interrupts_enabled.rxfifo_ovf().bit_is_set(), @@ -2265,6 +2319,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..1203c3a95f --- /dev/null +++ b/examples/src/bin/uart_break_detection.rs @@ -0,0 +1,36 @@ +//! Blocking UART break detection example. +//! +//! The following wiring is assumed: +//! - TX => GPIO17 +//! - RX => GPIO16 + +//% CHIPS: esp32 + +#![no_std] +#![no_main] + +use esp_backtrace as _; +use esp_hal::{ + main, + uart::{Config as UartConfig, DataBits, Parity, RxConfig, 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) + .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) + .with_tx(peripherals.GPIO17); + + 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..7b1fcfee2c --- /dev/null +++ b/examples/src/bin/uart_break_detection_async.rs @@ -0,0 +1,36 @@ +//! Async UART break detection example. +//! +//! The following wiring is assumed: +//! - TX => GPIO17 +//! - 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, RxConfig, 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) + .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) + .with_tx(peripherals.GPIO17) + .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..c0da47ab4d --- /dev/null +++ b/examples/src/bin/uart_interrupts.rs @@ -0,0 +1,69 @@ +//! Example of responding to UART interrupts. +//! +//! The following wiring is assumed: +//! - TX => GPIO17 +//! - RX => GPIO16 + +//% CHIPS: esp32 + +#![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) + .with_tx(peripherals.GPIO17); + + 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_bytes(&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 9f67950934..f7bc944088 100644 --- a/hil-test/Cargo.toml +++ b/hil-test/Cargo.toml @@ -156,6 +156,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; + } +}