From 9be5d233cdff2144fa64c98a693aa2ce068d2fe1 Mon Sep 17 00:00:00 2001 From: Zach Grimaldi Date: Tue, 7 Oct 2025 20:41:31 -0400 Subject: [PATCH 01/17] feat: uart break send + detect --- esp-hal/CHANGELOG.md | 1 + esp-hal/src/uart/mod.rs | 51 ++++++++++++++++++++- examples/interrupt/uart/Cargo.toml | 54 ++++++++++++++++++++++ examples/interrupt/uart/src/main.rs | 68 ++++++++++++++++++++++++++++ examples/peripheral/uart/Cargo.toml | 53 ++++++++++++++++++++++ examples/peripheral/uart/src/main.rs | 37 +++++++++++++++ hil-test/Cargo.toml | 5 ++ hil-test/src/bin/uart_brk_det.rs | 42 +++++++++++++++++ 8 files changed, 310 insertions(+), 1 deletion(-) create mode 100644 examples/interrupt/uart/Cargo.toml create mode 100644 examples/interrupt/uart/src/main.rs create mode 100644 examples/peripheral/uart/Cargo.toml create mode 100644 examples/peripheral/uart/src/main.rs create mode 100644 hil-test/src/bin/uart_brk_det.rs diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index 7f80c2f8b50..5c9e3d9f590 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `DmaTxBuffer` and `DmaRxBuffer` now have a `Final` associated type. (#3923) - `RsaBackend, RsaContext`: Work-queue based RSA driver (#3910) - `aes::{AesBackend, AesContext, dma::AesDmaBackend}`: Work-queue based AES driver (#3880, #3897) +- Added `send_break` for sending software breaks with the UART driver (#3177) - `aes::cipher_modes`, `aes::CipherState` for constructing `AesContext`s (#3895) - `aes::dma::DmaCipherState` so that `AesDma` can properly support cipher modes that require state (IV, nonce, etc.) (#3897) - `uart::Uhci`: for UART with DMA using the UHCI peripheral (#3871, #4008, #4011) diff --git a/esp-hal/src/uart/mod.rs b/esp-hal/src/uart/mod.rs index f880de0cb05..738b4101999 100644 --- a/esp-hal/src/uart/mod.rs +++ b/esp-hal/src/uart/mod.rs @@ -471,6 +471,7 @@ where guard: tx_guard, rts_pin, tx_pin, + baudrate: config.baudrate, }, }; serial.init(config)?; @@ -507,6 +508,7 @@ pub struct UartTx<'d, Dm: DriverMode> { guard: PeripheralGuard, rts_pin: PinGuard, tx_pin: PinGuard, + baudrate: u32, } /// UART (Receive) @@ -606,6 +608,7 @@ where type ConfigError = ConfigError; fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { + self.baudrate = config.baudrate; self.apply_config(config) } } @@ -649,6 +652,7 @@ impl<'d> UartTx<'d, Blocking> { guard: self.guard, rts_pin: self.rts_pin, tx_pin: self.tx_pin, + baudrate: self.baudrate, } } } @@ -671,6 +675,7 @@ impl<'d> UartTx<'d, Async> { guard: self.guard, rts_pin: self.rts_pin, tx_pin: self.tx_pin, + baudrate: self.baudrate, } } @@ -844,6 +849,35 @@ where while !self.is_tx_idle() {} } + /// Sends a break signal for a specified duration in bit time, i.e. the time + /// it takes to transfer one bit at the current baud rate. The delay during + /// the break is just is busy-waiting. + pub fn send_break(&mut self, bits: u32) { + // Read the current TX inversion state + let original_txd_inv = self.uart.info().regs().conf0().read().txd_inv().bit(); + + // Invert the TX line (toggle the current state) + self.uart + .info() + .regs() + .conf0() + .modify(|_, w| w.txd_inv().bit(!original_txd_inv)); + + // Calculate total delay in microseconds: (bits * 1_000_000) / baudrate_bps + // Use u64 to avoid overflow, then convert back to u32 + let total_delay_us = (bits as u64 * 1_000_000) / self.baudrate as u64; + let delay_us = (total_delay_us as u32).max(1); + + crate::rom::ets_delay_us(delay_us); + + // Restore the original TX inversion state + self.uart + .info() + .regs() + .conf0() + .modify(|_, w| w.txd_inv().bit(original_txd_inv)); + } + /// Checks if the TX line is idle for this UART instance. /// /// Returns `true` if the transmit line is idle, meaning no data is @@ -1522,6 +1556,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, @@ -1679,6 +1718,12 @@ where self.tx.flush() } + /// Sends a break signal for a specified duration + #[instability::unstable] + pub fn send_break(&mut self, bits: u32) { + self.tx.send_break(bits) + } + /// Returns whether the UART buffer has data. /// /// If this function returns `true`, [`Self::read`] will not block. @@ -2150,6 +2195,7 @@ pub(crate) enum RxEvent { GlitchDetected, FrameError, ParityError, + BreakDetected, } fn rx_event_check_for_error(events: EnumSet) -> Result<(), RxError> { @@ -2159,7 +2205,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::CmdCharDetected + | RxEvent::FifoTout + | RxEvent::BreakDetected => continue, } } diff --git a/examples/interrupt/uart/Cargo.toml b/examples/interrupt/uart/Cargo.toml new file mode 100644 index 00000000000..8cdc6a0a9aa --- /dev/null +++ b/examples/interrupt/uart/Cargo.toml @@ -0,0 +1,54 @@ +[package] +name = "uart_interrupt" +version = "0.0.0" +edition = "2024" +publish = false + +[dependencies] +critical-section = "1.1.3" +esp-backtrace = { path = "../../../esp-backtrace", features = [ + "panic-handler", + "println", +] } +esp-bootloader-esp-idf = { path = "../../../esp-bootloader-esp-idf" } +esp-hal = { path = "../../../esp-hal", features = ["log-04", "unstable"] } +esp-println = { path = "../../../esp-println", features = ["log-04"] } + +[features] +esp32 = ["esp-backtrace/esp32", "esp-bootloader-esp-idf/esp32", "esp-hal/esp32"] +esp32c2 = [ + "esp-backtrace/esp32c2", + "esp-bootloader-esp-idf/esp32c2", + "esp-hal/esp32c2", +] +esp32c3 = [ + "esp-backtrace/esp32c3", + "esp-bootloader-esp-idf/esp32c3", + "esp-hal/esp32c3", +] +esp32c6 = [ + "esp-backtrace/esp32c6", + "esp-bootloader-esp-idf/esp32c6", + "esp-hal/esp32c6", +] +esp32h2 = [ + "esp-backtrace/esp32h2", + "esp-bootloader-esp-idf/esp32h2", + "esp-hal/esp32h2", +] +esp32s2 = [ + "esp-backtrace/esp32s2", + "esp-bootloader-esp-idf/esp32s2", + "esp-hal/esp32s2", +] +esp32s3 = [ + "esp-backtrace/esp32s3", + "esp-bootloader-esp-idf/esp32s3", + "esp-hal/esp32s3", +] + +[profile.release] +debug = true +debug-assertions = true +lto = "fat" +codegen-units = 1 diff --git a/examples/interrupt/uart/src/main.rs b/examples/interrupt/uart/src/main.rs new file mode 100644 index 00000000000..7dcc79366f2 --- /dev/null +++ b/examples/interrupt/uart/src/main.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::{ + Blocking, + handler, + main, + ram, + uart::{Config as UartConfig, DataBits, Parity, RxConfig, StopBits, Uart, UartInterrupt}, +}; + +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/examples/peripheral/uart/Cargo.toml b/examples/peripheral/uart/Cargo.toml new file mode 100644 index 00000000000..f7b5ad70f73 --- /dev/null +++ b/examples/peripheral/uart/Cargo.toml @@ -0,0 +1,53 @@ +[package] +name = "uart" +version = "0.0.0" +edition = "2024" +publish = false + +[dependencies] +esp-backtrace = { path = "../../../esp-backtrace", features = [ + "panic-handler", + "println", +] } +esp-bootloader-esp-idf = { path = "../../../esp-bootloader-esp-idf" } +esp-hal = { path = "../../../esp-hal", features = ["log-04", "unstable"] } +esp-println = { path = "../../../esp-println", features = ["log-04"] } + +[features] +esp32 = ["esp-backtrace/esp32", "esp-bootloader-esp-idf/esp32", "esp-hal/esp32"] +esp32c2 = [ + "esp-backtrace/esp32c2", + "esp-bootloader-esp-idf/esp32c2", + "esp-hal/esp32c2", +] +esp32c3 = [ + "esp-backtrace/esp32c3", + "esp-bootloader-esp-idf/esp32c3", + "esp-hal/esp32c3", +] +esp32c6 = [ + "esp-backtrace/esp32c6", + "esp-bootloader-esp-idf/esp32c6", + "esp-hal/esp32c6", +] +esp32h2 = [ + "esp-backtrace/esp32h2", + "esp-bootloader-esp-idf/esp32h2", + "esp-hal/esp32h2", +] +esp32s2 = [ + "esp-backtrace/esp32s2", + "esp-bootloader-esp-idf/esp32s2", + "esp-hal/esp32s2", +] +esp32s3 = [ + "esp-backtrace/esp32s3", + "esp-bootloader-esp-idf/esp32s3", + "esp-hal/esp32s3", +] + +[profile.release] +debug = true +debug-assertions = true +lto = "fat" +codegen-units = 1 diff --git a/examples/peripheral/uart/src/main.rs b/examples/peripheral/uart/src/main.rs new file mode 100644 index 00000000000..5dd9e3a1198 --- /dev/null +++ b/examples/peripheral/uart/src/main.rs @@ -0,0 +1,37 @@ +//! Example of sending a software break signal from a UART in +//! Blocking mode. +//! +//! The following wiring is assumed: +//! - TX => GPIO17 +//% CHIPS: esp32 +//% FEATURES: esp-hal/unstable + +#![no_std] +#![no_main] + +use esp_backtrace as _; +use esp_hal::{ + delay::Delay, + 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_tx(peripherals.GPIO17); + let delay = Delay::new(); + + loop { + // Send a break signal for 20 bits + uart.send_break(20); + delay.delay_millis(500); + } +} diff --git a/hil-test/Cargo.toml b/hil-test/Cargo.toml index 339cfd6c9d1..19078948a8a 100644 --- a/hil-test/Cargo.toml +++ b/hil-test/Cargo.toml @@ -158,6 +158,11 @@ name = "uart_async" harness = false required-features = ["embassy"] +[[bin]] +name = "uart_brk_det" +harness = false +required-features = ["embassy"] + [[bin]] name = "uart_uhci" harness = false diff --git a/hil-test/src/bin/uart_brk_det.rs b/hil-test/src/bin/uart_brk_det.rs new file mode 100644 index 00000000000..c31916e340f --- /dev/null +++ b/hil-test/src/bin/uart_brk_det.rs @@ -0,0 +1,42 @@ +//! UART Break Detection test +//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 +//% FEATURES: embassy unstable + +#![no_std] +#![no_main] + +use esp_hal::{ + Blocking, + uart::{Config as UartConfig, Uart}, +}; +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 (rx, tx) = hil_test::common_test_pins!(peripherals); + let uart = Uart::new(peripherals.UART1, UartConfig::default()) + .expect("Failed to initialize UART") + .with_rx(rx) + .with_tx(tx); + + Context { uart } + } + + #[test] + async fn test_break_detection(mut ctx: Context) { + ctx.uart.send_break(1); + ctx.uart.into_async().wait_for_break_async().await; + // should exit with break detected before timeout + } +} From 91ef6109a1335e7bde52299982b6d195dfaaab9c Mon Sep 17 00:00:00 2001 From: Zach Grimaldi Date: Tue, 7 Oct 2025 20:46:36 -0400 Subject: [PATCH 02/17] fix: example configs --- esp-hal/src/uart/mod.rs | 10 +++++++++ examples/interrupt/uart/.cargo/config.toml | 24 +++++++++++++++++++++ examples/peripheral/uart/.cargo/config.toml | 24 +++++++++++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 examples/interrupt/uart/.cargo/config.toml create mode 100644 examples/peripheral/uart/.cargo/config.toml diff --git a/esp-hal/src/uart/mod.rs b/esp-hal/src/uart/mod.rs index 738b4101999..f7f78076131 100644 --- a/esp-hal/src/uart/mod.rs +++ b/esp-hal/src/uart/mod.rs @@ -2706,6 +2706,7 @@ impl Info { match interrupt { UartInterrupt::AtCmd => w.at_cmd_char_det().bit(enable), UartInterrupt::TxDone => w.tx_done().bit(enable), + UartInterrupt::RxBreakDetected => w.brk_det().bit(enable), UartInterrupt::RxFifoFull => w.rxfifo_full().bit(enable), UartInterrupt::RxTimeout => w.rxfifo_tout().bit(enable), }; @@ -2726,6 +2727,9 @@ impl Info { if ints.tx_done().bit_is_set() { res.insert(UartInterrupt::TxDone); } + if ints.brk_det().bit_is_set() { + res.insert(UartInterrupt::RxBreakDetected); + } if ints.rxfifo_full().bit_is_set() { res.insert(UartInterrupt::RxFifoFull); } @@ -2744,6 +2748,7 @@ impl Info { match interrupt { UartInterrupt::AtCmd => w.at_cmd_char_det().clear_bit_by_one(), UartInterrupt::TxDone => w.tx_done().clear_bit_by_one(), + UartInterrupt::RxBreakDetected => w.brk_det().clear_bit_by_one(), UartInterrupt::RxFifoFull => w.rxfifo_full().clear_bit_by_one(), UartInterrupt::RxTimeout => w.rxfifo_tout().clear_bit_by_one(), }; @@ -2807,6 +2812,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), @@ -2827,6 +2833,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; } @@ -2855,6 +2864,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/interrupt/uart/.cargo/config.toml b/examples/interrupt/uart/.cargo/config.toml new file mode 100644 index 00000000000..44dacc4278b --- /dev/null +++ b/examples/interrupt/uart/.cargo/config.toml @@ -0,0 +1,24 @@ +[target.'cfg(target_arch = "riscv32")'] +runner = "espflash flash --monitor" +rustflags = [ + "-C", "link-arg=-Tlinkall.x", + "-C", "force-frame-pointers", +] + +[target.'cfg(target_arch = "xtensa")'] +runner = "espflash flash --monitor" +rustflags = [ + # GNU LD + "-C", "link-arg=-Wl,-Tlinkall.x", + "-C", "link-arg=-nostartfiles", + + # LLD + # "-C", "link-arg=-Tlinkall.x", + # "-C", "linker=rust-lld", +] + +[env] +ESP_LOG = "info" + +[unstable] +build-std = ["core", "alloc"] diff --git a/examples/peripheral/uart/.cargo/config.toml b/examples/peripheral/uart/.cargo/config.toml new file mode 100644 index 00000000000..44dacc4278b --- /dev/null +++ b/examples/peripheral/uart/.cargo/config.toml @@ -0,0 +1,24 @@ +[target.'cfg(target_arch = "riscv32")'] +runner = "espflash flash --monitor" +rustflags = [ + "-C", "link-arg=-Tlinkall.x", + "-C", "force-frame-pointers", +] + +[target.'cfg(target_arch = "xtensa")'] +runner = "espflash flash --monitor" +rustflags = [ + # GNU LD + "-C", "link-arg=-Wl,-Tlinkall.x", + "-C", "link-arg=-nostartfiles", + + # LLD + # "-C", "link-arg=-Tlinkall.x", + # "-C", "linker=rust-lld", +] + +[env] +ESP_LOG = "info" + +[unstable] +build-std = ["core", "alloc"] From 8426ce4c529a686ca3820aec6fe559eb53646ea1 Mon Sep 17 00:00:00 2001 From: Zach Grimaldi Date: Wed, 8 Oct 2025 07:16:33 -0400 Subject: [PATCH 03/17] fix: not needed directives --- examples/interrupt/uart/src/main.rs | 3 --- examples/peripheral/uart/src/main.rs | 2 -- 2 files changed, 5 deletions(-) diff --git a/examples/interrupt/uart/src/main.rs b/examples/interrupt/uart/src/main.rs index 7dcc79366f2..4e8edaefb6c 100644 --- a/examples/interrupt/uart/src/main.rs +++ b/examples/interrupt/uart/src/main.rs @@ -3,9 +3,6 @@ //! The following wiring is assumed: //! - RX => GPIO16 -//% CHIPS: esp32 -//% FEATURES: esp-hal/unstable - #![no_std] #![no_main] diff --git a/examples/peripheral/uart/src/main.rs b/examples/peripheral/uart/src/main.rs index 5dd9e3a1198..b7acfe87e8a 100644 --- a/examples/peripheral/uart/src/main.rs +++ b/examples/peripheral/uart/src/main.rs @@ -3,8 +3,6 @@ //! //! The following wiring is assumed: //! - TX => GPIO17 -//% CHIPS: esp32 -//% FEATURES: esp-hal/unstable #![no_std] #![no_main] From ef52491d3e20c1521a141523297d085c85a5bc60 Mon Sep 17 00:00:00 2001 From: Zach Grimaldi Date: Wed, 8 Oct 2025 07:20:11 -0400 Subject: [PATCH 04/17] fix: instability::unstable; one line doc comment --- esp-hal/src/uart/mod.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/esp-hal/src/uart/mod.rs b/esp-hal/src/uart/mod.rs index 870c6e52b1c..7daacceed6b 100644 --- a/esp-hal/src/uart/mod.rs +++ b/esp-hal/src/uart/mod.rs @@ -860,9 +860,11 @@ where while !self.is_tx_idle() {} } - /// Sends a break signal for a specified duration in bit time, i.e. the time - /// it takes to transfer one bit at the current baud rate. The delay during - /// the break is just is busy-waiting. + /// Sends a break signal for a specified duration in bit time. + /// + /// Duration is in bits, the time it takes to transfer one bit at the + /// current baud rate. The delay during the break is just is busy-waiting. + #[instability::unstable] pub fn send_break(&mut self, bits: u32) { // Read the current TX inversion state let original_txd_inv = self.uart.info().regs().conf0().read().txd_inv().bit(); From 56fd47e3bec84c5c2fa91cefee04833cf4c86002 Mon Sep 17 00:00:00 2001 From: Zach Grimaldi Date: Wed, 8 Oct 2025 07:22:23 -0400 Subject: [PATCH 05/17] Update esp-hal/CHANGELOG.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Dániel Buga --- esp-hal/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index b7432eb5b3a..b991786bb3f 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -18,7 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `DmaTxBuffer` and `DmaRxBuffer` now have a `Final` associated type. (#3923) - `RsaBackend, RsaContext`: Work-queue based RSA driver (#3910) - `aes::{AesBackend, AesContext, dma::AesDmaBackend}`: Work-queue based AES driver (#3880, #3897) -- Added `send_break` for sending software breaks with the UART driver (#3177) +- Added `send_break` for sending software breaks with the UART driver (#4284) - `aes::cipher_modes`, `aes::CipherState` for constructing `AesContext`s (#3895) - `aes::dma::DmaCipherState` so that `AesDma` can properly support cipher modes that require state (IV, nonce, etc.) (#3897) - `uart::Uhci`: for UART with DMA using the UHCI peripheral (#3871, #4008, #4011) From b49bd1384561b30a642d72b8899735fe8ef92feb Mon Sep 17 00:00:00 2001 From: Zach Grimaldi Date: Wed, 8 Oct 2025 07:35:44 -0400 Subject: [PATCH 06/17] fix: fold in hil test --- hil-test/src/bin/uart.rs | 9 +++++++ hil-test/src/bin/uart_brk_det.rs | 42 -------------------------------- 2 files changed, 9 insertions(+), 42 deletions(-) delete mode 100644 hil-test/src/bin/uart_brk_det.rs diff --git a/hil-test/src/bin/uart.rs b/hil-test/src/bin/uart.rs index 1300876e6f1..5997b3be7c7 100644 --- a/hil-test/src/bin/uart.rs +++ b/hil-test/src/bin/uart.rs @@ -241,4 +241,13 @@ mod tests { assert_eq!(buf, bytes); } + + #[test] + fn test_break_detection(ctx: Context) { + let mut tx = ctx.uart0.split().1.with_tx(ctx.tx); + let mut rx = ctx.uart1.split().0.with_rx(ctx.rx); + + tx.send_break(1); + rx.wait_for_break(); + } } diff --git a/hil-test/src/bin/uart_brk_det.rs b/hil-test/src/bin/uart_brk_det.rs deleted file mode 100644 index c31916e340f..00000000000 --- a/hil-test/src/bin/uart_brk_det.rs +++ /dev/null @@ -1,42 +0,0 @@ -//! UART Break Detection test -//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 -//% FEATURES: embassy unstable - -#![no_std] -#![no_main] - -use esp_hal::{ - Blocking, - uart::{Config as UartConfig, Uart}, -}; -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 (rx, tx) = hil_test::common_test_pins!(peripherals); - let uart = Uart::new(peripherals.UART1, UartConfig::default()) - .expect("Failed to initialize UART") - .with_rx(rx) - .with_tx(tx); - - Context { uart } - } - - #[test] - async fn test_break_detection(mut ctx: Context) { - ctx.uart.send_break(1); - ctx.uart.into_async().wait_for_break_async().await; - // should exit with break detected before timeout - } -} From 0001ee10e4dd355c1cc8a85b07e95200d9023a6c Mon Sep 17 00:00:00 2001 From: Zach Grimaldi Date: Wed, 8 Oct 2025 07:38:38 -0400 Subject: [PATCH 07/17] chore: remove old test from toml --- hil-test/Cargo.toml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/hil-test/Cargo.toml b/hil-test/Cargo.toml index 3cb5c1019c7..567134352e3 100644 --- a/hil-test/Cargo.toml +++ b/hil-test/Cargo.toml @@ -150,11 +150,6 @@ name = "uart_async" harness = false required-features = ["embassy"] -[[bin]] -name = "uart_brk_det" -harness = false -required-features = ["embassy"] - [[bin]] name = "uart_uhci" harness = false From d8b75448fd021e477984165a05d7052e30d0bb3c Mon Sep 17 00:00:00 2001 From: Zach Grimaldi Date: Wed, 8 Oct 2025 07:47:30 -0400 Subject: [PATCH 08/17] feat: add missing `wait_for_break` fn --- esp-hal/src/uart/mod.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/esp-hal/src/uart/mod.rs b/esp-hal/src/uart/mod.rs index 7daacceed6b..94d9424157a 100644 --- a/esp-hal/src/uart/mod.rs +++ b/esp-hal/src/uart/mod.rs @@ -972,6 +972,16 @@ impl<'d> UartRx<'d, Blocking> { Ok(uart_rx) } + /// Waits for a break condition to be detected. + /// + /// This is a blocking function that will continuously check for a break condition. + #[instability::unstable] + pub fn wait_for_break(&mut self) { + while !self.uart.info().brk_det().bit_is_set() { + // wait + } + } + /// Reconfigures the driver to operate in [`Async`] mode. #[instability::unstable] pub fn into_async(self) -> UartRx<'d, Async> { From e750ea7712d3ece2016065353aa36327ef4f9dd6 Mon Sep 17 00:00:00 2001 From: Zach Grimaldi Date: Wed, 8 Oct 2025 07:54:08 -0400 Subject: [PATCH 09/17] fix: self.regs --- esp-hal/src/uart/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esp-hal/src/uart/mod.rs b/esp-hal/src/uart/mod.rs index 94d9424157a..e0bb9ec9419 100644 --- a/esp-hal/src/uart/mod.rs +++ b/esp-hal/src/uart/mod.rs @@ -977,7 +977,7 @@ impl<'d> UartRx<'d, Blocking> { /// This is a blocking function that will continuously check for a break condition. #[instability::unstable] pub fn wait_for_break(&mut self) { - while !self.uart.info().brk_det().bit_is_set() { + while !self.regs().int_raw().read().brk_det().bit_is_set() { // wait } } From ebda145faf85852ebc982211e0c4417cb8006fda Mon Sep 17 00:00:00 2001 From: Zach Grimaldi Date: Wed, 8 Oct 2025 08:24:15 -0400 Subject: [PATCH 10/17] feat: wait_for_break with timeout + async --- esp-hal/src/uart/mod.rs | 78 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/esp-hal/src/uart/mod.rs b/esp-hal/src/uart/mod.rs index e0bb9ec9419..74e7007b366 100644 --- a/esp-hal/src/uart/mod.rs +++ b/esp-hal/src/uart/mod.rs @@ -975,11 +975,37 @@ impl<'d> UartRx<'d, Blocking> { /// Waits for a break condition to be detected. /// /// This is a blocking function that will continuously check for a break condition. + /// After detection, the break interrupt flag is automatically cleared. #[instability::unstable] pub fn wait_for_break(&mut self) { while !self.regs().int_raw().read().brk_det().bit_is_set() { // wait } + self.regs().int_clr().write(|w| w.brk_det().clear_bit_by_one()); + } + + /// Waits for a break condition to be detected with a timeout. + /// + /// This is a blocking function that will check for a break condition up to + /// the specified timeout. Returns `true` if a break was detected, `false` if + /// the timeout elapsed. After successful detection, the break interrupt flag + /// is automatically cleared. + /// + /// ## Arguments + /// * `timeout_us` - Timeout in microseconds + #[instability::unstable] + pub fn wait_for_break_with_timeout(&mut self, timeout_us: u32) -> bool { + let start = crate::time::Instant::now(); + let timeout_duration = crate::time::Duration::from_micros(timeout_us as u64); + + while !self.regs().int_raw().read().brk_det().bit_is_set() { + if crate::time::Instant::now() - start >= timeout_duration { + return false; + } + } + + self.regs().int_clr().write(|w| w.brk_det().clear_bit_by_one()); + true } /// Reconfigures the driver to operate in [`Async`] mode. @@ -1135,6 +1161,21 @@ impl<'d> UartRx<'d, Async> { Ok(()) } + + /// Waits for a break condition to be detected asynchronously. + /// + /// This is an async function that will await until a break condition is + /// detected on the RX line. After detection, the break interrupt flag is + /// automatically cleared. + /// + /// ## Cancellation + /// + /// This function is cancellation safe. + #[instability::unstable] + pub async fn wait_for_break_async(&mut self) { + UartRxFuture::new(self.uart.reborrow(), RxEvent::BreakDetected).await; + self.regs().int_clr().write(|w| w.brk_det().clear_bit_by_one()); + } } impl<'d, Dm> UartRx<'d, Dm> @@ -1425,6 +1466,29 @@ impl<'d> Uart<'d, Blocking> { pub fn clear_interrupts(&mut self, interrupts: EnumSet) { self.tx.uart.info().clear_interrupts(interrupts) } + + /// Waits for a break condition to be detected. + /// + /// This is a blocking function that will continuously check for a break condition. + /// After detection, the break interrupt flag is automatically cleared. + #[instability::unstable] + pub fn wait_for_break(&mut self) { + self.rx.wait_for_break() + } + + /// Waits for a break condition to be detected with a timeout. + /// + /// This is a blocking function that will check for a break condition up to + /// the specified timeout. Returns `true` if a break was detected, `false` if + /// the timeout elapsed. After successful detection, the break interrupt flag + /// is automatically cleared. + /// + /// ## Arguments + /// * `timeout_us` - Timeout in microseconds + #[instability::unstable] + pub fn wait_for_break_with_timeout(&mut self, timeout_us: u32) -> bool { + self.rx.wait_for_break_with_timeout(timeout_us) + } } impl<'d> Uart<'d, Async> { @@ -1564,6 +1628,20 @@ impl<'d> Uart<'d, Async> { pub async fn read_exact_async(&mut self, buf: &mut [u8]) -> Result<(), RxError> { self.rx.read_exact_async(buf).await } + + /// Waits for a break condition to be detected asynchronously. + /// + /// This is an async function that will await until a break condition is + /// detected on the RX line. After detection, the break interrupt flag is + /// automatically cleared. + /// + /// ## Cancellation + /// + /// This function is cancellation safe. + #[instability::unstable] + pub async fn wait_for_break_async(&mut self) { + self.rx.wait_for_break_async().await + } } /// List of exposed UART events. From 70861fbeff6cb8b228eb7a2b62f0b3d40432d74c Mon Sep 17 00:00:00 2001 From: Zach Grimaldi Date: Wed, 8 Oct 2025 08:24:40 -0400 Subject: [PATCH 11/17] chore: fmt --- esp-hal/src/uart/mod.rs | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/esp-hal/src/uart/mod.rs b/esp-hal/src/uart/mod.rs index 74e7007b366..b9973820cf6 100644 --- a/esp-hal/src/uart/mod.rs +++ b/esp-hal/src/uart/mod.rs @@ -973,7 +973,7 @@ impl<'d> UartRx<'d, Blocking> { } /// Waits for a break condition to be detected. - /// + /// /// This is a blocking function that will continuously check for a break condition. /// After detection, the break interrupt flag is automatically cleared. #[instability::unstable] @@ -981,30 +981,34 @@ impl<'d> UartRx<'d, Blocking> { while !self.regs().int_raw().read().brk_det().bit_is_set() { // wait } - self.regs().int_clr().write(|w| w.brk_det().clear_bit_by_one()); + self.regs() + .int_clr() + .write(|w| w.brk_det().clear_bit_by_one()); } /// Waits for a break condition to be detected with a timeout. - /// + /// /// This is a blocking function that will check for a break condition up to /// the specified timeout. Returns `true` if a break was detected, `false` if /// the timeout elapsed. After successful detection, the break interrupt flag /// is automatically cleared. - /// + /// /// ## Arguments /// * `timeout_us` - Timeout in microseconds #[instability::unstable] pub fn wait_for_break_with_timeout(&mut self, timeout_us: u32) -> bool { let start = crate::time::Instant::now(); let timeout_duration = crate::time::Duration::from_micros(timeout_us as u64); - + while !self.regs().int_raw().read().brk_det().bit_is_set() { if crate::time::Instant::now() - start >= timeout_duration { return false; } } - - self.regs().int_clr().write(|w| w.brk_det().clear_bit_by_one()); + + self.regs() + .int_clr() + .write(|w| w.brk_det().clear_bit_by_one()); true } @@ -1163,7 +1167,7 @@ impl<'d> UartRx<'d, Async> { } /// Waits for a break condition to be detected asynchronously. - /// + /// /// This is an async function that will await until a break condition is /// detected on the RX line. After detection, the break interrupt flag is /// automatically cleared. @@ -1174,7 +1178,9 @@ impl<'d> UartRx<'d, Async> { #[instability::unstable] pub async fn wait_for_break_async(&mut self) { UartRxFuture::new(self.uart.reborrow(), RxEvent::BreakDetected).await; - self.regs().int_clr().write(|w| w.brk_det().clear_bit_by_one()); + self.regs() + .int_clr() + .write(|w| w.brk_det().clear_bit_by_one()); } } @@ -1468,7 +1474,7 @@ impl<'d> Uart<'d, Blocking> { } /// Waits for a break condition to be detected. - /// + /// /// This is a blocking function that will continuously check for a break condition. /// After detection, the break interrupt flag is automatically cleared. #[instability::unstable] @@ -1477,12 +1483,12 @@ impl<'d> Uart<'d, Blocking> { } /// Waits for a break condition to be detected with a timeout. - /// + /// /// This is a blocking function that will check for a break condition up to /// the specified timeout. Returns `true` if a break was detected, `false` if /// the timeout elapsed. After successful detection, the break interrupt flag /// is automatically cleared. - /// + /// /// ## Arguments /// * `timeout_us` - Timeout in microseconds #[instability::unstable] @@ -1630,7 +1636,7 @@ impl<'d> Uart<'d, Async> { } /// Waits for a break condition to be detected asynchronously. - /// + /// /// This is an async function that will await until a break condition is /// detected on the RX line. After detection, the break interrupt flag is /// automatically cleared. From fd088aaaaf7fd2b1977e642155640b8dca5b82c9 Mon Sep 17 00:00:00 2001 From: Zach Grimaldi Date: Thu, 9 Oct 2025 16:28:46 -0400 Subject: [PATCH 12/17] fix: pins now match embassy_serial example --- examples/interrupt/uart/Cargo.toml | 1 + examples/interrupt/uart/src/main.rs | 22 ++++++++++++++++++++-- examples/peripheral/uart/Cargo.toml | 1 + examples/peripheral/uart/src/main.rs | 22 ++++++++++++++++++++-- 4 files changed, 42 insertions(+), 4 deletions(-) diff --git a/examples/interrupt/uart/Cargo.toml b/examples/interrupt/uart/Cargo.toml index 8cdc6a0a9aa..8b1e09d4475 100644 --- a/examples/interrupt/uart/Cargo.toml +++ b/examples/interrupt/uart/Cargo.toml @@ -5,6 +5,7 @@ edition = "2024" publish = false [dependencies] +cfg-if = "1.0.0" critical-section = "1.1.3" esp-backtrace = { path = "../../../esp-backtrace", features = [ "panic-handler", diff --git a/examples/interrupt/uart/src/main.rs b/examples/interrupt/uart/src/main.rs index 4e8edaefb6c..4180abe48d5 100644 --- a/examples/interrupt/uart/src/main.rs +++ b/examples/interrupt/uart/src/main.rs @@ -1,7 +1,8 @@ //! Example of responding to UART interrupts. //! //! The following wiring is assumed: -//! - RX => GPIO16 +//! - RX => GPIO3 (ESP32), GPIO19 (ESP32-C2), GPIO20 (ESP32-C3), GPIO17 (ESP32-C6), GPIO23 +//! (ESP32-H2), GPIO44 (ESP32-S2/S3) #![no_std] #![no_main] @@ -23,6 +24,23 @@ static SERIAL: Mutex>>> = Mutex::new(RefCell::new( #[main] fn main() -> ! { let peripherals = esp_hal::init(esp_hal::Config::default()); + + cfg_if::cfg_if! { + if #[cfg(feature = "esp32")] { + let rx_pin = peripherals.GPIO3; + } else if #[cfg(feature = "esp32c2")] { + let rx_pin = peripherals.GPIO19; + } else if #[cfg(feature = "esp32c3")] { + let rx_pin = peripherals.GPIO20; + } else if #[cfg(feature = "esp32c6")] { + let rx_pin = peripherals.GPIO17; + } else if #[cfg(feature = "esp32h2")] { + let rx_pin = peripherals.GPIO23; + } else if #[cfg(any(feature = "esp32s2", feature = "esp32s3"))] { + let rx_pin = peripherals.GPIO44; + } + } + let uart_config = UartConfig::default() .with_baudrate(19200) .with_data_bits(DataBits::_8) @@ -31,7 +49,7 @@ fn main() -> ! { .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_rx(rx_pin); uart.set_interrupt_handler(handler); diff --git a/examples/peripheral/uart/Cargo.toml b/examples/peripheral/uart/Cargo.toml index f7b5ad70f73..9ee194e1541 100644 --- a/examples/peripheral/uart/Cargo.toml +++ b/examples/peripheral/uart/Cargo.toml @@ -5,6 +5,7 @@ edition = "2024" publish = false [dependencies] +cfg-if = "1.0.0" esp-backtrace = { path = "../../../esp-backtrace", features = [ "panic-handler", "println", diff --git a/examples/peripheral/uart/src/main.rs b/examples/peripheral/uart/src/main.rs index b7acfe87e8a..35bdec359a6 100644 --- a/examples/peripheral/uart/src/main.rs +++ b/examples/peripheral/uart/src/main.rs @@ -2,7 +2,8 @@ //! Blocking mode. //! //! The following wiring is assumed: -//! - TX => GPIO17 +//! - TX => GPIO1 (ESP32), GPIO20 (ESP32-C2), GPIO21 (ESP32-C3), GPIO16 (ESP32-C6), GPIO24 +//! (ESP32-H2), GPIO43 (ESP32-S2/S3) #![no_std] #![no_main] @@ -17,6 +18,23 @@ use esp_hal::{ #[main] fn main() -> ! { let peripherals = esp_hal::init(esp_hal::Config::default()); + + cfg_if::cfg_if! { + if #[cfg(feature = "esp32")] { + let tx_pin = peripherals.GPIO1; + } else if #[cfg(feature = "esp32c2")] { + let tx_pin = peripherals.GPIO20; + } else if #[cfg(feature = "esp32c3")] { + let tx_pin = peripherals.GPIO21; + } else if #[cfg(feature = "esp32c6")] { + let tx_pin = peripherals.GPIO16; + } else if #[cfg(feature = "esp32h2")] { + let tx_pin = peripherals.GPIO24; + } else if #[cfg(any(feature = "esp32s2", feature = "esp32s3"))] { + let tx_pin = peripherals.GPIO43; + } + } + let uart_config = UartConfig::default() .with_baudrate(19200) .with_data_bits(DataBits::_8) @@ -24,7 +42,7 @@ fn main() -> ! { .with_stop_bits(StopBits::_1); let mut uart = Uart::new(peripherals.UART1, uart_config) .expect("Failed to initialize UART") - .with_tx(peripherals.GPIO17); + .with_tx(tx_pin); let delay = Delay::new(); loop { From d54743adf7de46cda8300b4497066ded83bc5a83 Mon Sep 17 00:00:00 2001 From: Zach Grimaldi Date: Mon, 13 Oct 2025 19:46:01 -0400 Subject: [PATCH 13/17] test: increase break length --- hil-test/src/bin/uart.rs | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/hil-test/src/bin/uart.rs b/hil-test/src/bin/uart.rs index b73c170e495..e53c84370d9 100644 --- a/hil-test/src/bin/uart.rs +++ b/hil-test/src/bin/uart.rs @@ -244,8 +244,26 @@ mod tests { let mut tx = ctx.uart0.split().1.with_tx(ctx.tx); let mut rx = ctx.uart1.split().0.with_rx(ctx.rx); - tx.send_break(1); - rx.wait_for_break(); + tx.send_break(100); + assert!(rx.wait_for_break_with_timeout(1_000_000)); + } + + #[test] + fn test_break_detection_no_break(ctx: Context) { + let mut rx = ctx.uart1.split().0.with_rx(ctx.rx); + + assert!(!rx.wait_for_break_with_timeout(100_000)); + } + + #[test] + fn test_break_detection_multiple(ctx: Context) { + let mut tx = ctx.uart0.split().1.with_tx(ctx.tx); + let mut rx = ctx.uart1.split().0.with_rx(ctx.rx); + + for _ in 0..3 { + tx.send_break(50); + assert!(rx.wait_for_break_with_timeout(1_000_000)); + } } } From 1a8a819c518611046713cf9c5b514b88688089a8 Mon Sep 17 00:00:00 2001 From: Zach Grimaldi Date: Mon, 13 Oct 2025 19:56:05 -0400 Subject: [PATCH 14/17] test: uses wait_for_break method --- hil-test/src/bin/uart.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hil-test/src/bin/uart.rs b/hil-test/src/bin/uart.rs index e53c84370d9..b75a60f73ce 100644 --- a/hil-test/src/bin/uart.rs +++ b/hil-test/src/bin/uart.rs @@ -245,7 +245,7 @@ mod tests { let mut rx = ctx.uart1.split().0.with_rx(ctx.rx); tx.send_break(100); - assert!(rx.wait_for_break_with_timeout(1_000_000)); + rx.wait_for_break(); } #[test] From 4a5dfa47fd0eb0938c83587e4cb9b36404abc562 Mon Sep 17 00:00:00 2001 From: Zach Grimaldi Date: Mon, 13 Oct 2025 20:09:12 -0400 Subject: [PATCH 15/17] test: with timeout --- hil-test/src/bin/uart.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hil-test/src/bin/uart.rs b/hil-test/src/bin/uart.rs index b75a60f73ce..b56a993e608 100644 --- a/hil-test/src/bin/uart.rs +++ b/hil-test/src/bin/uart.rs @@ -245,7 +245,7 @@ mod tests { let mut rx = ctx.uart1.split().0.with_rx(ctx.rx); tx.send_break(100); - rx.wait_for_break(); + rx.wait_for_break_with_timeout(1_000_000); } #[test] From 2b83646fb8e3013aa21ac4b6b1f220f3f64dcd8f Mon Sep 17 00:00:00 2001 From: Zach Grimaldi Date: Mon, 13 Oct 2025 20:17:47 -0400 Subject: [PATCH 16/17] fix: extend break on other test --- hil-test/src/bin/uart.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hil-test/src/bin/uart.rs b/hil-test/src/bin/uart.rs index b56a993e608..1619911ef7b 100644 --- a/hil-test/src/bin/uart.rs +++ b/hil-test/src/bin/uart.rs @@ -261,7 +261,7 @@ mod tests { let mut rx = ctx.uart1.split().0.with_rx(ctx.rx); for _ in 0..3 { - tx.send_break(50); + tx.send_break(100); assert!(rx.wait_for_break_with_timeout(1_000_000)); } } From fa05bbdec30b250e81d4ab2e760c8dfe1179dfda Mon Sep 17 00:00:00 2001 From: Zach Grimaldi Date: Mon, 13 Oct 2025 21:08:31 -0400 Subject: [PATCH 17/17] fix: missing assert --- hil-test/src/bin/uart.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hil-test/src/bin/uart.rs b/hil-test/src/bin/uart.rs index 1619911ef7b..b88169828f0 100644 --- a/hil-test/src/bin/uart.rs +++ b/hil-test/src/bin/uart.rs @@ -245,7 +245,7 @@ mod tests { let mut rx = ctx.uart1.split().0.with_rx(ctx.rx); tx.send_break(100); - rx.wait_for_break_with_timeout(1_000_000); + assert!(rx.wait_for_break_with_timeout(1_000_000)); } #[test]