Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: uart break detection interrupt #2858

Open
wants to merge 27 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
3d14724
feat: uart break detection interrupt
zpg6 Dec 21, 2024
be1a908
format: run `cargo xtask fmt-packages`
zpg6 Dec 21, 2024
b7c3f70
docs: CHANGELOG entry
zpg6 Dec 21, 2024
3e52d6a
fix: no Result needed for `wait_for_break_async`
zpg6 Dec 21, 2024
44be3e6
fix: clarify added interrupt in changelog
zpg6 Dec 21, 2024
d1531ac
test: adding `uart_interrupts` example
zpg6 Dec 29, 2024
a38ed91
test: adds examples for blocking and async break detection w/o handlers
zpg6 Dec 29, 2024
118dce8
chore: format on register write statement
zpg6 Dec 29, 2024
735f527
chore: comment format
zpg6 Dec 29, 2024
fecb91c
chore: more formatting
zpg6 Dec 29, 2024
473780c
fix: for blocking impl be sure to enable the interrupt and clear it
zpg6 Dec 29, 2024
9d0c122
fix: flipped rx and tx in `Uart::new()`
zpg6 Dec 29, 2024
06cc4fd
fix: note only tested on `esp32`
zpg6 Dec 29, 2024
a19f915
fix: better handler debug output to demonstrate
zpg6 Dec 29, 2024
7c830df
fix: again more useful debug output
zpg6 Dec 30, 2024
af8a228
chore: formatting
zpg6 Dec 30, 2024
149c6b9
chore: format and debug output
zpg6 Dec 30, 2024
d344c91
docs: clarify interrupt every time a byte is received
zpg6 Dec 30, 2024
a2153e7
fmt: debug message too long
zpg6 Dec 30, 2024
f2c9d35
test: adds uart_brk_det HIL test
zpg6 Dec 30, 2024
ed719da
chore: embassy features not needed for blocking example
zpg6 Dec 30, 2024
fd52ecf
chore: revert to latest main
zpg6 Jan 26, 2025
1deff46
Merge branch 'main' into feat/uart/break-detection-interrupt
zpg6 Jan 26, 2025
cdb7bdc
feat: migrate to v0.23
zpg6 Jan 26, 2025
049ee95
chore: add back into changelog
zpg6 Jan 26, 2025
9f908e9
chore: fmt hil test
zpg6 Jan 26, 2025
772a297
fix: unused import
zpg6 Jan 26, 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
3 changes: 2 additions & 1 deletion esp-hal/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- SPI: Added support for 3-wire SPI (#2919)
- Add separate config for Rx and Tx (UART) #2965
- Additional interrupt available in `esp_hal::uart::UartInterrupt` - as well as UartRx functions `wait_for_break()` and `wait_for_break_async().await` (#2858)

### Changed

Expand Down Expand Up @@ -1134,4 +1135,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[0.4.0]: https://github.com/esp-rs/esp-hal/compare/v0.3.0...v0.4.0
[0.3.0]: https://github.com/esp-rs/esp-hal/compare/v0.2.0...v0.3.0
[0.2.0]: https://github.com/esp-rs/esp-hal/compare/v0.1.0...v0.2.0
[0.1.0]: https://github.com/esp-rs/esp-hal/releases/tag/v0.1.0
[0.1.0]: https://github.com/esp-rs/esp-hal/releases/tag/v0.1.0
57 changes: 56 additions & 1 deletion esp-hal/src/uart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,23 @@ where
Ok(count)
}

/// Busy waits for a break condition to be detected on the RX
/// line. Condition is met when the receiver detects a NULL character
/// (i.e. logic 0 for one NULL character transmission) after stop bits.
///
/// Clears the break detection interrupt before returning.
pub fn wait_for_break(&mut self) {
// Enable the break detection interrupt
self.regs().int_ena().write(|w| w.brk_det().bit(true));

while !self.regs().int_raw().read().brk_det().bit() {
// Just busy waiting
}

// Clear the break detection interrupt
self.regs().int_clr().write(|w| w.brk_det().bit(true));
}

/// Read bytes from the RX FIFO without checking for errors.
fn flush_buffer(&mut self, buf: &mut [u8]) -> usize {
let mut count = 0;
Expand Down Expand Up @@ -1047,6 +1064,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 @@ -1170,6 +1192,11 @@ where
sync_regs(self.regs());
}

/// Busy waits for a break condition to be detected on the RX line.
pub fn wait_for_break(&mut self) {
self.rx.wait_for_break();
}

/// Flush the transmit buffer of the UART
pub fn flush(&mut self) {
self.tx.flush()
Expand Down Expand Up @@ -1475,6 +1502,7 @@ pub(crate) enum TxEvent {
#[derive(Debug, EnumSetType)]
pub(crate) enum RxEvent {
FifoFull,
BreakDetected,
CmdCharDetected,
FifoOvf,
FifoTout,
Expand All @@ -1490,7 +1518,10 @@ fn rx_event_check_for_error(events: EnumSet<RxEvent>) -> Result<(), Error> {
RxEvent::GlitchDetected => return Err(Error::GlitchOccurred),
RxEvent::FrameError => return Err(Error::FrameFormatViolated),
RxEvent::ParityError => return Err(Error::ParityMismatch),
RxEvent::FifoFull | RxEvent::CmdCharDetected | RxEvent::FifoTout => continue,
RxEvent::FifoFull
| RxEvent::BreakDetected
| RxEvent::CmdCharDetected
| RxEvent::FifoTout => continue,
}
}

Expand Down Expand Up @@ -1633,6 +1664,13 @@ impl Uart<'_, Async> {
self.rx.read_async(buf).await
}

/// Asynchronously waits for a break condition on the RX line.
/// Condition is met when the receiver detects a NULL character (i.e. logic
/// 0 for one NULL character transmission) after stop bits.
pub async fn wait_for_break_async(&mut self) {
self.rx.wait_for_break_async().await;
}

/// Asynchronously writes data to the UART transmit buffer.
pub async fn write_async(&mut self, words: &[u8]) -> Result<usize, Error> {
self.tx.write_async(words).await
Expand Down Expand Up @@ -1757,6 +1795,13 @@ impl UartRx<'_, Async> {
}
}
}

/// Interrupt-driven wait for a break condition on the RX line.
/// Condition is met when the receiver detects a NULL character (i.e. logic
/// 0 for one NULL character transmission) after stop bits.
pub async fn wait_for_break_async(&mut self) {
UartRxFuture::new(self.uart.reborrow(), RxEvent::BreakDetected).await;
}
}

#[cfg(any(doc, feature = "unstable"))]
Expand Down Expand Up @@ -1815,6 +1860,7 @@ pub(super) fn intr_handler(uart: &Info, state: &State) {
let rx_wake = interrupts.rxfifo_full().bit_is_set()
|| interrupts.rxfifo_ovf().bit_is_set()
|| interrupts.rxfifo_tout().bit_is_set()
|| interrupts.brk_det().bit_is_set()
|| interrupts.at_cmd_char_det().bit_is_set()
|| interrupts.glitch_det().bit_is_set()
|| interrupts.frm_err().bit_is_set()
Expand Down Expand Up @@ -2130,6 +2176,7 @@ impl Info {
UartInterrupt::AtCmd => w.at_cmd_char_det().bit(enable),
UartInterrupt::TxDone => w.tx_done().bit(enable),
UartInterrupt::RxFifoFull => w.rxfifo_full().bit(enable),
UartInterrupt::RxBreakDetected => w.brk_det().bit(enable),
};
}
w
Expand All @@ -2151,6 +2198,9 @@ impl Info {
if ints.rxfifo_full().bit_is_set() {
res.insert(UartInterrupt::RxFifoFull);
}
if ints.brk_det().bit_is_set() {
res.insert(UartInterrupt::RxBreakDetected);
}

res
}
Expand All @@ -2164,6 +2214,7 @@ impl Info {
UartInterrupt::AtCmd => w.at_cmd_char_det().clear_bit_by_one(),
UartInterrupt::TxDone => w.tx_done().clear_bit_by_one(),
UartInterrupt::RxFifoFull => w.rxfifo_full().clear_bit_by_one(),
UartInterrupt::RxBreakDetected => w.brk_det().clear_bit_by_one(),
};
}
w
Expand Down Expand Up @@ -2202,6 +2253,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 @@ -2222,6 +2274,7 @@ impl Info {
for event in events {
let event_triggered = match event {
RxEvent::FifoFull => interrupts_enabled.rxfifo_full().bit_is_clear(),
RxEvent::BreakDetected => interrupts_enabled.brk_det().bit_is_clear(),
RxEvent::CmdCharDetected => interrupts_enabled.at_cmd_char_det().bit_is_clear(),

RxEvent::FifoOvf => interrupts_enabled.rxfifo_ovf().bit_is_clear(),
Expand All @@ -2244,6 +2297,7 @@ impl Info {
for event in events {
let event_triggered = match event {
RxEvent::FifoFull => interrupts_enabled.rxfifo_full().bit_is_set(),
RxEvent::BreakDetected => interrupts_enabled.brk_det().bit_is_set(),
RxEvent::CmdCharDetected => interrupts_enabled.at_cmd_char_det().bit_is_set(),

RxEvent::FifoOvf => interrupts_enabled.rxfifo_ovf().bit_is_set(),
Expand All @@ -2265,6 +2319,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
36 changes: 36 additions & 0 deletions examples/src/bin/uart_break_detection.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//! Blocking UART break detection example.
//!
//! The following wiring is assumed:
//! - TX => GPIO17
//! - RX => GPIO16

//% CHIPS: esp32

#![no_std]
#![no_main]

use esp_backtrace as _;
use esp_hal::{
main,
uart::{Config as UartConfig, DataBits, Parity, RxConfig, 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)
.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_tx(peripherals.GPIO17);

loop {
uart.wait_for_break();
esp_println::print!("\nBREAK");
}
}
36 changes: 36 additions & 0 deletions examples/src/bin/uart_break_detection_async.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//! Async UART break detection example.
//!
//! The following wiring is assumed:
//! - TX => GPIO17
//! - RX => GPIO16

//% CHIPS: esp32
//% FEATURES: embassy esp-hal/unstable

#![no_std]
#![no_main]

use embassy_executor::Spawner;
use esp_backtrace as _;
use esp_hal::uart::{Config as UartConfig, DataBits, Parity, RxConfig, StopBits, Uart};

#[esp_hal_embassy::main]
async fn main(_spawner: Spawner) {
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)
.with_tx(peripherals.GPIO17)
.into_async();

loop {
uart.wait_for_break_async().await;
esp_println::print!("\nBREAK");
}
}
69 changes: 69 additions & 0 deletions examples/src/bin/uart_interrupts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//! Example of responding to UART interrupts.
//!
//! The following wiring is assumed:
//! - TX => GPIO17
//! - RX => GPIO16

//% CHIPS: esp32

#![no_std]
#![no_main]

use core::cell::RefCell;

use critical_section::Mutex;
use esp_backtrace as _;
use esp_hal::{
handler,
main,
ram,
uart::{Config as UartConfig, DataBits, Parity, RxConfig, StopBits, Uart, UartInterrupt},
Blocking,
};

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)
.with_tx(peripherals.GPIO17);

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_bytes(&mut byte).unwrap();
esp_println::print!(" {:02X}", byte[0]);
}

serial.clear_interrupts(UartInterrupt::RxBreakDetected | UartInterrupt::RxFifoFull);
});
}
5 changes: 5 additions & 0 deletions hil-test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,11 @@ name = "uart_async"
harness = false
required-features = ["embassy"]

[[test]]
name = "uart_brk_det"
harness = false
required-features = ["embassy"]

[[test]]
name = "uart_regression"
harness = false
Expand Down
49 changes: 49 additions & 0 deletions hil-test/tests/uart_brk_det.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//! UART Break Detection test

//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: embassy

#![no_std]
#![no_main]

use esp_hal::{
uart::{Config as UartConfig, Uart},
Blocking,
};
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 (_, pin) = hil_test::common_test_pins!(peripherals);
let (rx, tx) = pin.split();
let uart = Uart::new(peripherals.UART1, UartConfig::default())
.expect("Failed to initialize UART")
.with_rx(rx)
.with_tx(tx);

Context { uart }
}

#[test]
fn test_wait_for_break_blocking(mut ctx: Context) {
// TODO: Send (or simulate) a break signal, otherwise this will fail via timeout
ctx.uart.wait_for_break();
}

#[test]
async fn test_wait_for_break_async(ctx: Context) {
// TODO: Send (or simulate) a break signal, otherwise this will fail via timeout
ctx.uart.into_async().wait_for_break_async().await;
}
}
Loading