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 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -50,6 +50,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `gpio::{Level, Pull, AlternateFunction, RtcFunction}` now implement `Hash` (#2842)
- `gpio::{GpioPin, AnyPin, Io, Output, OutputOpenDrain, Input, Flex}` now implement `Debug`, `defmt::Format` (#2842)
- More interrupts are available in `esp_hal::spi::master::SpiInterrupt`, add `enable_listen`,`interrupts` and `clear_interrupts` for ESP32/ESP32-S2 (#2833)
- Additional interrupt available in `esp_hal::uart::UartInterrupt` - as well as UartRx functions `wait_for_break()` and `wait_for_break_async().await` (#2858)

- The `ExtU64` and `RateExtU32` traits have been added to `esp_hal::time` (#2845)

Expand Down
61 changes: 59 additions & 2 deletions esp-hal/src/uart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -972,6 +972,27 @@ where
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.register_block()
.int_ena()
.write(|w| w.brk_det().bit(true));

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

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

#[allow(clippy::useless_conversion)]
fn rx_fifo_count(&self) -> u16 {
let fifo_cnt: u16 = self
Expand Down Expand Up @@ -1157,6 +1178,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
/// [`Config::rx_fifo_full_threshold`] specifies.
RxFifoFull,
Expand Down Expand Up @@ -1257,6 +1283,11 @@ where
self.rx.read_byte()
}

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

/// Change the configuration.
pub fn apply_config(&mut self, config: &Config) -> Result<(), ConfigError> {
self.rx.apply_config(config)?;
Expand Down Expand Up @@ -1613,6 +1644,7 @@ pub(crate) enum TxEvent {
pub(crate) enum RxEvent {
FifoFull,
CmdCharDetected,
BreakDetected,
FifoOvf,
FifoTout,
GlitchDetected,
Expand Down Expand Up @@ -1651,6 +1683,7 @@ impl UartRxFuture {
let event_triggered = match event {
RxEvent::FifoFull => interrupts_enabled.rxfifo_full().bit_is_clear(),
RxEvent::CmdCharDetected => interrupts_enabled.at_cmd_char_det().bit_is_clear(),
RxEvent::BreakDetected => interrupts_enabled.brk_det().bit_is_clear(),

RxEvent::FifoOvf => interrupts_enabled.rxfifo_ovf().bit_is_clear(),
RxEvent::FifoTout => interrupts_enabled.rxfifo_tout().bit_is_clear(),
Expand All @@ -1671,6 +1704,7 @@ impl UartRxFuture {
match event {
RxEvent::FifoFull => w.rxfifo_full().bit(enable),
RxEvent::CmdCharDetected => w.at_cmd_char_det().bit(enable),
RxEvent::BreakDetected => w.brk_det().bit(enable),
RxEvent::FifoOvf => w.rxfifo_ovf().bit(enable),
RxEvent::FifoTout => w.rxfifo_tout().bit(enable),
RxEvent::GlitchDetected => w.glitch_det().bit(enable),
Expand Down Expand Up @@ -1806,6 +1840,13 @@ where
pub async fn flush_async(&mut self) -> Result<(), Error> {
self.tx.flush_async().await
}

/// Asynchronously 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.
pub async fn wait_for_break_async(&mut self) {
self.rx.wait_for_break_async().await;
}
}

impl<T> UartTx<'_, Async, T>
Expand Down Expand Up @@ -1920,7 +1961,10 @@ where
RxEvent::GlitchDetected => return Err(Error::RxGlitchDetected),
RxEvent::FrameError => return Err(Error::RxFrameError),
RxEvent::ParityError => return Err(Error::RxParityError),
RxEvent::FifoFull | RxEvent::CmdCharDetected | RxEvent::FifoTout => continue,
RxEvent::FifoFull
| RxEvent::CmdCharDetected
| RxEvent::BreakDetected
| RxEvent::FifoTout => continue,
}
}
// Unfortunately, the uart's rx-timeout counter counts up whenever there is
Expand All @@ -1936,6 +1980,13 @@ where
}
}
}

/// 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 @@ -2009,7 +2060,8 @@ pub(super) fn intr_handler(uart: &Info, state: &State) {
|| interrupts.at_cmd_char_det().bit_is_set()
|| interrupts.glitch_det().bit_is_set()
|| interrupts.frm_err().bit_is_set()
|| interrupts.parity_err().bit_is_set();
|| interrupts.parity_err().bit_is_set()
|| interrupts.brk_det().bit_is_set();
let tx_wake = interrupts.tx_done().bit_is_set() || interrupts.txfifo_empty().bit_is_set();
uart.register_block()
.int_clr()
Expand Down Expand Up @@ -2300,6 +2352,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 @@ -2321,6 +2374,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 @@ -2334,6 +2390,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
43 changes: 43 additions & 0 deletions examples/src/bin/uart_break_detection.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//! 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::{
entry,
uart::{Config as UartConfig, DataBits, StopBits, Uart},
};

#[entry]
fn main() -> ! {
let peripherals = esp_hal::init(esp_hal::Config::default());
let uart_config = UartConfig::default()
.baudrate(19200)
.data_bits(DataBits::DataBits8)
.parity_none()
.stop_bits(StopBits::Stop1)
.rx_fifo_full_threshold(1); // interrupt every time a byte is received
let mut uart = Uart::new(
peripherals.UART1,
uart_config,
peripherals.GPIO16, // RX
peripherals.GPIO17, // TX
)
.expect("Failed to initialize UART");

loop {
uart.wait_for_break();
esp_println::print!("\nBREAK");

while let Ok(byte) = uart.read_byte() {
esp_println::print!(" {:02X}", byte);
}
}
}
45 changes: 45 additions & 0 deletions examples/src/bin/uart_break_detection_async.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//! Async UART break detection example.
//!
//! The following wiring is assumed:
//! - TX => GPIO17
//! - RX => GPIO16

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

#![no_std]
#![no_main]

use embassy_executor::Spawner;
use esp_backtrace as _;
use esp_hal::uart::{Config as UartConfig, DataBits, 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()
.baudrate(19200)
.data_bits(DataBits::DataBits8)
.parity_none()
.stop_bits(StopBits::Stop1)
.rx_fifo_full_threshold(1); // interrupt every time a byte is received
let mut uart = Uart::new(
peripherals.UART1,
uart_config,
peripherals.GPIO16, // RX
peripherals.GPIO17, // TX
)
.expect("Failed to initialize UART")
.into_async();

loop {
uart.wait_for_break_async().await;
esp_println::print!("\nBREAK");

let mut buf = [0u8; 11];
let len = uart.read_async(&mut buf).await.unwrap();
for byte in buf.iter().take(len) {
esp_println::print!(" {:02X}", byte);
}
}
}
70 changes: 70 additions & 0 deletions examples/src/bin/uart_interrupts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//! 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::{
entry,
interrupt::InterruptConfigurable,
macros::{handler, ram},
uart::{Config as UartConfig, DataBits, StopBits, Uart, UartInterrupt},
Blocking,
};

static SERIAL: Mutex<RefCell<Option<Uart<Blocking>>>> = Mutex::new(RefCell::new(None));

#[entry]
fn main() -> ! {
let peripherals = esp_hal::init(esp_hal::Config::default());
let uart_config = UartConfig::default()
.baudrate(19200)
.data_bits(DataBits::DataBits8)
.parity_none()
.stop_bits(StopBits::Stop1)
.rx_fifo_full_threshold(1); // interrupt every time a byte is received
let mut uart = Uart::new(
peripherals.UART1,
uart_config,
peripherals.GPIO16, // RX
peripherals.GPIO17, // TX
)
.expect("Failed to initialize UART");

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) {
esp_println::print!(" {:02X}", serial.read_byte().expect("Read byte failed"));
}

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 @@ -152,6 +152,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
45 changes: 45 additions & 0 deletions hil-test/tests/uart_brk_det.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//! UART Break Detection test

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

#![no_std]
#![no_main]

use esp_hal::{
uart::{self, 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, uart::Config::default(), rx, tx).unwrap();

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