Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
9be5d23
feat: uart break send + detect
zpg6 Oct 8, 2025
91ef610
fix: example configs
zpg6 Oct 8, 2025
97bb924
Merge branch 'main' into feat/uart-break
zpg6 Oct 8, 2025
8426ce4
fix: not needed directives
zpg6 Oct 8, 2025
ef52491
fix: instability::unstable; one line doc comment
zpg6 Oct 8, 2025
56fd47e
Update esp-hal/CHANGELOG.md
zpg6 Oct 8, 2025
b49bd13
fix: fold in hil test
zpg6 Oct 8, 2025
0001ee1
chore: remove old test from toml
zpg6 Oct 8, 2025
d8b7544
feat: add missing `wait_for_break` fn
zpg6 Oct 8, 2025
e750ea7
fix: self.regs
zpg6 Oct 8, 2025
ebda145
feat: wait_for_break with timeout + async
zpg6 Oct 8, 2025
70861fb
chore: fmt
zpg6 Oct 8, 2025
5ff884b
Merge branch 'main' into feat/uart-break
zpg6 Oct 8, 2025
dcbd3b4
Merge branch 'main' into feat/uart-break
zpg6 Oct 9, 2025
fd088aa
fix: pins now match embassy_serial example
zpg6 Oct 9, 2025
adc4fa5
Merge branch 'main' into feat/uart-break
zpg6 Oct 13, 2025
d54743a
test: increase break length
zpg6 Oct 13, 2025
1a8a819
test: uses wait_for_break method
zpg6 Oct 13, 2025
4a5dfa4
test: with timeout
zpg6 Oct 14, 2025
2b83646
fix: extend break on other test
zpg6 Oct 14, 2025
fa05bbd
fix: missing assert
zpg6 Oct 14, 2025
accb847
test: explicit enable before first break
zpg6 Oct 30, 2025
eef7167
test: delay after enable
zpg6 Oct 30, 2025
ca684f0
test: sync_regs on c6/h2
zpg6 Oct 30, 2025
b975aee
test: interleaved
zpg6 Oct 30, 2025
c1c4203
test: sync and delay
zpg6 Oct 30, 2025
6d027ef
test: c6/h2 sync on send
zpg6 Oct 30, 2025
87c809d
test: sync only without additional delay
zpg6 Oct 30, 2025
cd7704b
test: break detection amongst transmission
zpg6 Oct 30, 2025
c805a66
fix: data tests should flush to allow full tx
zpg6 Oct 30, 2025
e182482
fix: delete unneeded example
zpg6 Nov 5, 2025
4e95ac4
feat: added break sending to uart example
zpg6 Nov 5, 2025
07eb400
fix: use time::Duration
zpg6 Nov 5, 2025
07e3e13
fix: TRMs dictate c3 and s3 need sync_regs after write to conf0
zpg6 Nov 5, 2025
fbc48e5
fix: use Duration in HIL tests
zpg6 Nov 5, 2025
6c3f3c3
fix: save unoptimizable register read
zpg6 Nov 5, 2025
fb5bdbc
fix: remove cancellation safe (they're not)
zpg6 Nov 5, 2025
52c8457
fix: reg assignment bits()
zpg6 Nov 5, 2025
41f91c5
test: no sync after enable_listen_rx (just after conf0 writes)
zpg6 Nov 5, 2025
73e2812
chore: retrigger ci
zpg6 Nov 5, 2025
15b3bb3
chore: retrigger ci again
zpg6 Nov 5, 2025
0dc5623
chore: update example payload to be something more relevant
zpg6 Nov 5, 2025
4fdeade
test: put back sync for c6/h2
zpg6 Nov 5, 2025
6aded73
Merge branch 'main' into feat/uart-break
zpg6 Nov 5, 2025
156da24
docs: update changelog
zpg6 Nov 5, 2025
39a007a
docs: clarify changelog entry
zpg6 Nov 5, 2025
6207cc2
fix: missing byte in example
zpg6 Nov 5, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions esp-hal/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Added the `float-save-restore` feature (enabled by default) for Xtensa MCUs. (#4394)
- Added blocking `send_break`, `wait_for_break` and `wait_for_break_with_timeout` for sending and detecting software breaks with the UART driver (#4284)
- Added support for `RxBreakDetected` interrupt and `wait_for_break_async` for detecting software breaks asynchronously to the UART driver (#4284)

### Changed

Expand Down
177 changes: 176 additions & 1 deletion esp-hal/src/uart/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,7 @@ where
mem_guard,
rts_pin,
tx_pin,
baudrate: config.baudrate,
},
};
serial.init(config)?;
Expand Down Expand Up @@ -523,6 +524,7 @@ pub struct UartTx<'d, Dm: DriverMode> {
mem_guard: MemoryGuard<'d>,
rts_pin: PinGuard,
tx_pin: PinGuard,
baudrate: u32,
}

/// UART (Receive)
Expand Down Expand Up @@ -623,6 +625,7 @@ where
type ConfigError = ConfigError;

fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> {
self.baudrate = config.baudrate;
self.apply_config(config)
}
}
Expand Down Expand Up @@ -667,6 +670,7 @@ impl<'d> UartTx<'d, Blocking> {
mem_guard: self.mem_guard,
rts_pin: self.rts_pin,
tx_pin: self.tx_pin,
baudrate: self.baudrate,
}
}
}
Expand All @@ -690,6 +694,7 @@ impl<'d> UartTx<'d, Async> {
mem_guard: self.mem_guard,
rts_pin: self.rts_pin,
tx_pin: self.tx_pin,
baudrate: self.baudrate,
}
}

Expand Down Expand Up @@ -863,6 +868,44 @@ where
while !self.is_tx_idle() {}
}

/// 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_conf0 = self.uart.info().regs().conf0().read();
let original_txd_inv = original_conf0.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));

#[cfg(any(esp32c3, esp32c6, esp32h2, esp32s3))]
sync_regs(self.uart.info().regs());

// 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 register state
self.uart
.info()
.regs()
.conf0()
.write(|w| unsafe { w.bits(original_conf0.bits()) });

#[cfg(any(esp32c3, esp32c6, esp32h2, esp32s3))]
sync_regs(self.uart.info().regs());
}

/// Checks if the TX line is idle for this UART instance.
///
/// Returns `true` if the transmit line is idle, meaning no data is
Expand Down Expand Up @@ -944,6 +987,50 @@ 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.
/// After detection, the break interrupt flag is automatically cleared.
#[instability::unstable]
pub fn wait_for_break(&mut self) {
self.enable_break_detection();

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` - Maximum time to wait for a break condition
#[instability::unstable]
pub fn wait_for_break_with_timeout(&mut self, timeout: crate::time::Duration) -> bool {
self.enable_break_detection();

let start = crate::time::Instant::now();

while !self.regs().int_raw().read().brk_det().bit_is_set() {
if crate::time::Instant::now() - start >= timeout {
return false;
}
}

self.regs()
.int_clr()
.write(|w| w.brk_det().clear_bit_by_one());
true
}

/// Reconfigures the driver to operate in [`Async`] mode.
#[instability::unstable]
pub fn into_async(self) -> UartRx<'d, Async> {
Expand Down Expand Up @@ -1099,6 +1186,19 @@ 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.
#[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>
Expand Down Expand Up @@ -1144,6 +1244,23 @@ where
self
}

/// Enable break detection.
///
/// This must be called before any breaks are expected to be received.
/// Break detection is enabled automatically by [`Self::wait_for_break`]
/// and [`Self::wait_for_break_with_timeout`], but calling this method
/// explicitly ensures that breaks occurring before the first wait call
/// will be reliably detected.
#[instability::unstable]
pub fn enable_break_detection(&mut self) {
self.uart
.info()
.enable_listen_rx(RxEvent::BreakDetected.into(), true);

#[cfg(any(esp32c6, esp32h2))]
sync_regs(self.regs());
}

/// Change the configuration.
///
/// ## Errors
Expand Down Expand Up @@ -1389,6 +1506,29 @@ impl<'d> Uart<'d, Blocking> {
pub fn clear_interrupts(&mut self, interrupts: EnumSet<UartInterrupt>) {
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` - Maximum time to wait for a break condition
#[instability::unstable]
pub fn wait_for_break_with_timeout(&mut self, timeout: crate::time::Duration) -> bool {
self.rx.wait_for_break_with_timeout(timeout)
}
}

impl<'d> Uart<'d, Async> {
Expand Down Expand Up @@ -1528,6 +1668,16 @@ 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.
#[instability::unstable]
pub async fn wait_for_break_async(&mut self) {
self.rx.wait_for_break_async().await
}
}

/// List of exposed UART events.
Expand All @@ -1543,6 +1693,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,
Expand Down Expand Up @@ -1700,6 +1855,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.
Expand Down Expand Up @@ -2233,6 +2394,7 @@ pub(crate) enum RxEvent {
GlitchDetected,
FrameError,
ParityError,
BreakDetected,
}

fn rx_event_check_for_error(events: EnumSet<RxEvent>) -> Result<(), RxError> {
Expand All @@ -2242,7 +2404,10 @@ fn rx_event_check_for_error(events: EnumSet<RxEvent>) -> 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,
}
}

Expand Down Expand Up @@ -2804,6 +2969,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),
};
Expand All @@ -2824,6 +2990,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);
}
Expand All @@ -2842,6 +3011,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(),
};
Expand Down Expand Up @@ -2905,6 +3075,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),
Expand All @@ -2925,6 +3096,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;
}
Expand Down Expand Up @@ -2953,6 +3127,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(),
Expand Down
24 changes: 24 additions & 0 deletions examples/interrupt/uart/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -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"]
Loading
Loading