Skip to content
Open
Show file tree
Hide file tree
Changes from 9 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
1 change: 1 addition & 0 deletions esp-hal/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 (#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)
Expand Down
73 changes: 72 additions & 1 deletion esp-hal/src/uart/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,7 @@
guard: tx_guard,
rts_pin,
tx_pin,
baudrate: config.baudrate,
},
};
serial.init(config)?;
Expand Down Expand Up @@ -518,6 +519,7 @@
guard: PeripheralGuard,
rts_pin: PinGuard,
tx_pin: PinGuard,
baudrate: u32,
}

/// UART (Receive)
Expand Down Expand Up @@ -617,6 +619,7 @@
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 @@ -660,6 +663,7 @@
guard: self.guard,
rts_pin: self.rts_pin,
tx_pin: self.tx_pin,
baudrate: self.baudrate,
}
}
}
Expand All @@ -682,6 +686,7 @@
guard: self.guard,
rts_pin: self.rts_pin,
tx_pin: self.tx_pin,
baudrate: self.baudrate,
}
}

Expand Down Expand Up @@ -855,6 +860,37 @@
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_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
Expand Down Expand Up @@ -936,6 +972,16 @@
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() {

Check failure on line 980 in esp-hal/src/uart/mod.rs

View workflow job for this annotation

GitHub Actions / esp-hal

no method named `brk_det` found for reference `&'static uart::Info` in the current scope

Check failure on line 980 in esp-hal/src/uart/mod.rs

View workflow job for this annotation

GitHub Actions / esp-hal

no method named `brk_det` found for reference `&'static uart::Info` in the current scope

Check failure on line 980 in esp-hal/src/uart/mod.rs

View workflow job for this annotation

GitHub Actions / esp-hal

no method named `brk_det` found for reference `&'static uart::Info` in the current scope
// wait
}
}

Comment on lines 990 to 1033
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bugadani - I added wait_for_break to complete the HIL test but I would intend to use it more as an interrupt handler now that we have Rx event for break detection.

Do you approve of this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think doing both should be fine. This function has lower latency than any interrupt handler would, so it can probably stay and remain useful in this form.

It makes sense to add an UartInterrupt::Break variant.

It would be nice to have a wait_for_break_with_timeout variant, and an async fn wait_for_break_async, too.

/// Reconfigures the driver to operate in [`Async`] mode.
#[instability::unstable]
pub fn into_async(self) -> UartRx<'d, Async> {
Expand Down Expand Up @@ -1533,6 +1579,11 @@
/// 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 @@ -1690,6 +1741,12 @@
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 @@ -2271,6 +2328,7 @@
GlitchDetected,
FrameError,
ParityError,
BreakDetected,
}

fn rx_event_check_for_error(events: EnumSet<RxEvent>) -> Result<(), RxError> {
Expand All @@ -2280,7 +2338,10 @@
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 @@ -2838,6 +2899,7 @@
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 @@ -2858,6 +2920,9 @@
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 @@ -2876,6 +2941,7 @@
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 @@ -2939,6 +3005,7 @@
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 @@ -2959,6 +3026,9 @@
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 @@ -2987,6 +3057,7 @@
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"]
54 changes: 54 additions & 0 deletions examples/interrupt/uart/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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
65 changes: 65 additions & 0 deletions examples/interrupt/uart/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//! Example of responding to UART interrupts.
//!
//! The following wiring is assumed:
//! - RX => GPIO16

#![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<RefCell<Option<Uart<Blocking>>>> = 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);
});
}
24 changes: 24 additions & 0 deletions examples/peripheral/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