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 36 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 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
ef343f3
fix: managing merge conflicts
zpg6 Feb 25, 2025
ed711b8
fix: fmt
zpg6 Feb 25, 2025
a05264e
fix: revert uart.rs
zpg6 Feb 25, 2025
aa29c3a
fix: fmt
zpg6 Feb 25, 2025
4962299
Merge branch 'main' into feat/uart/break-detection-interrupt
zpg6 Feb 25, 2025
351ea0e
fix: repush to match with main
zpg6 Feb 25, 2025
8d01053
fix: `.rx`
zpg6 Feb 25, 2025
bd3d1aa
fix: fmt
zpg6 Feb 25, 2025
a710fcc
chore: cleanup examples
zpg6 Feb 25, 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: 1 addition & 1 deletion esp-hal/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -1174,4 +1174,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
77 changes: 58 additions & 19 deletions esp-hal/src/uart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -904,6 +904,24 @@ where
Ok(to_read)
}

/// 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.
#[instability::unstable]
pub fn wait_for_break(&mut self) {
Copy link
Contributor

Choose a reason for hiding this comment

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

  • Why do you need the int_ena bit to be set?
  • You shouldn't clear int_ena at the end of the function without actually checking if it was cleared before
  • This function can end up hanging if the user has an interrupt handled that clears the brk_det bit. I know it would be their own fault but we should try our best to minimize the change of bugs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

  • Why do you need the int_ena bit to be set?
    • I am operating under the assumption that the int_raw only functions if int_ena is set. Is this incorrect?
  • You shouldn't clear int_ena at the end of the function without actually checking if it was cleared before
    • Sorry, do you mean int_clr?
  • This function can end up hanging if the user has an interrupt handled that clears the brk_det bit. I know it would be their own fault but we should try our best to minimize the change of bugs.
    • Makes sense

Copy link
Contributor

Choose a reason for hiding this comment

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

Is this incorrect?

No, as far as I know only int_st and the actual interrupt handling is affected, where int_st = int_raw & int_ena. Usually.

Sorry, do you mean int_clr?

Hmm yeah disregard that point, I saw something else, not what you wrote. But in that case you should disable listening for the interrupt :)

// 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));
}

#[allow(clippy::useless_conversion)]
fn rx_fifo_count(&self) -> u16 {
let fifo_cnt: u16 = self.regs().status().read().rxfifo_cnt().bits().into();
Expand Down Expand Up @@ -1101,6 +1119,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 @@ -1273,6 +1296,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) -> Result<(), TxError> {
self.tx.flush()
Expand Down Expand Up @@ -1683,6 +1711,7 @@ pub(crate) enum TxEvent {
#[derive(Debug, EnumSetType)]
pub(crate) enum RxEvent {
FifoFull,
BreakDetected,
CmdCharDetected,
FifoOvf,
FifoTout,
Expand All @@ -1698,7 +1727,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::BreakDetected
| RxEvent::CmdCharDetected
| RxEvent::FifoTout => continue,
}
}

Expand Down Expand Up @@ -1835,23 +1867,11 @@ impl Uart<'_, Async> {
self.rx.read_async(buf).await
}

/// Fill buffer asynchronously.
///
/// This function reads data from the UART receive buffer into the
/// provided buffer. If the buffer is empty, the function waits
/// asynchronously for data to become available, or for an error to occur.
///
/// Note that this function may ignore the `rx_fifo_full_threshold` setting
/// to ensure that it does not wait for more data than the buffer can hold.
///
/// ## Cancellation
///
/// This function is **not** cancellation safe. If the future is dropped
/// before it resolves, or if an error occurs during the read operation,
/// previously read data may be lost.
#[instability::unstable]
pub async fn read_exact_async(&mut self, buf: &mut [u8]) -> Result<(), RxError> {
self.rx.read_exact_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;
}

/// Write data into the TX buffer.
Expand Down Expand Up @@ -1971,6 +1991,7 @@ impl UartRx<'_, Async> {

// Wait for space or event
let mut events = RxEvent::FifoFull
| RxEvent::BreakDetected
| RxEvent::FifoOvf
| RxEvent::FrameError
| RxEvent::GlitchDetected
Expand Down Expand Up @@ -2054,6 +2075,13 @@ impl UartRx<'_, Async> {

Ok(())
}

/// 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;
}
}

#[instability::unstable]
Expand All @@ -2063,7 +2091,8 @@ impl embedded_io_async::Read for Uart<'_, Async> {
}

async fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), ReadExactError<Self::Error>> {
self.read_exact_async(buf)
self.rx
.read_exact_async(buf)
.await
.map_err(|e| ReadExactError::Other(IoError::Rx(e)))
}
Expand Down Expand Up @@ -2444,6 +2473,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 @@ -2465,6 +2495,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 @@ -2478,6 +2511,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 @@ -2552,6 +2586,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 @@ -2572,6 +2607,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 @@ -2600,6 +2638,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
34 changes: 34 additions & 0 deletions examples/src/bin/uart_break_detection.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//! Blocking UART break detection example.
//!
//! The following wiring is assumed:
//! - RX => GPIO16

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

#![no_std]
#![no_main]

use esp_backtrace as _;
use esp_hal::{
main,
uart::{Config as UartConfig, DataBits, Parity, 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);
let mut uart = Uart::new(peripherals.UART1, uart_config)
.expect("Failed to initialize UART")
.with_rx(peripherals.GPIO16);

loop {
uart.wait_for_break();
esp_println::print!("\nBREAK");
}
}
33 changes: 33 additions & 0 deletions examples/src/bin/uart_break_detection_async.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//! Async UART break detection example.
//!
//! The following wiring is assumed:
//! - 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, 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);
let mut uart = Uart::new(peripherals.UART1, uart_config)
.expect("Failed to initialize UART")
.with_rx(peripherals.GPIO16)
.into_async();

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

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

#![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);

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);
});
}
5 changes: 5 additions & 0 deletions hil-test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,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