Ring-buffered uart rx with one-period overrun detection
This commit is contained in:
parent
855c0d1423
commit
49455792cb
@ -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<W>,
|
||||
@ -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<C: Channel> 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<P = C> + '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<usize, OverrunError> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ pub use gpdma::*;
|
||||
#[cfg(dmamux)]
|
||||
mod dmamux;
|
||||
|
||||
pub(crate) mod ringbuffer;
|
||||
pub mod word;
|
||||
|
||||
use core::mem;
|
||||
|
433
embassy-stm32/src/dma/ringbuffer.rs
Normal file
433
embassy-stm32/src/dma/ringbuffer.rs
Normal file
@ -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<usize, OverrunError> {
|
||||
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>) -> 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<Option<usize>>,
|
||||
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
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
286
embassy-stm32/src/usart/rx_ringbuffered.rs
Normal file
286
embassy-stm32/src/usart/rx_ringbuffered.rs
Normal file
@ -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<T>> {
|
||||
_peri: PeripheralRef<'d, T>,
|
||||
ring_buf: RingBuffer<'d, RxDma, u8>,
|
||||
}
|
||||
|
||||
impl<'d, T: BasicInstance, RxDma: super::RxDma<T>> 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<T>> 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<usize, Error> {
|
||||
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<T: BasicInstance, RxDma: super::RxDma<T>> 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<T, Rx> Io for RingBufferedUartRx<'_, T, Rx>
|
||||
where
|
||||
T: BasicInstance,
|
||||
Rx: RxDma<T>,
|
||||
{
|
||||
type Error = Error;
|
||||
}
|
||||
|
||||
impl<T, Rx> Read for RingBufferedUartRx<'_, T, Rx>
|
||||
where
|
||||
T: BasicInstance,
|
||||
Rx: RxDma<T>,
|
||||
{
|
||||
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
|
||||
self.read(buf).await
|
||||
}
|
||||
}
|
||||
}
|
@ -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}
|
||||
|
||||
|
188
tests/stm32/src/bin/usart_rx_ringbuffered.rs
Normal file
188
tests/stm32/src/bin/usart_rx_ringbuffered.rs
Normal file
@ -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<u8> = 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);
|
||||
}
|
||||
}
|
||||
}
|
10
tests/utils/Cargo.toml
Normal file
10
tests/utils/Cargo.toml
Normal file
@ -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"
|
52
tests/utils/src/bin/saturate_serial.rs
Normal file
52
tests/utils/src/bin/saturate_serial.rs
Normal file
@ -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: {} <port-name>", basename.to_string_lossy());
|
||||
}
|
||||
}
|
||||
|
||||
fn saturate<T: SerialPort>(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::<usize>() % 0x1000;
|
||||
let buf: Vec<u8> = (written..written + len).map(|x| x as u8).collect();
|
||||
|
||||
port.write_all(&buf)?;
|
||||
|
||||
if sleep {
|
||||
let micros = (random::<usize>() % 1000) as u64;
|
||||
println!("Sleeping {}us", micros);
|
||||
port.flush().unwrap();
|
||||
thread::sleep(Duration::from_micros(micros));
|
||||
}
|
||||
|
||||
written += len;
|
||||
println!("Written: {}", written);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user