Skip to content

Commit 7fc858d

Browse files
zpg6bugadaniJurajSadel
authored
feat: uart break send + detect (#4284)
* feat: uart break send + detect * fix: example configs * fix: not needed directives * fix: instability::unstable; one line doc comment * Update esp-hal/CHANGELOG.md Co-authored-by: Dániel Buga <[email protected]> * fix: fold in hil test * chore: remove old test from toml * feat: add missing `wait_for_break` fn * fix: self.regs * feat: wait_for_break with timeout + async * chore: fmt * fix: pins now match embassy_serial example * test: increase break length * test: uses wait_for_break method * test: with timeout * fix: extend break on other test * fix: missing assert * test: explicit enable before first break * test: delay after enable * test: sync_regs on c6/h2 * test: interleaved * test: sync and delay * test: c6/h2 sync on send * test: sync only without additional delay * test: break detection amongst transmission * fix: data tests should flush to allow full tx * fix: delete unneeded example * feat: added break sending to uart example * fix: use time::Duration * fix: TRMs dictate c3 and s3 need sync_regs after write to conf0 * fix: use Duration in HIL tests * fix: save unoptimizable register read * fix: remove cancellation safe (they're not) * fix: reg assignment bits() * test: no sync after enable_listen_rx (just after conf0 writes) * chore: retrigger ci * chore: retrigger ci again * chore: update example payload to be something more relevant * test: put back sync for c6/h2 * docs: update changelog * docs: clarify changelog entry * fix: missing byte in example * fix: changelog version * Update esp-hal/src/uart/mod.rs Co-authored-by: Juraj Sadel <[email protected]> --------- Co-authored-by: Dániel Buga <[email protected]> Co-authored-by: Juraj Sadel <[email protected]>
1 parent 2d6018d commit 7fc858d

File tree

6 files changed

+518
-1
lines changed

6 files changed

+518
-1
lines changed

esp-hal/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
- Added blocking `send_break`, `wait_for_break` and `wait_for_break_with_timeout` for sending and detecting software breaks with the UART driver (#4284)
13+
- Added support for `RxBreakDetected` interrupt and `wait_for_break_async` for detecting software breaks asynchronously to the UART driver (#4284)
1214

1315
### Changed
1416

esp-hal/src/uart/mod.rs

Lines changed: 176 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,7 @@ where
486486
mem_guard,
487487
rts_pin,
488488
tx_pin,
489+
baudrate: config.baudrate,
489490
},
490491
};
491492
serial.init(config)?;
@@ -523,6 +524,7 @@ pub struct UartTx<'d, Dm: DriverMode> {
523524
mem_guard: MemoryGuard<'d>,
524525
rts_pin: PinGuard,
525526
tx_pin: PinGuard,
527+
baudrate: u32,
526528
}
527529

528530
/// UART (Receive)
@@ -623,6 +625,7 @@ where
623625
type ConfigError = ConfigError;
624626

625627
fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> {
628+
self.baudrate = config.baudrate;
626629
self.apply_config(config)
627630
}
628631
}
@@ -667,6 +670,7 @@ impl<'d> UartTx<'d, Blocking> {
667670
mem_guard: self.mem_guard,
668671
rts_pin: self.rts_pin,
669672
tx_pin: self.tx_pin,
673+
baudrate: self.baudrate,
670674
}
671675
}
672676
}
@@ -690,6 +694,7 @@ impl<'d> UartTx<'d, Async> {
690694
mem_guard: self.mem_guard,
691695
rts_pin: self.rts_pin,
692696
tx_pin: self.tx_pin,
697+
baudrate: self.baudrate,
693698
}
694699
}
695700

@@ -863,6 +868,44 @@ where
863868
while !self.is_tx_idle() {}
864869
}
865870

871+
/// Sends a break signal for a specified duration in bit time.
872+
///
873+
/// Duration is in bits, the time it takes to transfer one bit at the
874+
/// current baud rate. The delay during the break is just busy-waiting.
875+
#[instability::unstable]
876+
pub fn send_break(&mut self, bits: u32) {
877+
// Read the current TX inversion state
878+
let original_conf0 = self.uart.info().regs().conf0().read();
879+
let original_txd_inv = original_conf0.txd_inv().bit();
880+
881+
// Invert the TX line (toggle the current state)
882+
self.uart
883+
.info()
884+
.regs()
885+
.conf0()
886+
.modify(|_, w| w.txd_inv().bit(!original_txd_inv));
887+
888+
#[cfg(any(esp32c3, esp32c6, esp32h2, esp32s3))]
889+
sync_regs(self.uart.info().regs());
890+
891+
// Calculate total delay in microseconds: (bits * 1_000_000) / baudrate_bps
892+
// Use u64 to avoid overflow, then convert back to u32
893+
let total_delay_us = (bits as u64 * 1_000_000) / self.baudrate as u64;
894+
let delay_us = (total_delay_us as u32).max(1);
895+
896+
crate::rom::ets_delay_us(delay_us);
897+
898+
// Restore the original register state
899+
self.uart
900+
.info()
901+
.regs()
902+
.conf0()
903+
.write(|w| unsafe { w.bits(original_conf0.bits()) });
904+
905+
#[cfg(any(esp32c3, esp32c6, esp32h2, esp32s3))]
906+
sync_regs(self.uart.info().regs());
907+
}
908+
866909
/// Checks if the TX line is idle for this UART instance.
867910
///
868911
/// Returns `true` if the transmit line is idle, meaning no data is
@@ -944,6 +987,50 @@ impl<'d> UartRx<'d, Blocking> {
944987
Ok(uart_rx)
945988
}
946989

990+
/// Waits for a break condition to be detected.
991+
///
992+
/// This is a blocking function that will continuously check for a break condition.
993+
/// After detection, the break interrupt flag is automatically cleared.
994+
#[instability::unstable]
995+
pub fn wait_for_break(&mut self) {
996+
self.enable_break_detection();
997+
998+
while !self.regs().int_raw().read().brk_det().bit_is_set() {
999+
// wait
1000+
}
1001+
1002+
self.regs()
1003+
.int_clr()
1004+
.write(|w| w.brk_det().clear_bit_by_one());
1005+
}
1006+
1007+
/// Waits for a break condition to be detected with a timeout.
1008+
///
1009+
/// This is a blocking function that will check for a break condition up to
1010+
/// the specified timeout. Returns `true` if a break was detected, `false` if
1011+
/// the timeout elapsed. After successful detection, the break interrupt flag
1012+
/// is automatically cleared.
1013+
///
1014+
/// ## Arguments
1015+
/// * `timeout` - Maximum time to wait for a break condition
1016+
#[instability::unstable]
1017+
pub fn wait_for_break_with_timeout(&mut self, timeout: crate::time::Duration) -> bool {
1018+
self.enable_break_detection();
1019+
1020+
let start = crate::time::Instant::now();
1021+
1022+
while !self.regs().int_raw().read().brk_det().bit_is_set() {
1023+
if crate::time::Instant::now() - start >= timeout {
1024+
return false;
1025+
}
1026+
}
1027+
1028+
self.regs()
1029+
.int_clr()
1030+
.write(|w| w.brk_det().clear_bit_by_one());
1031+
true
1032+
}
1033+
9471034
/// Reconfigures the driver to operate in [`Async`] mode.
9481035
#[instability::unstable]
9491036
pub fn into_async(self) -> UartRx<'d, Async> {
@@ -1099,6 +1186,19 @@ impl<'d> UartRx<'d, Async> {
10991186

11001187
Ok(())
11011188
}
1189+
1190+
/// Waits for a break condition to be detected asynchronously.
1191+
///
1192+
/// This is an async function that will await until a break condition is
1193+
/// detected on the RX line. After detection, the break interrupt flag is
1194+
/// automatically cleared.
1195+
#[instability::unstable]
1196+
pub async fn wait_for_break_async(&mut self) {
1197+
UartRxFuture::new(self.uart.reborrow(), RxEvent::BreakDetected).await;
1198+
self.regs()
1199+
.int_clr()
1200+
.write(|w| w.brk_det().clear_bit_by_one());
1201+
}
11021202
}
11031203

11041204
impl<'d, Dm> UartRx<'d, Dm>
@@ -1144,6 +1244,23 @@ where
11441244
self
11451245
}
11461246

1247+
/// Enable break detection.
1248+
///
1249+
/// This must be called before any breaks are expected to be received.
1250+
/// Break detection is enabled automatically by [`Self::wait_for_break`]
1251+
/// and [`Self::wait_for_break_with_timeout`], but calling this method
1252+
/// explicitly ensures that breaks occurring before the first wait call
1253+
/// will be reliably detected.
1254+
#[instability::unstable]
1255+
pub fn enable_break_detection(&mut self) {
1256+
self.uart
1257+
.info()
1258+
.enable_listen_rx(RxEvent::BreakDetected.into(), true);
1259+
1260+
#[cfg(any(esp32c6, esp32h2))]
1261+
sync_regs(self.regs());
1262+
}
1263+
11471264
/// Change the configuration.
11481265
///
11491266
/// ## Errors
@@ -1389,6 +1506,29 @@ impl<'d> Uart<'d, Blocking> {
13891506
pub fn clear_interrupts(&mut self, interrupts: EnumSet<UartInterrupt>) {
13901507
self.tx.uart.info().clear_interrupts(interrupts)
13911508
}
1509+
1510+
/// Waits for a break condition to be detected.
1511+
///
1512+
/// This is a blocking function that will continuously check for a break condition.
1513+
/// After detection, the break interrupt flag is automatically cleared.
1514+
#[instability::unstable]
1515+
pub fn wait_for_break(&mut self) {
1516+
self.rx.wait_for_break()
1517+
}
1518+
1519+
/// Waits for a break condition to be detected with a timeout.
1520+
///
1521+
/// This is a blocking function that will check for a break condition up to
1522+
/// the specified timeout. Returns `true` if a break was detected, `false` if
1523+
/// the timeout elapsed. After successful detection, the break interrupt flag
1524+
/// is automatically cleared.
1525+
///
1526+
/// ## Arguments
1527+
/// * `timeout` - Maximum time to wait for a break condition
1528+
#[instability::unstable]
1529+
pub fn wait_for_break_with_timeout(&mut self, timeout: crate::time::Duration) -> bool {
1530+
self.rx.wait_for_break_with_timeout(timeout)
1531+
}
13921532
}
13931533

13941534
impl<'d> Uart<'d, Async> {
@@ -1528,6 +1668,16 @@ impl<'d> Uart<'d, Async> {
15281668
pub async fn read_exact_async(&mut self, buf: &mut [u8]) -> Result<(), RxError> {
15291669
self.rx.read_exact_async(buf).await
15301670
}
1671+
1672+
/// Waits for a break condition to be detected asynchronously.
1673+
///
1674+
/// This is an async function that will await until a break condition is
1675+
/// detected on the RX line. After detection, the break interrupt flag is
1676+
/// automatically cleared.
1677+
#[instability::unstable]
1678+
pub async fn wait_for_break_async(&mut self) {
1679+
self.rx.wait_for_break_async().await
1680+
}
15311681
}
15321682

15331683
/// List of exposed UART events.
@@ -1543,6 +1693,11 @@ pub enum UartInterrupt {
15431693
/// The transmitter has finished sending out all data from the FIFO.
15441694
TxDone,
15451695

1696+
/// Break condition has been detected.
1697+
/// Triggered when the receiver detects a NULL character (i.e. logic 0 for
1698+
/// one NULL character transmission) after stop bits.
1699+
RxBreakDetected,
1700+
15461701
/// The receiver has received more data than what
15471702
/// [`RxConfig::fifo_full_threshold`] specifies.
15481703
RxFifoFull,
@@ -1700,6 +1855,12 @@ where
17001855
self.tx.flush()
17011856
}
17021857

1858+
/// Sends a break signal for a specified duration
1859+
#[instability::unstable]
1860+
pub fn send_break(&mut self, bits: u32) {
1861+
self.tx.send_break(bits)
1862+
}
1863+
17031864
/// Returns whether the UART buffer has data.
17041865
///
17051866
/// If this function returns `true`, [`Self::read`] will not block.
@@ -2233,6 +2394,7 @@ pub(crate) enum RxEvent {
22332394
GlitchDetected,
22342395
FrameError,
22352396
ParityError,
2397+
BreakDetected,
22362398
}
22372399

22382400
fn rx_event_check_for_error(events: EnumSet<RxEvent>) -> Result<(), RxError> {
@@ -2242,7 +2404,10 @@ fn rx_event_check_for_error(events: EnumSet<RxEvent>) -> Result<(), RxError> {
22422404
RxEvent::GlitchDetected => return Err(RxError::GlitchOccurred),
22432405
RxEvent::FrameError => return Err(RxError::FrameFormatViolated),
22442406
RxEvent::ParityError => return Err(RxError::ParityMismatch),
2245-
RxEvent::FifoFull | RxEvent::CmdCharDetected | RxEvent::FifoTout => continue,
2407+
RxEvent::FifoFull
2408+
| RxEvent::CmdCharDetected
2409+
| RxEvent::FifoTout
2410+
| RxEvent::BreakDetected => continue,
22462411
}
22472412
}
22482413

@@ -2804,6 +2969,7 @@ impl Info {
28042969
match interrupt {
28052970
UartInterrupt::AtCmd => w.at_cmd_char_det().bit(enable),
28062971
UartInterrupt::TxDone => w.tx_done().bit(enable),
2972+
UartInterrupt::RxBreakDetected => w.brk_det().bit(enable),
28072973
UartInterrupt::RxFifoFull => w.rxfifo_full().bit(enable),
28082974
UartInterrupt::RxTimeout => w.rxfifo_tout().bit(enable),
28092975
};
@@ -2824,6 +2990,9 @@ impl Info {
28242990
if ints.tx_done().bit_is_set() {
28252991
res.insert(UartInterrupt::TxDone);
28262992
}
2993+
if ints.brk_det().bit_is_set() {
2994+
res.insert(UartInterrupt::RxBreakDetected);
2995+
}
28272996
if ints.rxfifo_full().bit_is_set() {
28282997
res.insert(UartInterrupt::RxFifoFull);
28292998
}
@@ -2842,6 +3011,7 @@ impl Info {
28423011
match interrupt {
28433012
UartInterrupt::AtCmd => w.at_cmd_char_det().clear_bit_by_one(),
28443013
UartInterrupt::TxDone => w.tx_done().clear_bit_by_one(),
3014+
UartInterrupt::RxBreakDetected => w.brk_det().clear_bit_by_one(),
28453015
UartInterrupt::RxFifoFull => w.rxfifo_full().clear_bit_by_one(),
28463016
UartInterrupt::RxTimeout => w.rxfifo_tout().clear_bit_by_one(),
28473017
};
@@ -2905,6 +3075,7 @@ impl Info {
29053075
for event in events {
29063076
match event {
29073077
RxEvent::FifoFull => w.rxfifo_full().bit(enable),
3078+
RxEvent::BreakDetected => w.brk_det().bit(enable),
29083079
RxEvent::CmdCharDetected => w.at_cmd_char_det().bit(enable),
29093080

29103081
RxEvent::FifoOvf => w.rxfifo_ovf().bit(enable),
@@ -2925,6 +3096,9 @@ impl Info {
29253096
if pending_interrupts.rxfifo_full().bit_is_set() {
29263097
active_events |= RxEvent::FifoFull;
29273098
}
3099+
if pending_interrupts.brk_det().bit_is_set() {
3100+
active_events |= RxEvent::BreakDetected;
3101+
}
29283102
if pending_interrupts.at_cmd_char_det().bit_is_set() {
29293103
active_events |= RxEvent::CmdCharDetected;
29303104
}
@@ -2953,6 +3127,7 @@ impl Info {
29533127
for event in events {
29543128
match event {
29553129
RxEvent::FifoFull => w.rxfifo_full().clear_bit_by_one(),
3130+
RxEvent::BreakDetected => w.brk_det().clear_bit_by_one(),
29563131
RxEvent::CmdCharDetected => w.at_cmd_char_det().clear_bit_by_one(),
29573132

29583133
RxEvent::FifoOvf => w.rxfifo_ovf().clear_bit_by_one(),
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
[target.'cfg(target_arch = "riscv32")']
2+
runner = "espflash flash --monitor"
3+
rustflags = [
4+
"-C", "link-arg=-Tlinkall.x",
5+
"-C", "force-frame-pointers",
6+
]
7+
8+
[target.'cfg(target_arch = "xtensa")']
9+
runner = "espflash flash --monitor"
10+
rustflags = [
11+
# GNU LD
12+
"-C", "link-arg=-Wl,-Tlinkall.x",
13+
"-C", "link-arg=-nostartfiles",
14+
15+
# LLD
16+
# "-C", "link-arg=-Tlinkall.x",
17+
# "-C", "linker=rust-lld",
18+
]
19+
20+
[env]
21+
ESP_LOG = "info"
22+
23+
[unstable]
24+
build-std = ["core", "alloc"]

0 commit comments

Comments
 (0)