Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
30 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
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 (#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)
Expand Down
63 changes: 62 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 @@ where
guard: tx_guard,
rts_pin,
tx_pin,
baudrate: config.baudrate,
},
};
serial.init(config)?;
Expand Down Expand Up @@ -518,6 +519,7 @@ pub struct UartTx<'d, Dm: DriverMode> {
guard: PeripheralGuard,
rts_pin: PinGuard,
tx_pin: PinGuard,
baudrate: u32,
}

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

Expand Down Expand Up @@ -855,6 +860,37 @@ 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_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 @@ -1533,6 +1569,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 @@ -1690,6 +1731,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 @@ -2271,6 +2318,7 @@ pub(crate) enum RxEvent {
GlitchDetected,
FrameError,
ParityError,
BreakDetected,
}

fn rx_event_check_for_error(events: EnumSet<RxEvent>) -> Result<(), RxError> {
Expand All @@ -2280,7 +2328,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 @@ -2838,6 +2889,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 @@ -2858,6 +2910,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 @@ -2876,6 +2931,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 @@ -2939,6 +2995,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 @@ -2959,6 +3016,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 @@ -2987,6 +3047,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"]
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