From 49455792cb5406f3e2df0f3850c0d650965e6b2b Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Wed, 26 Apr 2023 10:51:23 +0200 Subject: [PATCH] Ring-buffered uart rx with one-period overrun detection --- embassy-stm32/src/dma/dma.rs | 186 +++++++- embassy-stm32/src/dma/mod.rs | 1 + embassy-stm32/src/dma/ringbuffer.rs | 433 +++++++++++++++++++ embassy-stm32/src/usart/mod.rs | 43 +- embassy-stm32/src/usart/rx_ringbuffered.rs | 286 ++++++++++++ tests/stm32/Cargo.toml | 2 + tests/stm32/src/bin/usart_rx_ringbuffered.rs | 188 ++++++++ tests/utils/Cargo.toml | 10 + tests/utils/src/bin/saturate_serial.rs | 52 +++ 9 files changed, 1177 insertions(+), 24 deletions(-) create mode 100644 embassy-stm32/src/dma/ringbuffer.rs create mode 100644 embassy-stm32/src/usart/rx_ringbuffered.rs create mode 100644 tests/stm32/src/bin/usart_rx_ringbuffered.rs create mode 100644 tests/utils/Cargo.toml create mode 100644 tests/utils/src/bin/saturate_serial.rs diff --git a/embassy-stm32/src/dma/dma.rs b/embassy-stm32/src/dma/dma.rs index ef1d2757..17d82fe2 100644 --- a/embassy-stm32/src/dma/dma.rs +++ b/embassy-stm32/src/dma/dma.rs @@ -9,6 +9,7 @@ use embassy_hal_common::{into_ref, Peripheral, PeripheralRef}; use embassy_sync::waitqueue::AtomicWaker; use pac::dma::regs; +use super::ringbuffer::{DmaCtrl, DmaRingBuffer, OverrunError}; use super::word::{Word, WordSize}; use super::Dir; use crate::_generated::DMA_CHANNEL_COUNT; @@ -445,7 +446,6 @@ impl<'a, C: Channel> Future for Transfer<'a, C> { // ================================== -#[must_use = "futures do nothing unless you `.await` or poll them"] pub struct DoubleBuffered<'a, C: Channel, W: Word> { channel: PeripheralRef<'a, C>, _phantom: PhantomData, @@ -578,15 +578,6 @@ impl<'a, C: Channel, W: Word> DoubleBuffered<'a, C, W> { let ch = self.channel.regs().st(self.channel.num()); unsafe { ch.ndtr().read() }.ndt() } - - pub fn blocking_wait(mut self) { - while self.is_running() {} - - // "Subsequent reads and writes cannot be moved ahead of preceding reads." - fence(Ordering::SeqCst); - - core::mem::forget(self); - } } impl<'a, C: Channel, W: Word> Drop for DoubleBuffered<'a, C, W> { @@ -598,3 +589,178 @@ impl<'a, C: Channel, W: Word> Drop for DoubleBuffered<'a, C, W> { fence(Ordering::SeqCst); } } + +// ============================== + +impl DmaCtrl for C { + fn tcif(&self) -> bool { + let channel_number = self.num(); + let dma = self.regs(); + let isrn = channel_number / 4; + let isrbit = channel_number % 4; + + unsafe { dma.isr(isrn).read() }.tcif(isrbit) + } + + fn clear_tcif(&mut self) { + let channel_number = self.num(); + let dma = self.regs(); + let isrn = channel_number / 4; + let isrbit = channel_number % 4; + + unsafe { + dma.ifcr(isrn).write(|w| { + w.set_tcif(isrbit, true); + }) + } + } + + fn ndtr(&self) -> usize { + let ch = self.regs().st(self.num()); + unsafe { ch.ndtr().read() }.ndt() as usize + } +} + +pub struct RingBuffer<'a, C: Channel, W: Word> { + cr: regs::Cr, + channel: PeripheralRef<'a, C>, + ringbuf: DmaRingBuffer<'a, W>, +} + +impl<'a, C: Channel, W: Word> RingBuffer<'a, C, W> { + pub unsafe fn new_read( + channel: impl Peripheral

+ 'a, + _request: Request, + peri_addr: *mut W, + buffer: &'a mut [W], + options: TransferOptions, + ) -> Self { + into_ref!(channel); + + let len = buffer.len(); + assert!(len > 0 && len <= 0xFFFF); + + let dir = Dir::PeripheralToMemory; + let data_size = W::size(); + + let channel_number = channel.num(); + let dma = channel.regs(); + + // "Preceding reads and writes cannot be moved past subsequent writes." + fence(Ordering::SeqCst); + + let mut w = regs::Cr(0); + w.set_dir(dir.into()); + w.set_msize(data_size.into()); + w.set_psize(data_size.into()); + w.set_pl(vals::Pl::VERYHIGH); + w.set_minc(vals::Inc::INCREMENTED); + w.set_pinc(vals::Inc::FIXED); + w.set_teie(true); + w.set_tcie(false); + w.set_circ(vals::Circ::ENABLED); + #[cfg(dma_v1)] + w.set_trbuff(true); + #[cfg(dma_v2)] + w.set_chsel(_request); + w.set_pburst(options.pburst.into()); + w.set_mburst(options.mburst.into()); + w.set_pfctrl(options.flow_ctrl.into()); + w.set_en(true); + + let buffer_ptr = buffer.as_mut_ptr(); + let mut this = Self { + channel, + cr: w, + ringbuf: DmaRingBuffer::new(buffer), + }; + this.clear_irqs(); + + #[cfg(dmamux)] + super::dmamux::configure_dmamux(&mut *this.channel, _request); + + let ch = dma.st(channel_number); + ch.par().write_value(peri_addr as u32); + ch.m0ar().write_value(buffer_ptr as u32); + ch.ndtr().write_value(regs::Ndtr(len as _)); + ch.fcr().write(|w| { + if let Some(fth) = options.fifo_threshold { + // FIFO mode + w.set_dmdis(vals::Dmdis::DISABLED); + w.set_fth(fth.into()); + } else { + // Direct mode + w.set_dmdis(vals::Dmdis::ENABLED); + } + }); + + this + } + + pub fn start(&mut self) { + let ch = self.channel.regs().st(self.channel.num()); + unsafe { ch.cr().write_value(self.cr) } + } + + pub fn clear(&mut self) { + self.ringbuf.clear(); + } + + /// Read bytes from the ring buffer + /// OverrunError is returned if the portion to be read was overwritten by the DMA controller. + pub fn read(&mut self, buf: &mut [W]) -> Result { + self.ringbuf.read(&mut *self.channel, buf) + } + + fn clear_irqs(&mut self) { + let channel_number = self.channel.num(); + let dma = self.channel.regs(); + let isrn = channel_number / 4; + let isrbit = channel_number % 4; + + unsafe { + dma.ifcr(isrn).write(|w| { + w.set_tcif(isrbit, true); + w.set_teif(isrbit, true); + }) + } + } + + pub fn request_stop(&mut self) { + let ch = self.channel.regs().st(self.channel.num()); + + // Disable the channel. Keep the IEs enabled so the irqs still fire. + unsafe { + ch.cr().write(|w| { + w.set_teie(true); + w.set_tcie(true); + }) + } + } + + pub fn is_running(&mut self) -> bool { + let ch = self.channel.regs().st(self.channel.num()); + unsafe { ch.cr().read() }.en() + } + + /// Gets the total remaining transfers for the channel + /// Note: this will be zero for transfers that completed without cancellation. + pub fn get_remaining_transfers(&self) -> usize { + let ch = self.channel.regs().st(self.channel.num()); + unsafe { ch.ndtr().read() }.ndt() as usize + } + + pub fn set_ndtr(&mut self, ndtr: usize) { + self.ringbuf.ndtr = ndtr; + } +} + +impl<'a, C: Channel, W: Word> Drop for RingBuffer<'a, C, W> { + fn drop(&mut self) { + self.request_stop(); + while self.is_running() {} + + // "Subsequent reads and writes cannot be moved ahead of preceding reads." + fence(Ordering::SeqCst); + } +} diff --git a/embassy-stm32/src/dma/mod.rs b/embassy-stm32/src/dma/mod.rs index 3312ca75..3ac0d1b3 100644 --- a/embassy-stm32/src/dma/mod.rs +++ b/embassy-stm32/src/dma/mod.rs @@ -21,6 +21,7 @@ pub use gpdma::*; #[cfg(dmamux)] mod dmamux; +pub(crate) mod ringbuffer; pub mod word; use core::mem; diff --git a/embassy-stm32/src/dma/ringbuffer.rs b/embassy-stm32/src/dma/ringbuffer.rs new file mode 100644 index 00000000..f9ace601 --- /dev/null +++ b/embassy-stm32/src/dma/ringbuffer.rs @@ -0,0 +1,433 @@ +use core::ops::Range; +use core::sync::atomic::{compiler_fence, Ordering}; + +use super::word::Word; + +/// A "read-only" ring-buffer to be used together with the DMA controller which +/// writes in a circular way, "uncontrolled" to the buffer. +/// +/// A snapshot of the ring buffer state can be attained by setting the `ndtr` field +/// to the current register value. `ndtr` describes the current position of the DMA +/// write. +/// +/// # Safety +/// +/// The ring buffer controls the TCIF (transfer completed interrupt flag) to +/// detect buffer overruns, hence this interrupt must be disabled. +/// The buffer can detect overruns up to one period, that is, for a X byte buffer, +/// overruns can be detected if they happen from byte X+1 up to 2X. After this +/// point, overrunds may or may not be detected. +/// +/// # Buffer layout +/// +/// ```text +/// Without wraparound: With wraparound: +/// +/// + buf +--- NDTR ---+ + buf +---------- NDTR ----------+ +/// | | | | | | +/// v v v v v v +/// +-----------------------------------------+ +-----------------------------------------+ +/// |oooooooooooXXXXXXXXXXXXXXXXoooooooooooooo| |XXXXXXXXXXXXXooooooooooooXXXXXXXXXXXXXXXX| +/// +-----------------------------------------+ +-----------------------------------------+ +/// ^ ^ ^ ^ ^ ^ +/// | | | | | | +/// +- first --+ | +- end ------+ | +/// | | | | +/// +- end --------------------+ +- first ----------------+ +/// ``` +pub struct DmaRingBuffer<'a, W: Word> { + pub(crate) dma_buf: &'a mut [W], + first: usize, + pub ndtr: usize, + expect_next_read_to_wrap: bool, +} + +#[derive(Debug, PartialEq)] +pub struct OverrunError; + +pub trait DmaCtrl { + /// Get the NDTR register value, i.e. the space left in the underlying + /// buffer until the dma writer wraps. + fn ndtr(&self) -> usize; + + /// Read the transfer completed interrupt flag + /// This flag is set by the dma controller when NDTR is reloaded, + /// i.e. when the writing wraps. + fn tcif(&self) -> bool; + + /// Clear the transfer completed interrupt flag + fn clear_tcif(&mut self); +} + +impl<'a, W: Word> DmaRingBuffer<'a, W> { + pub fn new(dma_buf: &'a mut [W]) -> Self { + let ndtr = dma_buf.len(); + Self { + dma_buf, + first: 0, + ndtr, + expect_next_read_to_wrap: false, + } + } + + /// Reset the ring buffer to its initial state + pub fn clear(&mut self) { + self.first = 0; + self.ndtr = self.dma_buf.len(); + self.expect_next_read_to_wrap = false; + } + + /// The buffer end position + fn end(&self) -> usize { + self.dma_buf.len() - self.ndtr + } + + /// Returns whether the buffer is empty + #[allow(dead_code)] + pub fn is_empty(&self) -> bool { + self.first == self.end() + } + + /// The current number of bytes in the buffer + /// This may change at any time if dma is currently active + #[allow(dead_code)] + pub fn len(&self) -> usize { + // Read out a stable end (the dma periheral can change it at anytime) + let end = self.end(); + if self.first <= end { + // No wrap + end - self.first + } else { + self.dma_buf.len() - self.first + end + } + } + + /// Read bytes from the ring buffer + /// OverrunError is returned if the portion to be read was overwritten by the DMA controller. + pub fn read(&mut self, dma: &mut impl DmaCtrl, buf: &mut [W]) -> Result { + let end = self.end(); + + compiler_fence(Ordering::SeqCst); + + if self.first == end { + // The buffer is currently empty + + if dma.tcif() { + // The dma controller has written such that the ring buffer now wraps + // This is the special case where exactly n*dma_buf.len(), n = 1,2,..., bytes was written, + // but where additional bytes are now written causing the ring buffer to wrap. + // This is only an error if the writing has passed the current unread region. + self.ndtr = dma.ndtr(); + if self.end() > self.first { + dma.clear_tcif(); + return Err(OverrunError); + } + } + + self.expect_next_read_to_wrap = false; + Ok(0) + } else if self.first < end { + // The available, unread portion in the ring buffer DOES NOT wrap + + if self.expect_next_read_to_wrap { + // The read was expected to wrap but it did not + + dma.clear_tcif(); + return Err(OverrunError); + } + + // Copy out the bytes from the dma buffer + let len = self.copy_to(buf, self.first..end); + + compiler_fence(Ordering::SeqCst); + + if dma.tcif() { + // The dma controller has written such that the ring buffer now wraps + + self.ndtr = dma.ndtr(); + if self.end() > self.first { + // The bytes that we have copied out have overflowed + // as the writer has now both wrapped and is currently writing + // within the region that we have just copied out + + // Clear transfer completed interrupt flag + dma.clear_tcif(); + return Err(OverrunError); + } + } + + self.first = (self.first + len) % self.dma_buf.len(); + self.expect_next_read_to_wrap = false; + Ok(len) + } else { + // The available, unread portion in the ring buffer DOES wrap + // The dma controller has wrapped since we last read and is currently + // writing (or the next byte added will be) in the beginning of the ring buffer. + + // If the unread portion wraps then the writer must also have wrapped, + // or it has wrapped and we already cleared the TCIF flag + assert!(dma.tcif() || self.expect_next_read_to_wrap); + + // Clear transfer completed interrupt flag + dma.clear_tcif(); + + if self.first + buf.len() < self.dma_buf.len() { + // The provided read buffer is not large enough to include all bytes from the tail of the dma buffer. + + // Copy out from the dma buffer + let len = self.copy_to(buf, self.first..self.dma_buf.len()); + + compiler_fence(Ordering::SeqCst); + + // We have now copied out the data from dma_buf + // Make sure that the just read part was not overwritten during the copy + self.ndtr = dma.ndtr(); + if self.end() > self.first || dma.tcif() { + // The writer has entered the data that we have just read since we read out `end` in the beginning and until now. + return Err(OverrunError); + } + + self.first = (self.first + len) % self.dma_buf.len(); + self.expect_next_read_to_wrap = true; + Ok(len) + } else { + // The provided read buffer is large enough to include all bytes from the tail of the dma buffer, + // so the next read will not have any unread tail bytes in the ring buffer. + + // Copy out from the dma buffer + let tail = self.copy_to(buf, self.first..self.dma_buf.len()); + let head = self.copy_to(&mut buf[tail..], 0..end); + + compiler_fence(Ordering::SeqCst); + + // We have now copied out the data from dma_buf + // Make sure that the just read part was not overwritten during the copy + self.ndtr = dma.ndtr(); + if self.end() > self.first || dma.tcif() { + return Err(OverrunError); + } + + self.first = head; + self.expect_next_read_to_wrap = false; + Ok(tail + head) + } + } + } + + /// Copy from the dma buffer at `data_range` into `buf` + fn copy_to(&mut self, buf: &mut [W], data_range: Range) -> usize { + // Limit the number of bytes that can be copied + let length = usize::min(data_range.len(), buf.len()); + + // Copy from dma buffer into read buffer + // We need to do it like this instead of a simple copy_from_slice() because + // reading from a part of memory that may be simultaneously written to is unsafe + unsafe { + let dma_buf = self.dma_buf.as_ptr(); + + for i in 0..length { + buf[i] = core::ptr::read_volatile(dma_buf.offset((data_range.start + i) as isize)); + } + } + + length + } +} + +#[cfg(test)] +mod tests { + use core::array; + use core::cell::RefCell; + + use super::*; + + struct TestCtrl { + next_ndtr: RefCell>, + tcif: bool, + } + + impl TestCtrl { + pub const fn new() -> Self { + Self { + next_ndtr: RefCell::new(None), + tcif: false, + } + } + + pub fn set_next_ndtr(&mut self, ndtr: usize) { + self.next_ndtr.borrow_mut().replace(ndtr); + } + } + + impl DmaCtrl for TestCtrl { + fn ndtr(&self) -> usize { + self.next_ndtr.borrow_mut().unwrap() + } + + fn tcif(&self) -> bool { + self.tcif + } + + fn clear_tcif(&mut self) { + self.tcif = false; + } + } + + #[test] + fn empty() { + let mut dma_buf = [0u8; 16]; + let ringbuf = DmaRingBuffer::new(&mut dma_buf); + + assert!(ringbuf.is_empty()); + assert_eq!(0, ringbuf.len()); + } + + #[test] + fn can_read() { + let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15 + let mut ctrl = TestCtrl::new(); + let mut ringbuf = DmaRingBuffer::new(&mut dma_buf); + ringbuf.ndtr = 6; + + assert!(!ringbuf.is_empty()); + assert_eq!(10, ringbuf.len()); + + let mut buf = [0; 2]; + assert_eq!(2, ringbuf.read(&mut ctrl, &mut buf).unwrap()); + assert_eq!([0, 1], buf); + assert_eq!(8, ringbuf.len()); + + let mut buf = [0; 2]; + assert_eq!(2, ringbuf.read(&mut ctrl, &mut buf).unwrap()); + assert_eq!([2, 3], buf); + assert_eq!(6, ringbuf.len()); + + let mut buf = [0; 8]; + assert_eq!(6, ringbuf.read(&mut ctrl, &mut buf).unwrap()); + assert_eq!([4, 5, 6, 7, 8, 9], buf[..6]); + assert_eq!(0, ringbuf.len()); + + let mut buf = [0; 2]; + assert_eq!(0, ringbuf.read(&mut ctrl, &mut buf).unwrap()); + } + + #[test] + fn can_read_with_wrap() { + let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15 + let mut ctrl = TestCtrl::new(); + let mut ringbuf = DmaRingBuffer::new(&mut dma_buf); + ringbuf.first = 12; + ringbuf.ndtr = 10; + + // The dma controller has written 4 + 6 bytes and has reloaded NDTR + ctrl.tcif = true; + ctrl.set_next_ndtr(10); + + assert!(!ringbuf.is_empty()); + assert_eq!(6 + 4, ringbuf.len()); + + let mut buf = [0; 2]; + assert_eq!(2, ringbuf.read(&mut ctrl, &mut buf).unwrap()); + assert_eq!([12, 13], buf); + assert_eq!(6 + 2, ringbuf.len()); + + let mut buf = [0; 4]; + assert_eq!(4, ringbuf.read(&mut ctrl, &mut buf).unwrap()); + assert_eq!([14, 15, 0, 1], buf); + assert_eq!(4, ringbuf.len()); + } + + #[test] + fn can_read_when_dma_writer_is_wrapped_and_read_does_not_wrap() { + let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15 + let mut ctrl = TestCtrl::new(); + let mut ringbuf = DmaRingBuffer::new(&mut dma_buf); + ringbuf.first = 2; + ringbuf.ndtr = 6; + + // The dma controller has written 6 + 2 bytes and has reloaded NDTR + ctrl.tcif = true; + ctrl.set_next_ndtr(14); + + let mut buf = [0; 2]; + assert_eq!(2, ringbuf.read(&mut ctrl, &mut buf).unwrap()); + assert_eq!([2, 3], buf); + + assert_eq!(true, ctrl.tcif); // The interrupt flag IS NOT cleared + } + + #[test] + fn can_read_when_dma_writer_is_wrapped_and_read_wraps() { + let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15 + let mut ctrl = TestCtrl::new(); + let mut ringbuf = DmaRingBuffer::new(&mut dma_buf); + ringbuf.first = 12; + ringbuf.ndtr = 10; + + // The dma controller has written 6 + 2 bytes and has reloaded NDTR + ctrl.tcif = true; + ctrl.set_next_ndtr(14); + + let mut buf = [0; 10]; + assert_eq!(10, ringbuf.read(&mut ctrl, &mut buf).unwrap()); + assert_eq!([12, 13, 14, 15, 0, 1, 2, 3, 4, 5], buf); + + assert_eq!(false, ctrl.tcif); // The interrupt flag IS cleared + } + + #[test] + fn cannot_read_when_dma_writer_wraps_with_same_ndtr() { + let mut dma_buf = [0u8; 16]; + let mut ctrl = TestCtrl::new(); + let mut ringbuf = DmaRingBuffer::new(&mut dma_buf); + ringbuf.first = 6; + ringbuf.ndtr = 10; + ctrl.set_next_ndtr(9); + + assert!(ringbuf.is_empty()); // The ring buffer thinks that it is empty + + // The dma controller has written exactly 16 bytes + ctrl.tcif = true; + + let mut buf = [0; 2]; + assert_eq!(Err(OverrunError), ringbuf.read(&mut ctrl, &mut buf)); + + assert_eq!(false, ctrl.tcif); // The interrupt flag IS cleared + } + + #[test] + fn cannot_read_when_dma_writer_overwrites_during_not_wrapping_read() { + let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15 + let mut ctrl = TestCtrl::new(); + let mut ringbuf = DmaRingBuffer::new(&mut dma_buf); + ringbuf.first = 2; + ringbuf.ndtr = 6; + + // The dma controller has written 6 + 3 bytes and has reloaded NDTR + ctrl.tcif = true; + ctrl.set_next_ndtr(13); + + let mut buf = [0; 2]; + assert_eq!(Err(OverrunError), ringbuf.read(&mut ctrl, &mut buf)); + + assert_eq!(false, ctrl.tcif); // The interrupt flag IS cleared + } + + #[test] + fn cannot_read_when_dma_writer_overwrites_during_wrapping_read() { + let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15 + let mut ctrl = TestCtrl::new(); + let mut ringbuf = DmaRingBuffer::new(&mut dma_buf); + ringbuf.first = 12; + ringbuf.ndtr = 10; + + // The dma controller has written 6 + 13 bytes and has reloaded NDTR + ctrl.tcif = true; + ctrl.set_next_ndtr(3); + + let mut buf = [0; 2]; + assert_eq!(Err(OverrunError), ringbuf.read(&mut ctrl, &mut buf)); + + assert_eq!(false, ctrl.tcif); // The interrupt flag IS cleared + } +} diff --git a/embassy-stm32/src/usart/mod.rs b/embassy-stm32/src/usart/mod.rs index 26656165..fea0c5f1 100644 --- a/embassy-stm32/src/usart/mod.rs +++ b/embassy-stm32/src/usart/mod.rs @@ -283,8 +283,8 @@ impl<'d, T: BasicInstance, RxDma> UartRx<'d, T, RxDma> { let (sr, cr1, cr3) = unsafe { (sr(r).read(), r.cr1().read(), r.cr3().read()) }; + let mut wake = false; let has_errors = (sr.pe() && cr1.peie()) || ((sr.fe() || sr.ne() || sr.ore()) && cr3.eie()); - if has_errors { // clear all interrupts and DMA Rx Request unsafe { @@ -304,22 +304,35 @@ impl<'d, T: BasicInstance, RxDma> UartRx<'d, T, RxDma> { }); } - compiler_fence(Ordering::SeqCst); + wake = true; + } else { + if cr1.idleie() && sr.idle() { + // IDLE detected: no more data will come + unsafe { + r.cr1().modify(|w| { + // disable idle line detection + w.set_idleie(false); + }); - s.rx_waker.wake(); - } else if cr1.idleie() && sr.idle() { - // IDLE detected: no more data will come - unsafe { - r.cr1().modify(|w| { - // disable idle line detection - w.set_idleie(false); - }); + r.cr3().modify(|w| { + // disable DMA Rx Request + w.set_dmar(false); + }); + } - r.cr3().modify(|w| { - // disable DMA Rx Request - w.set_dmar(false); - }); + wake = true; } + + if cr1.rxneie() { + // We cannot check the RXNE flag as it is auto-cleared by the DMA controller + + // It is up to the listener to determine if this in fact was a RX event and disable the RXNE detection + + wake = true; + } + } + + if wake { compiler_fence(Ordering::SeqCst); s.rx_waker.wake(); @@ -972,6 +985,8 @@ mod eio { pub use buffered::*; #[cfg(feature = "nightly")] mod buffered; +mod rx_ringbuffered; +pub use rx_ringbuffered::RingBufferedUartRx; #[cfg(usart_v1)] fn tdr(r: crate::pac::usart::Usart) -> *mut u8 { diff --git a/embassy-stm32/src/usart/rx_ringbuffered.rs b/embassy-stm32/src/usart/rx_ringbuffered.rs new file mode 100644 index 00000000..0dc90ece --- /dev/null +++ b/embassy-stm32/src/usart/rx_ringbuffered.rs @@ -0,0 +1,286 @@ +use core::future::poll_fn; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::Poll; + +use embassy_hal_common::drop::OnDrop; +use embassy_hal_common::PeripheralRef; + +use super::{rdr, sr, BasicInstance, Error, UartRx}; +use crate::dma::ringbuffer::OverrunError; +use crate::dma::RingBuffer; + +pub struct RingBufferedUartRx<'d, T: BasicInstance, RxDma: super::RxDma> { + _peri: PeripheralRef<'d, T>, + ring_buf: RingBuffer<'d, RxDma, u8>, +} + +impl<'d, T: BasicInstance, RxDma: super::RxDma> UartRx<'d, T, RxDma> { + /// Turn the `UartRx` into a buffered uart which can continously receive in the background + /// without the possibility of loosing bytes. The `dma_buf` is a buffer registered to the + /// DMA controller, and must be sufficiently large, such that it will not overflow. + pub fn into_ring_buffered(self, dma_buf: &'d mut [u8]) -> RingBufferedUartRx<'d, T, RxDma> { + assert!(dma_buf.len() > 0 && dma_buf.len() <= 0xFFFF); + + let request = self.rx_dma.request(); + let opts = Default::default(); + let ring_buf = unsafe { RingBuffer::new_read(self.rx_dma, request, rdr(T::regs()), dma_buf, opts) }; + RingBufferedUartRx { + _peri: self._peri, + ring_buf, + } + } +} + +impl<'d, T: BasicInstance, RxDma: super::RxDma> RingBufferedUartRx<'d, T, RxDma> { + pub fn start(&mut self) -> Result<(), Error> { + // Clear the ring buffer so that it is ready to receive data + self.ring_buf.clear(); + + self.setup_uart(); + + Ok(()) + } + + /// Start uart background receive + fn setup_uart(&mut self) { + // fence before starting DMA. + compiler_fence(Ordering::SeqCst); + + self.ring_buf.start(); + + let r = T::regs(); + // clear all interrupts and DMA Rx Request + // SAFETY: only clears Rx related flags + unsafe { + r.cr1().modify(|w| { + // disable RXNE interrupt + w.set_rxneie(false); + // enable parity interrupt if not ParityNone + w.set_peie(w.pce()); + // disable idle line interrupt + w.set_idleie(false); + }); + r.cr3().modify(|w| { + // enable Error Interrupt: (Frame error, Noise error, Overrun error) + w.set_eie(true); + // enable DMA Rx Request + w.set_dmar(true); + }); + } + } + + /// Stop uart background receive + fn teardown_uart(&mut self) { + let r = T::regs(); + // clear all interrupts and DMA Rx Request + // SAFETY: only clears Rx related flags + unsafe { + r.cr1().modify(|w| { + // disable RXNE interrupt + w.set_rxneie(false); + // disable parity interrupt + w.set_peie(false); + // disable idle line interrupt + w.set_idleie(false); + }); + r.cr3().modify(|w| { + // disable Error Interrupt: (Frame error, Noise error, Overrun error) + w.set_eie(false); + // disable DMA Rx Request + w.set_dmar(false); + }); + } + + compiler_fence(Ordering::SeqCst); + + self.ring_buf.request_stop(); + while self.ring_buf.is_running() {} + } + + /// Read bytes that are readily available in the ring buffer. + /// If no bytes are currently available in the buffer the call waits until data are received. + /// + /// Background receive is started if `start()` has not been previously called. + /// + /// Receive in the background is terminated if an error is returned. + /// It must then manually be started again by calling `start()` or by re-calling `read()`. + pub async fn read(&mut self, buf: &mut [u8]) -> Result { + let r = T::regs(); + + // SAFETY: read only + let is_started = unsafe { r.cr3().read().dmar() }; + + // Start background receive if it was not already started + if !is_started { + self.start()?; + } + + // SAFETY: read only and we only use Rx related flags + let s = unsafe { sr(r).read() }; + let has_errors = s.pe() || s.fe() || s.ne() || s.ore(); + if has_errors { + self.teardown_uart(); + + if s.pe() { + return Err(Error::Parity); + } else if s.fe() { + return Err(Error::Framing); + } else if s.ne() { + return Err(Error::Noise); + } else { + return Err(Error::Overrun); + } + } + + let ndtr = self.ring_buf.get_remaining_transfers(); + self.ring_buf.set_ndtr(ndtr); + match self.ring_buf.read(buf) { + Ok(len) if len == 0 => {} + Ok(len) => { + assert!(len > 0); + return Ok(len); + } + Err(OverrunError) => { + // Stop any transfer from now on + // The user must re-start to receive any more data + self.teardown_uart(); + return Err(Error::Overrun); + } + } + + // Wait for any data since `ndtr` + self.wait_for_data(ndtr).await?; + + // ndtr is now different than the value provided to `wait_for_data()` + // Re-sample ndtr now when it has changed. + self.ring_buf.set_ndtr(self.ring_buf.get_remaining_transfers()); + let len = self.ring_buf.read(buf).map_err(|_err| Error::Overrun)?; + assert!(len > 0); + Ok(len) + } + + /// Wait for uart data + async fn wait_for_data(&mut self, old_ndtr: usize) -> Result<(), Error> { + let r = T::regs(); + + // make sure USART state is restored to neutral state when this future is dropped + let _drop = OnDrop::new(move || { + // SAFETY: only clears Rx related flags + unsafe { + r.cr1().modify(|w| { + // disable RXNE interrupt + w.set_rxneie(false); + }); + } + }); + + // SAFETY: only sets Rx related flags + unsafe { + r.cr1().modify(|w| { + // enable RXNE interrupt + w.set_rxneie(true); + }); + } + + // future which completes when RX "not empty" is detected, + // i.e. when there is data in uart rx register + let rxne = poll_fn(|cx| { + let s = T::state(); + + // Register waker to be awaken when RXNE interrupt is received + s.rx_waker.register(cx.waker()); + + compiler_fence(Ordering::SeqCst); + + // SAFETY: read only and we only use Rx related flags + let s = unsafe { sr(r).read() }; + let has_errors = s.pe() || s.fe() || s.ne() || s.ore(); + if has_errors { + if s.pe() { + return Poll::Ready(Err(Error::Parity)); + } else if s.fe() { + return Poll::Ready(Err(Error::Framing)); + } else if s.ne() { + return Poll::Ready(Err(Error::Noise)); + } else { + return Poll::Ready(Err(Error::Overrun)); + } + } + + // Re-sample ndtr and determine if it has changed since we started + // waiting for data. + let new_ndtr = self.ring_buf.get_remaining_transfers(); + if new_ndtr != old_ndtr { + // Some data was received as NDTR has changed + Poll::Ready(Ok(())) + } else { + // It may be that the DMA controller is currently busy consuming the + // RX data register. We therefore wait register to become empty. + while unsafe { sr(r).read().rxne() } {} + + compiler_fence(Ordering::SeqCst); + + // Re-get again: This time we know that the DMA controller has consumed + // the current read register if it was busy doing so + let new_ndtr = self.ring_buf.get_remaining_transfers(); + if new_ndtr != old_ndtr { + // Some data was received as NDTR has changed + Poll::Ready(Ok(())) + } else { + Poll::Pending + } + } + }); + + compiler_fence(Ordering::SeqCst); + + let new_ndtr = self.ring_buf.get_remaining_transfers(); + if new_ndtr != old_ndtr { + // Fast path - NDTR has already changed, no reason to poll + Ok(()) + } else { + // NDTR has not changed since we first read from the ring buffer + // Wait for RXNE interrupt... + match rxne.await { + Ok(()) => Ok(()), + Err(e) => { + self.teardown_uart(); + Err(e) + } + } + } + } +} + +impl> Drop for RingBufferedUartRx<'_, T, RxDma> { + fn drop(&mut self) { + self.teardown_uart(); + } +} + +#[cfg(all(feature = "unstable-traits", feature = "nightly"))] +mod eio { + use embedded_io::asynch::Read; + use embedded_io::Io; + + use super::RingBufferedUartRx; + use crate::usart::{BasicInstance, Error, RxDma}; + + impl Io for RingBufferedUartRx<'_, T, Rx> + where + T: BasicInstance, + Rx: RxDma, + { + type Error = Error; + } + + impl Read for RingBufferedUartRx<'_, T, Rx> + where + T: BasicInstance, + Rx: RxDma, + { + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.read(buf).await + } + } +} diff --git a/tests/stm32/Cargo.toml b/tests/stm32/Cargo.toml index d10d01e2..240fad52 100644 --- a/tests/stm32/Cargo.toml +++ b/tests/stm32/Cargo.toml @@ -33,6 +33,8 @@ embedded-hal = "0.2.6" embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.10" } embedded-hal-async = { version = "=0.2.0-alpha.1" } panic-probe = { version = "0.3.0", features = ["print-defmt"] } +rand_core = { version = "0.6", default-features = false } +rand_chacha = { version = "0.3", default-features = false } chrono = { version = "^0.4", default-features = false, optional = true} diff --git a/tests/stm32/src/bin/usart_rx_ringbuffered.rs b/tests/stm32/src/bin/usart_rx_ringbuffered.rs new file mode 100644 index 00000000..3ea8bfb7 --- /dev/null +++ b/tests/stm32/src/bin/usart_rx_ringbuffered.rs @@ -0,0 +1,188 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +#[path = "../example_common.rs"] +mod example_common; +use embassy_executor::Spawner; +use embassy_stm32::interrupt; +use embassy_stm32::usart::{Config, DataBits, Parity, RingBufferedUartRx, StopBits, Uart, UartTx}; +use embassy_time::{Duration, Timer}; +use example_common::*; +use rand_chacha::ChaCha8Rng; +use rand_core::{RngCore, SeedableRng}; + +#[cfg(feature = "stm32f103c8")] +mod board { + pub type Uart = embassy_stm32::peripherals::USART1; + pub type TxDma = embassy_stm32::peripherals::DMA1_CH4; + pub type RxDma = embassy_stm32::peripherals::DMA1_CH5; +} +#[cfg(feature = "stm32g491re")] +mod board { + pub type Uart = embassy_stm32::peripherals::USART1; + pub type TxDma = embassy_stm32::peripherals::DMA1_CH1; + pub type RxDma = embassy_stm32::peripherals::DMA1_CH2; +} +#[cfg(feature = "stm32g071rb")] +mod board { + pub type Uart = embassy_stm32::peripherals::USART1; + pub type TxDma = embassy_stm32::peripherals::DMA1_CH1; + pub type RxDma = embassy_stm32::peripherals::DMA1_CH2; +} +#[cfg(feature = "stm32f429zi")] +mod board { + pub type Uart = embassy_stm32::peripherals::USART2; + pub type TxDma = embassy_stm32::peripherals::DMA1_CH6; + pub type RxDma = embassy_stm32::peripherals::DMA1_CH5; +} +#[cfg(feature = "stm32wb55rg")] +mod board { + pub type Uart = embassy_stm32::peripherals::LPUART1; + pub type TxDma = embassy_stm32::peripherals::DMA1_CH1; + pub type RxDma = embassy_stm32::peripherals::DMA1_CH2; +} +#[cfg(feature = "stm32h755zi")] +mod board { + pub type Uart = embassy_stm32::peripherals::USART1; + pub type TxDma = embassy_stm32::peripherals::DMA1_CH0; + pub type RxDma = embassy_stm32::peripherals::DMA1_CH1; +} +#[cfg(feature = "stm32u585ai")] +mod board { + pub type Uart = embassy_stm32::peripherals::USART3; + pub type TxDma = embassy_stm32::peripherals::GPDMA1_CH0; + pub type RxDma = embassy_stm32::peripherals::GPDMA1_CH1; +} + +const ONE_BYTE_DURATION_US: u32 = 9_000_000 / 115200; + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_stm32::init(config()); + info!("Hello World!"); + + // Arduino pins D0 and D1 + // They're connected together with a 1K resistor. + #[cfg(feature = "stm32f103c8")] + let (tx, rx, usart, irq, tx_dma, rx_dma) = ( + p.PA9, + p.PA10, + p.USART1, + interrupt::take!(USART1), + p.DMA1_CH4, + p.DMA1_CH5, + ); + #[cfg(feature = "stm32g491re")] + let (tx, rx, usart, irq, tx_dma, rx_dma) = + (p.PC4, p.PC5, p.USART1, interrupt::take!(USART1), p.DMA1_CH1, p.DMA1_CH2); + #[cfg(feature = "stm32g071rb")] + let (tx, rx, usart, irq, tx_dma, rx_dma) = + (p.PC4, p.PC5, p.USART1, interrupt::take!(USART1), p.DMA1_CH1, p.DMA1_CH2); + #[cfg(feature = "stm32f429zi")] + let (tx, rx, usart, irq, tx_dma, rx_dma) = + (p.PA2, p.PA3, p.USART2, interrupt::take!(USART2), p.DMA1_CH6, p.DMA1_CH5); + #[cfg(feature = "stm32wb55rg")] + let (tx, rx, usart, irq, tx_dma, rx_dma) = ( + p.PA2, + p.PA3, + p.LPUART1, + interrupt::take!(LPUART1), + p.DMA1_CH1, + p.DMA1_CH2, + ); + #[cfg(feature = "stm32h755zi")] + let (tx, rx, usart, irq, tx_dma, rx_dma) = + (p.PB6, p.PB7, p.USART1, interrupt::take!(USART1), p.DMA1_CH0, p.DMA1_CH1); + #[cfg(feature = "stm32u585ai")] + let (tx, rx, usart, irq, tx_dma, rx_dma) = ( + p.PD8, + p.PD9, + p.USART3, + interrupt::take!(USART3), + p.GPDMA1_CH0, + p.GPDMA1_CH1, + ); + + // To run this test, use the saturating_serial test utility to saturate the serial port + + let mut config = Config::default(); + config.baudrate = 115200; + config.data_bits = DataBits::DataBits8; + config.stop_bits = StopBits::STOP1; + config.parity = Parity::ParityNone; + + let usart = Uart::new(usart, rx, tx, irq, tx_dma, rx_dma, config); + let (tx, rx) = usart.split(); + static mut DMA_BUF: [u8; 64] = [0; 64]; + let dma_buf = unsafe { DMA_BUF.as_mut() }; + let rx = rx.into_ring_buffered(dma_buf); + + info!("Spawning tasks"); + spawner.spawn(transmit_task(tx)).unwrap(); + spawner.spawn(receive_task(rx)).unwrap(); +} + +#[embassy_executor::task] +async fn transmit_task(mut tx: UartTx<'static, board::Uart, board::TxDma>) { + let mut rng = ChaCha8Rng::seed_from_u64(1337); + + info!("Starting random transmissions into void..."); + + let mut i: u8 = 0; + loop { + let mut buf = [0; 32]; + let len = 1 + (rng.next_u32() as usize % (buf.len() - 1)); + for b in &mut buf[..len] { + *b = i; + i = i.wrapping_add(1); + } + + tx.write(&buf[..len]).await.unwrap(); + Timer::after(Duration::from_micros((rng.next_u32() % 10000) as _)).await; + + //i += 1; + //if i % 1000 == 0 { + // trace!("Wrote {} times", i); + //} + } +} + +#[embassy_executor::task] +async fn receive_task(mut rx: RingBufferedUartRx<'static, board::Uart, board::RxDma>) { + info!("Ready to receive..."); + + let mut rng = ChaCha8Rng::seed_from_u64(1337); + + let mut i = 0; + let mut expected: Option = None; + loop { + let mut buf = [0; 100]; + let max_len = 1 + (rng.next_u32() as usize % (buf.len() - 1)); + let received = rx.read(&mut buf[..max_len]).await.unwrap(); + + if expected.is_none() { + info!("Test started"); + expected = Some(buf[0]); + } + + for byte in &buf[..received] { + if byte != &expected.unwrap() { + error!("Test fail! received {}, expected {}", *byte, expected.unwrap()); + cortex_m::asm::bkpt(); + return; + } + expected = Some(expected.unwrap().wrapping_add(1)); + } + + if received < max_len { + let byte_count = rng.next_u32() % 64; + Timer::after(Duration::from_micros((byte_count * ONE_BYTE_DURATION_US) as _)).await; + } + + i += 1; + if i % 1000 == 0 { + trace!("Read {} times", i); + } + } +} diff --git a/tests/utils/Cargo.toml b/tests/utils/Cargo.toml new file mode 100644 index 00000000..7d66fd58 --- /dev/null +++ b/tests/utils/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "test-utils" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rand = "0.8" +serial = "0.4" diff --git a/tests/utils/src/bin/saturate_serial.rs b/tests/utils/src/bin/saturate_serial.rs new file mode 100644 index 00000000..28480516 --- /dev/null +++ b/tests/utils/src/bin/saturate_serial.rs @@ -0,0 +1,52 @@ +use std::path::Path; +use std::time::Duration; +use std::{env, io, thread}; + +use rand::random; +use serial::SerialPort; + +pub fn main() { + if let Some(port_name) = env::args().nth(1) { + let sleep = env::args().position(|x| x == "--sleep").is_some(); + + println!("Saturating port {:?} with 115200 8N1", port_name); + println!("Sleep: {}", sleep); + let mut port = serial::open(&port_name).unwrap(); + if saturate(&mut port, sleep).is_err() { + eprintln!("Unable to saturate port"); + } + } else { + let path = env::args().next().unwrap(); + let basepath = Path::new(&path).with_extension(""); + let basename = basepath.file_name().unwrap(); + eprintln!("USAGE: {} ", basename.to_string_lossy()); + } +} + +fn saturate(port: &mut T, sleep: bool) -> io::Result<()> { + port.reconfigure(&|settings| { + settings.set_baud_rate(serial::Baud115200)?; + settings.set_char_size(serial::Bits8); + settings.set_parity(serial::ParityNone); + settings.set_stop_bits(serial::Stop1); + Ok(()) + })?; + + let mut written = 0; + loop { + let len = random::() % 0x1000; + let buf: Vec = (written..written + len).map(|x| x as u8).collect(); + + port.write_all(&buf)?; + + if sleep { + let micros = (random::() % 1000) as u64; + println!("Sleeping {}us", micros); + port.flush().unwrap(); + thread::sleep(Duration::from_micros(micros)); + } + + written += len; + println!("Written: {}", written); + } +} \ No newline at end of file