diff --git a/embassy-hal-internal/src/atomic_ring_buffer.rs b/embassy-hal-internal/src/atomic_ring_buffer.rs index 00b7a12497..8c3889b855 100644 --- a/embassy-hal-internal/src/atomic_ring_buffer.rs +++ b/embassy-hal-internal/src/atomic_ring_buffer.rs @@ -133,6 +133,18 @@ impl RingBuffer { self.len.load(Ordering::Relaxed) } + /// Return number of items available to read. + pub fn available(&self) -> usize { + let end = self.end.load(Ordering::Relaxed); + let len = self.len.load(Ordering::Relaxed); + let start = self.start.load(Ordering::Relaxed); + if end >= start { + end - start + } else { + 2 * len - start + end + } + } + /// Check if buffer is full. pub fn is_full(&self) -> bool { let len = self.len.load(Ordering::Relaxed); @@ -142,6 +154,11 @@ impl RingBuffer { self.wrap(start + len) == end } + /// Check if buffer is at least half full. + pub fn is_half_full(&self) -> bool { + self.available() >= self.len.load(Ordering::Relaxed) / 2 + } + /// Check if buffer is empty. pub fn is_empty(&self) -> bool { let start = self.start.load(Ordering::Relaxed); @@ -394,6 +411,7 @@ mod tests { rb.init(b.as_mut_ptr(), 4); assert_eq!(rb.is_empty(), true); + assert_eq!(rb.is_half_full(), false); assert_eq!(rb.is_full(), false); rb.writer().push(|buf| { @@ -406,6 +424,7 @@ mod tests { }); assert_eq!(rb.is_empty(), false); + assert_eq!(rb.is_half_full(), true); assert_eq!(rb.is_full(), true); rb.writer().push(|buf| { @@ -415,6 +434,7 @@ mod tests { }); assert_eq!(rb.is_empty(), false); + assert_eq!(rb.is_half_full(), true); assert_eq!(rb.is_full(), true); rb.reader().pop(|buf| { @@ -424,6 +444,7 @@ mod tests { }); assert_eq!(rb.is_empty(), false); + assert_eq!(rb.is_half_full(), true); assert_eq!(rb.is_full(), false); rb.reader().pop(|buf| { @@ -432,6 +453,7 @@ mod tests { }); assert_eq!(rb.is_empty(), false); + assert_eq!(rb.is_half_full(), true); assert_eq!(rb.is_full(), false); rb.reader().pop(|buf| { @@ -447,6 +469,7 @@ mod tests { }); assert_eq!(rb.is_empty(), true); + assert_eq!(rb.is_half_full(), false); assert_eq!(rb.is_full(), false); rb.reader().pop(|buf| { @@ -460,14 +483,28 @@ mod tests { 1 }); + assert_eq!(rb.is_empty(), false); + assert_eq!(rb.is_half_full(), false); + assert_eq!(rb.is_full(), false); + rb.writer().push(|buf| { assert_eq!(3, buf.len()); buf[0] = 11; - buf[1] = 12; - 2 + 1 + }); + + assert_eq!(rb.is_empty(), false); + assert_eq!(rb.is_half_full(), true); + assert_eq!(rb.is_full(), false); + + rb.writer().push(|buf| { + assert_eq!(2, buf.len()); + buf[0] = 12; + 1 }); assert_eq!(rb.is_empty(), false); + assert_eq!(rb.is_half_full(), true); assert_eq!(rb.is_full(), false); rb.writer().push(|buf| { @@ -477,6 +514,7 @@ mod tests { }); assert_eq!(rb.is_empty(), false); + assert_eq!(rb.is_half_full(), true); assert_eq!(rb.is_full(), true); } } @@ -490,6 +528,7 @@ mod tests { rb.init(b.as_mut_ptr(), b.len()); assert_eq!(rb.is_empty(), true); + assert_eq!(rb.is_half_full(), true); assert_eq!(rb.is_full(), true); rb.writer().push(|buf| { diff --git a/embassy-stm32/CHANGELOG.md b/embassy-stm32/CHANGELOG.md index 1443472f58..a6ee5c4b81 100644 --- a/embassy-stm32/CHANGELOG.md +++ b/embassy-stm32/CHANGELOG.md @@ -27,6 +27,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - fix: Cut down the capabilities of the STM32L412 and L422 RTC as those are missing binary timer mode and underflow interrupt. - fix: Allow configuration of the internal pull up/down resistors on the pins for the Qei peripheral, as well as the Qei decoder mode. - feat: stm32/rcc/mco: Added support for IO driver strength when using Master Clock Out IO. This changes signature on Mco::new taking a McoConfig struct ([#4679](https://github.com/embassy-rs/embassy/pull/4679)) +- feat: derive Clone, Copy and defmt::Format for all SPI-related configs +- feat: stm32/usart: add `eager_reads` option to control if buffered readers return as soon as possible or after more data is available ([#4668](https://github.com/embassy-rs/embassy/pull/4668)) +- feat: stm32/usart: add `de_assertion_time` and `de_deassertion_time` config options +- change: stm32/uart: BufferedUartRx now returns all available bytes from the internal buffer ## 0.4.0 - 2025-08-26 diff --git a/embassy-stm32/src/usart/buffered.rs b/embassy-stm32/src/usart/buffered.rs index c734eed490..10dc02334e 100644 --- a/embassy-stm32/src/usart/buffered.rs +++ b/embassy-stm32/src/usart/buffered.rs @@ -1,7 +1,7 @@ use core::future::poll_fn; use core::marker::PhantomData; use core::slice; -use core::sync::atomic::{AtomicBool, AtomicU8, Ordering}; +use core::sync::atomic::{AtomicBool, AtomicU8, AtomicUsize, Ordering}; use core::task::Poll; use embassy_embedded_hal::SetConfig; @@ -68,8 +68,15 @@ unsafe fn on_interrupt(r: Regs, state: &'static State) { // FIXME: Should we disable any further RX interrupts when the buffer becomes full. } - if !state.rx_buf.is_empty() { - state.rx_waker.wake(); + let eager = state.eager_reads.load(Ordering::Relaxed); + if eager > 0 { + if state.rx_buf.available() >= eager { + state.rx_waker.wake(); + } + } else { + if state.rx_buf.is_half_full() { + state.rx_waker.wake(); + } } } @@ -132,6 +139,7 @@ pub(super) struct State { tx_done: AtomicBool, tx_rx_refcount: AtomicU8, half_duplex_readback: AtomicBool, + eager_reads: AtomicUsize, } impl State { @@ -144,6 +152,7 @@ impl State { tx_done: AtomicBool::new(true), tx_rx_refcount: AtomicU8::new(0), half_duplex_readback: AtomicBool::new(false), + eager_reads: AtomicUsize::new(0), } } } @@ -419,6 +428,9 @@ impl<'d> BufferedUart<'d> { let state = T::buffered_state(); let kernel_clock = T::frequency(); + state + .eager_reads + .store(config.eager_reads.unwrap_or(0), Ordering::Relaxed); state.half_duplex_readback.store( config.duplex == Duplex::Half(HalfDuplexReadback::Readback), Ordering::Relaxed, @@ -456,6 +468,9 @@ impl<'d> BufferedUart<'d> { let info = self.rx.info; let state = self.rx.state; state.tx_rx_refcount.store(2, Ordering::Relaxed); + state + .eager_reads + .store(config.eager_reads.unwrap_or(0), Ordering::Relaxed); info.rcc.enable_and_reset(); @@ -527,6 +542,11 @@ impl<'d> BufferedUart<'d> { pub fn set_config(&mut self, config: &Config) -> Result<(), ConfigError> { reconfigure(self.rx.info, self.rx.kernel_clock, config)?; + self.rx + .state + .eager_reads + .store(config.eager_reads.unwrap_or(0), Ordering::Relaxed); + self.rx.info.regs.cr1().modify(|w| { w.set_rxneie(true); w.set_idleie(true); @@ -553,24 +573,30 @@ impl<'d> BufferedUartRx<'d> { poll_fn(move |cx| { let state = self.state; let mut rx_reader = unsafe { state.rx_buf.reader() }; - let data = rx_reader.pop_slice(); + let mut buf_len = 0; + let mut data = rx_reader.pop_slice(); - if !data.is_empty() { - let len = data.len().min(buf.len()); - buf[..len].copy_from_slice(&data[..len]); + while !data.is_empty() && buf_len < buf.len() { + let data_len = data.len().min(buf.len() - buf_len); + buf[buf_len..buf_len + data_len].copy_from_slice(&data[..data_len]); + buf_len += data_len; let do_pend = state.rx_buf.is_full(); - rx_reader.pop_done(len); + rx_reader.pop_done(data_len); if do_pend { self.info.interrupt.pend(); } - return Poll::Ready(Ok(len)); + data = rx_reader.pop_slice(); } - state.rx_waker.register(cx.waker()); - Poll::Pending + if buf_len != 0 { + Poll::Ready(Ok(buf_len)) + } else { + state.rx_waker.register(cx.waker()); + Poll::Pending + } }) .await } @@ -579,21 +605,24 @@ impl<'d> BufferedUartRx<'d> { loop { let state = self.state; let mut rx_reader = unsafe { state.rx_buf.reader() }; - let data = rx_reader.pop_slice(); + let mut buf_len = 0; + let mut data = rx_reader.pop_slice(); - if !data.is_empty() { - let len = data.len().min(buf.len()); - buf[..len].copy_from_slice(&data[..len]); + while !data.is_empty() && buf_len < buf.len() { + let data_len = data.len().min(buf.len() - buf_len); + buf[buf_len..buf_len + data_len].copy_from_slice(&data[..data_len]); + buf_len += data_len; let do_pend = state.rx_buf.is_full(); - rx_reader.pop_done(len); + rx_reader.pop_done(data_len); if do_pend { self.info.interrupt.pend(); } - return Ok(len); + data = rx_reader.pop_slice(); } + return Ok(buf_len); } } @@ -633,6 +662,10 @@ impl<'d> BufferedUartRx<'d> { pub fn set_config(&mut self, config: &Config) -> Result<(), ConfigError> { reconfigure(self.info, self.kernel_clock, config)?; + self.state + .eager_reads + .store(config.eager_reads.unwrap_or(0), Ordering::Relaxed); + self.info.regs.cr1().modify(|w| { w.set_rxneie(true); w.set_idleie(true); diff --git a/embassy-stm32/src/usart/mod.rs b/embassy-stm32/src/usart/mod.rs index ff211e0c99..0d2d86acac 100644 --- a/embassy-stm32/src/usart/mod.rs +++ b/embassy-stm32/src/usart/mod.rs @@ -4,7 +4,7 @@ use core::future::poll_fn; use core::marker::PhantomData; -use core::sync::atomic::{compiler_fence, AtomicU8, Ordering}; +use core::sync::atomic::{compiler_fence, AtomicU8, AtomicUsize, Ordering}; use core::task::Poll; use embassy_embedded_hal::SetConfig; @@ -185,6 +185,12 @@ pub enum ConfigError { RxOrTxNotEnabled, /// Data bits and parity combination not supported DataParityNotSupported, + /// DE assertion time too high + #[cfg(not(any(usart_v1, usart_v2)))] + DeAssertionTimeTooHigh, + /// DE deassertion time too high + #[cfg(not(any(usart_v1, usart_v2)))] + DeDeassertionTimeTooHigh, } #[non_exhaustive] @@ -206,6 +212,21 @@ pub struct Config { /// If false: the error is ignored and cleared pub detect_previous_overrun: bool, + /// If `None` (the default) then read-like calls on `BufferedUartRx` and `RingBufferedUartRx` + /// typically only wake/return after line idle or after the buffer is at least half full + /// (for `BufferedUartRx`) or the DMA buffer is written at the half or full positions + /// (for `RingBufferedUartRx`), though it may also wake/return earlier in some circumstances. + /// + /// If `Some(n)` then such reads are also woken/return as soon as at least `n` words are + /// available in the buffer, in addition to waking/returning when the conditions described + /// above are met. `Some(0)` is treated as `None`. Setting this for `RingBufferedUartRx` + /// will trigger an interrupt for every received word to check the buffer level, which may + /// impact performance at high data rates. + /// + /// Has no effect on plain `Uart` or `UartRx` reads, which are specified to either + /// return a single word, a full buffer, or after line idle. + pub eager_reads: Option, + /// Set this to true if the line is considered noise free. /// This will increase the receiver’s tolerance to clock deviations, /// but will effectively disable noise detection. @@ -239,6 +260,14 @@ pub struct Config { /// Set the pin configuration for the DE pin. pub de_config: OutputConfig, + /// Set DE assertion time before the first start bit, 0-31 16ths of a bit period. + #[cfg(not(any(usart_v1, usart_v2)))] + pub de_assertion_time: u8, + + /// Set DE deassertion time after the last stop bit, 0-31 16ths of a bit period. + #[cfg(not(any(usart_v1, usart_v2)))] + pub de_deassertion_time: u8, + // private: set by new_half_duplex, not by the user. duplex: Duplex, } @@ -270,6 +299,7 @@ impl Default for Config { parity: Parity::ParityNone, // historical behavior detect_previous_overrun: false, + eager_reads: None, #[cfg(not(usart_v1))] assume_noise_free: false, #[cfg(any(usart_v3, usart_v4))] @@ -283,6 +313,10 @@ impl Default for Config { tx_config: OutputConfig::PushPull, rts_config: OutputConfig::PushPull, de_config: OutputConfig::PushPull, + #[cfg(not(any(usart_v1, usart_v2)))] + de_assertion_time: 0, + #[cfg(not(any(usart_v1, usart_v2)))] + de_deassertion_time: 0, duplex: Duplex::Full, } } @@ -966,6 +1000,9 @@ impl<'d, M: Mode> UartRx<'d, M> { let info = self.info; let state = self.state; state.tx_rx_refcount.store(1, Ordering::Relaxed); + state + .eager_reads + .store(config.eager_reads.unwrap_or(0), Ordering::Relaxed); info.rcc.enable_and_reset(); @@ -982,6 +1019,9 @@ impl<'d, M: Mode> UartRx<'d, M> { /// Reconfigure the driver pub fn set_config(&mut self, config: &Config) -> Result<(), ConfigError> { + self.state + .eager_reads + .store(config.eager_reads.unwrap_or(0), Ordering::Relaxed); reconfigure(self.info, self.kernel_clock, config) } @@ -1462,6 +1502,9 @@ impl<'d, M: Mode> Uart<'d, M> { let info = self.rx.info; let state = self.rx.state; state.tx_rx_refcount.store(2, Ordering::Relaxed); + state + .eager_reads + .store(config.eager_reads.unwrap_or(0), Ordering::Relaxed); info.rcc.enable_and_reset(); @@ -1690,6 +1733,16 @@ fn configure( return Err(ConfigError::RxOrTxNotEnabled); } + #[cfg(not(any(usart_v1, usart_v2)))] + let dem = r.cr3().read().dem(); + + #[cfg(not(any(usart_v1, usart_v2)))] + if config.de_assertion_time > 31 { + return Err(ConfigError::DeAssertionTimeTooHigh); + } else if config.de_deassertion_time > 31 { + return Err(ConfigError::DeDeassertionTimeTooHigh); + } + // UART must be disabled during configuration. r.cr1().modify(|w| { w.set_ue(false); @@ -1738,6 +1791,20 @@ fn configure( w.set_re(enable_rx); } + #[cfg(not(any(usart_v1, usart_v2)))] + if dem { + w.set_deat(if over8 { + config.de_assertion_time / 2 + } else { + config.de_assertion_time + }); + w.set_dedt(if over8 { + config.de_assertion_time / 2 + } else { + config.de_assertion_time + }); + } + // configure word size and parity, since the parity bit is inserted into the MSB position, // it increases the effective word size match (config.parity, config.data_bits) { @@ -2022,6 +2089,7 @@ struct State { rx_waker: AtomicWaker, tx_waker: AtomicWaker, tx_rx_refcount: AtomicU8, + eager_reads: AtomicUsize, } impl State { @@ -2030,6 +2098,7 @@ impl State { rx_waker: AtomicWaker::new(), tx_waker: AtomicWaker::new(), tx_rx_refcount: AtomicU8::new(0), + eager_reads: AtomicUsize::new(0), } } } diff --git a/embassy-stm32/src/usart/ringbuffered.rs b/embassy-stm32/src/usart/ringbuffered.rs index 5f4e87834e..27071fb313 100644 --- a/embassy-stm32/src/usart/ringbuffered.rs +++ b/embassy-stm32/src/usart/ringbuffered.rs @@ -26,9 +26,9 @@ use crate::Peri; /// contain enough bytes to fill the buffer passed by the caller of /// the function, or is empty. /// -/// Waiting for bytes operates in one of two modes, depending on -/// the behavior of the sender and the size of the buffer passed -/// to the function: +/// Waiting for bytes operates in one of three modes, depending on +/// the behavior of the sender, the size of the buffer passed +/// to the function, and the configuration: /// /// - If the sender sends intermittently, the 'idle line' /// condition will be detected when the sender stops, and any @@ -47,7 +47,11 @@ use crate::Peri; /// interrupt when those specific buffer addresses have been /// written. /// -/// In both cases this will result in variable latency due to the +/// - If `eager_reads` is enabled in `config`, the UART interrupt +/// is enabled on all data reception and the call will only wait +/// for at least one byte to be available before returning. +/// +/// In the first two cases this will result in variable latency due to the /// buffering effect. For example, if the baudrate is 2400 bps, and /// the configuration is 8 data bits, no parity bit, and one stop bit, /// then a byte will be received every ~4.16ms. If the ring buffer is @@ -68,15 +72,10 @@ use crate::Peri; /// sending, but would be falsely triggered in the worst-case /// buffer delay scenario. /// -/// Note: This latency is caused by the limited capabilities of the -/// STM32 DMA controller; since it cannot generate an interrupt when -/// it stores a byte into an empty ring buffer, or in any other -/// configurable conditions, it is not possible to take notice of the -/// contents of the ring buffer more quickly without introducing -/// polling. As a result the latency can be reduced by calling the -/// read functions repeatedly with smaller buffers to receive the -/// available bytes, as each call to a read function will explicitly -/// check the ring buffer for available bytes. +/// Note: Enabling `eager_reads` with `RingBufferedUartRx` will enable +/// an UART RXNE interrupt, which will cause an interrupt to occur on +/// every received data byte. The data is still copied using DMA, but +/// there is nevertheless additional processing overhead for each byte. pub struct RingBufferedUartRx<'d> { info: &'static Info, state: &'static State, @@ -133,6 +132,9 @@ impl<'d> UartRx<'d, Async> { impl<'d> RingBufferedUartRx<'d> { /// Reconfigure the driver pub fn set_config(&mut self, config: &Config) -> Result<(), ConfigError> { + self.state + .eager_reads + .store(config.eager_reads.unwrap_or(0), Ordering::Relaxed); reconfigure(self.info, self.kernel_clock, config) } @@ -148,8 +150,8 @@ impl<'d> RingBufferedUartRx<'d> { let r = self.info.regs; // clear all interrupts and DMA Rx Request r.cr1().modify(|w| { - // disable RXNE interrupt - w.set_rxneie(false); + // use RXNE only when returning reads early + w.set_rxneie(self.state.eager_reads.load(Ordering::Relaxed) > 0); // enable parity interrupt if not ParityNone w.set_peie(w.pce()); // enable idle line interrupt @@ -248,39 +250,67 @@ impl<'d> RingBufferedUartRx<'d> { async fn wait_for_data_or_idle(&mut self) -> Result<(), Error> { compiler_fence(Ordering::SeqCst); - // Future which completes when idle line is detected - let s = self.state; - let uart = poll_fn(|cx| { - s.rx_waker.register(cx.waker()); - - compiler_fence(Ordering::SeqCst); - - if check_idle_and_errors(self.info.regs)? { - // Idle line is detected - Poll::Ready(Ok(())) - } else { - Poll::Pending - } - }); + loop { + // Future which completes when idle line is detected + let s = self.state; + let mut uart_init = false; + let uart = poll_fn(|cx| { + s.rx_waker.register(cx.waker()); + + compiler_fence(Ordering::SeqCst); + + // We may have been woken by IDLE or, if eager_reads is set, by RXNE. + // However, DMA will clear RXNE, so we can't check directly, and because + // the other future borrows `ring_buf`, we can't check `len()` here either. + // Instead, return from this future and we'll check the length afterwards. + let eager = s.eager_reads.load(Ordering::Relaxed) > 0; + + let idle = check_idle_and_errors(self.info.regs)?; + if idle || (eager && uart_init) { + // Idle line is detected, or eager reads is set and some data is available. + Poll::Ready(Ok(idle)) + } else { + uart_init = true; + Poll::Pending + } + }); - let mut dma_init = false; - // Future which completes when the DMA controller indicates it - // has written to the ring buffer's middle byte, or last byte - let dma = poll_fn(|cx| { - self.ring_buf.set_waker(cx.waker()); + let mut dma_init = false; + // Future which completes when the DMA controller indicates it + // has written to the ring buffer's middle byte, or last byte + let dma = poll_fn(|cx| { + self.ring_buf.set_waker(cx.waker()); - let status = match dma_init { - false => Poll::Pending, - true => Poll::Ready(()), - }; + let status = match dma_init { + false => Poll::Pending, + true => Poll::Ready(()), + }; - dma_init = true; - status - }); + dma_init = true; + status + }); - match select(uart, dma).await { - Either::Left((result, _)) => result, - Either::Right(((), _)) => Ok(()), + match select(uart, dma).await { + // UART woke with line idle + Either::Left((Ok(true), _)) => { + return Ok(()); + } + // UART woke without idle or error: word received + Either::Left((Ok(false), _)) => { + let eager = self.state.eager_reads.load(Ordering::Relaxed); + if eager > 0 && self.ring_buf.len().unwrap_or(0) >= eager { + return Ok(()); + } else { + continue; + } + } + // UART woke with error + Either::Left((Err(e), _)) => { + return Err(e); + } + // DMA woke + Either::Right(((), _)) => return Ok(()), + } } }