From 2c3900443d6c0784a0bec3a3afba0aa5e7dc3d30 Mon Sep 17 00:00:00 2001 From: Lucas Martins Date: Wed, 30 Oct 2024 16:56:53 -0300 Subject: [PATCH 1/5] add ringbuffer and adjust irq to accomodate --- embassy-stm32/src/dma/gpdma.rs | 360 ++++++++++++++++++++++++++++++++- 1 file changed, 355 insertions(+), 5 deletions(-) diff --git a/embassy-stm32/src/dma/gpdma.rs b/embassy-stm32/src/dma/gpdma.rs index a877bb8d49..0f40bdc809 100644 --- a/embassy-stm32/src/dma/gpdma.rs +++ b/embassy-stm32/src/dma/gpdma.rs @@ -1,13 +1,14 @@ #![macro_use] -use core::future::Future; +use core::future::{poll_fn, Future}; use core::pin::Pin; -use core::sync::atomic::{fence, Ordering}; -use core::task::{Context, Poll}; +use core::sync::atomic::{fence, AtomicUsize, Ordering, AtomicBool}; +use core::task::{Context, Poll, Waker}; use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; use embassy_sync::waitqueue::AtomicWaker; +use super::ringbuffer::{DmaCtrl, ReadableDmaRingBuffer, self}; use super::word::{Word, WordSize}; use super::{AnyChannel, Channel, Dir, Request, STATE}; use crate::interrupt::typelevel::Interrupt; @@ -46,11 +47,17 @@ impl From for vals::Dw { pub(crate) struct ChannelState { waker: AtomicWaker, + circular_address: AtomicUsize, + complete_count: AtomicUsize, + is_circular: AtomicBool, } impl ChannelState { pub(crate) const NEW: Self = Self { waker: AtomicWaker::new(), + circular_address: AtomicUsize::new(0), + complete_count: AtomicUsize::new(0), + is_circular: AtomicBool::new(false), }; } @@ -96,9 +103,42 @@ impl AnyChannel { ); } - if sr.suspf() || sr.tcf() { - // disable all xxIEs to prevent the irq from firing again. + if sr.htf() && sr.tcf() { + // disable all interrupts if both the transfer complete and half transfer bits are set, + // sice it does not make sense to service this and this usually signals a overrun/reset. ch.cr().write(|_| {}); + } + + let circ = state.is_circular.load(Ordering::Relaxed); + if circ { + // wake the future for the half-transfer event + if sr.htf() { + ch.fcr().write(|w| { w.set_htf(true); }); + state.waker.wake(); + return; + } + + // Transfer complete: Clear it's flag and increment the completed count + if sr.tcf() { + ch.fcr().modify(|w| w.set_tcf(true)); + state.complete_count.fetch_add(1, Ordering::Relaxed); + state.waker.wake(); + return; + } + } + + // suspend request: disable all irqs + if sr.suspf() || (!circ && sr.tcf()) { + // ch.fcr().modify(|w| w.set_suspf(true)); + + // disable all xxIEs to prevent the irq from firing again. + ch.cr().modify(|w| { + w.set_tcie(false); + w.set_useie(false); + w.set_dteie(false); + w.set_suspie(false); + w.set_htie(false); + }); // Wake the future. It'll look at tcf and see it's set. state.waker.wake(); @@ -238,6 +278,7 @@ impl<'a> Transfer<'a> { w.set_sinc(dir == Dir::MemoryToPeripheral && incr_mem); w.set_dinc(dir == Dir::PeripheralToMemory && incr_mem); }); + ch.tr2().write(|w| { w.set_dreq(match dir { Dir::MemoryToPeripheral => vals::Dreq::DESTINATIONPERIPHERAL, @@ -339,3 +380,312 @@ impl<'a> Future for Transfer<'a> { } } } + + +/// Dma control interface for this DMA Type +struct DmaCtrlImpl<'a> { + channel: PeripheralRef<'a, AnyChannel>, + word_size: WordSize, +} +impl<'a> DmaCtrl for DmaCtrlImpl<'a> { + fn get_remaining_transfers(&self) -> usize { + let info = self.channel.info(); + let ch = info.dma.ch(info.num); + (ch.br1().read().bndt() / self.word_size.bytes() as u16) as usize + } + + // fn get_complete_count(&self) -> usize { + // STATE[self.channel.id as usize].complete_count.load(Ordering::Acquire) + // } + + fn reset_complete_count(&mut self) -> usize { + STATE[self.channel.id as usize].complete_count.swap(0, Ordering::AcqRel) + } + + fn set_waker(&mut self, waker: &Waker) { + STATE[self.channel.id as usize].waker.register(waker); + } +} + +struct RingBuffer {} + +impl RingBuffer { + fn configure<'a, W: Word>( + ch: &pac::gpdma::Channel, + channel_index: usize, + request: Request, + dir: Dir, + peri_addr: *mut W, + buffer: &'a mut [W], + _options: TransferOptions, + ) { + let state = &STATE[channel_index]; + // "Preceding reads and writes cannot be moved past subsequent writes." + fence(Ordering::SeqCst); + + state.is_circular.store(true, Ordering::Release); + + let mem_addr = buffer.as_ptr() as usize; + let mem_len = buffer.len(); + + ch.cr().write(|w| w.set_reset(true)); + ch.fcr().write(|w| w.0 = 0xFFFF_FFFF); // clear all irqs + + if mem_addr & 0b11 != 0 { + panic!("circular address must be 4-byte aligned"); + } + + state.circular_address.store(mem_addr, Ordering::Release); + + let lli = state.circular_address.as_ptr() as u32; + + ch.llr().write(|w| { + match dir { + Dir::MemoryToPeripheral => { + w.set_uda(false); + w.set_usa(true); + }, + + Dir::PeripheralToMemory => { + w.set_uda(true); + w.set_usa(false); + }, + } + + // lower 16 bits of the memory address + w.set_la(((lli >> 2usize) & 0x3fff) as u16); + }); + + ch.lbar().write(|w| { + // upper 16 bits of the address of lli1 + w.set_lba((lli >> 16usize) as u16); + }); + + let data_size = W::size(); + ch.tr1().write(|w| { + w.set_sdw(data_size.into()); + w.set_ddw(data_size.into()); + w.set_sinc(dir == Dir::MemoryToPeripheral); + w.set_dinc(dir == Dir::PeripheralToMemory); + }); + ch.tr2().write(|w| { + w.set_dreq(match dir { + Dir::MemoryToPeripheral => vals::Dreq::DESTINATIONPERIPHERAL, + Dir::PeripheralToMemory => vals::Dreq::SOURCEPERIPHERAL, + }); + w.set_reqsel(request); + }); + ch.br1().write(|w| { + // BNDT is specified as bytes, not as number of transfers. + w.set_bndt((mem_len * data_size.bytes()) as u16) + }); + + match dir { + Dir::MemoryToPeripheral => { + ch.sar().write_value(mem_addr as _); + ch.dar().write_value(peri_addr as _); + } + Dir::PeripheralToMemory => { + ch.sar().write_value(peri_addr as _); + ch.dar().write_value(mem_addr as _); + } + } + } + + #[inline] + fn clear_irqs(ch: &pac::gpdma::Channel) { + ch.fcr().modify(|w| { + w.set_htf(true); + w.set_tcf(true); + w.set_suspf(true); + }); + } + + fn is_running(ch: &pac::gpdma::Channel) -> bool { + !ch.sr().read().tcf() + } + + fn request_suspend(ch: &pac::gpdma::Channel) { + ch.cr().modify(|w| { + w.set_susp(true); + }); + } + + async fn stop(ch: &pac::gpdma::Channel, set_waker: &mut dyn FnMut(&Waker)) { + use core::sync::atomic::compiler_fence; + + Self::request_suspend(ch); + + //wait until cr.susp reads as true + poll_fn(|cx| { + set_waker(cx.waker()); + + compiler_fence(Ordering::SeqCst); + + let cr = ch.cr().read(); + + if cr.susp() { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await + } + + fn start(ch: &pac::gpdma::Channel) { + Self::clear_irqs(ch); + ch.cr().modify(|w| { + w.set_susp(false); + w.set_tcie(true); + w.set_useie(true); + w.set_dteie(true); + w.set_suspie(true); + w.set_htie(true); + w.set_en(true); + }); + } +} + +/// This is a Readable ring buffer. It reads data from a peripheral into a buffer. The reads happen in circular mode. +/// There are interrupts on complete and half complete. You should read half the buffer on every read. +pub struct ReadableRingBuffer<'a, W: Word> { + channel: PeripheralRef<'a, AnyChannel>, + ringbuf: ReadableDmaRingBuffer<'a, W>, +} + +impl<'a, W: Word> ReadableRingBuffer<'a, W> { + /// Create a new Readable ring buffer. + pub unsafe fn new( + channel: impl Peripheral

+ 'a, + request: Request, + peri_addr: *mut W, + buffer: &'a mut [W], + options: TransferOptions, + ) -> Self { + into_ref!(channel); + let channel: PeripheralRef<'a, AnyChannel> = channel.map_into(); + + #[cfg(dmamux)] + super::dmamux::configure_dmamux(&mut channel, request); + + let info = channel.info(); + + RingBuffer::configure( + &info.dma.ch(info.num), + channel.id as usize, + request, + Dir::PeripheralToMemory, + peri_addr, + buffer, + options, + ); + + Self { + channel, + ringbuf: ReadableDmaRingBuffer::new(buffer), + } + } + + /// Start reading the peripheral in ciccular mode. + pub fn start(&mut self) { + let info = self.channel.info(); + let ch = &info.dma.ch(info.num); + RingBuffer::start(ch); + } + + /// Request the transfer to stop. Use is_running() to see when the transfer is complete. + pub fn request_pause(&mut self) { + let info = self.channel.info(); + RingBuffer::request_suspend(&info.dma.ch(info.num)); + } + + + /// Request the transfer to stop. Use is_running() to see when the transfer is complete. + pub fn request_stop(&mut self) { + let info = self.channel.info(); + RingBuffer::request_suspend(&info.dma.ch(info.num)); + } + + /// Await until the stop completes. This is not used with request_stop(). Just call and await. + /// It will stop when the current transfer is complete. + pub async fn stop(&mut self) { + let info = self.channel.info(); + RingBuffer::stop(&info.dma.ch(info.num), &mut |waker| self.set_waker(waker)).await + } + + // /// Clear the buffers internal pointers. + // pub fn clear(&mut self) { + // self.ringbuf.reset(dma) + // } + + /// Read elements from the ring buffer + /// Return a tuple of the length read and the length remaining in the buffer + /// If not all of the elements were read, then there will be some elements in the buffer + /// remaining + /// The length remaining is the capacity, ring_buf.len(), less the elements remaining after the + /// read + /// Error is returned if the portion to be read was overwritten by the DMA controller. + pub fn read(&mut self, buf: &mut [W]) -> Result<(usize, usize), ringbuffer::Error> { + self.ringbuf.read( + &mut DmaCtrlImpl { + channel: self.channel.reborrow(), + word_size: W::size(), + }, + buf, + ) + } + + /// Read an exact number of elements from the ringbuffer. + /// + /// Returns the remaining number of elements available for immediate reading. + /// Error is returned if the portion to be read was overwritten by the DMA controller. + /// + /// Async/Wake Behavior: + /// The underlying DMA peripheral only can wake us when its buffer pointer has reached the halfway point, + /// and when it wraps around. This means that when called with a buffer of length 'M', when this + /// ring buffer was created with a buffer of size 'N': + /// - If M equals N/2 or N/2 divides evenly into M, this function will return every N/2 elements read on the DMA source. + /// - Otherwise, this function may need up to N/2 extra elements to arrive before returning. + pub async fn read_exact(&mut self, buffer: &mut [W]) -> Result { + self.ringbuf + .read_exact( + &mut DmaCtrlImpl { + channel: self.channel.reborrow(), + word_size: W::size(), + }, + buffer, + ) + .await + } + + /// The capacity of the ringbuffer + pub const fn cap(&self) -> usize { + self.ringbuf.cap() + } + + /// Set the waker for the DMA controller. + pub fn set_waker(&mut self, waker: &Waker) { + DmaCtrlImpl { + channel: self.channel.reborrow(), + word_size: W::size(), + } + .set_waker(waker); + } + + /// Return whether this transfer is still running. + pub fn is_running(&mut self) -> bool { + let info = self.channel.info(); + RingBuffer::is_running(&info.dma.ch(info.num)) + } +} + +impl<'a, W: Word> Drop for ReadableRingBuffer<'a, 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); + } +} From 2bd95129d2628d7472da326ff88437ae67ea7d5a Mon Sep 17 00:00:00 2001 From: Lucas Martins Date: Wed, 30 Oct 2024 16:57:14 -0300 Subject: [PATCH 2/5] remove feature gate around gpdma for usart --- embassy-stm32/src/usart/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/embassy-stm32/src/usart/mod.rs b/embassy-stm32/src/usart/mod.rs index 333e01e36c..f129a9f92b 100644 --- a/embassy-stm32/src/usart/mod.rs +++ b/embassy-stm32/src/usart/mod.rs @@ -1800,9 +1800,7 @@ pub use buffered::*; pub use crate::usart::buffered::InterruptHandler as BufferedInterruptHandler; mod buffered; -#[cfg(not(gpdma))] mod ringbuffered; -#[cfg(not(gpdma))] pub use ringbuffered::RingBufferedUartRx; #[cfg(any(usart_v1, usart_v2))] From 1759027949e3c4081bd603ca4f172b546cdc7411 Mon Sep 17 00:00:00 2001 From: Lucas Martins Date: Wed, 30 Oct 2024 16:57:31 -0300 Subject: [PATCH 3/5] add ringbuffer test --- .../stm32h5/src/bin/usart_dma_ringbuffer.rs | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 examples/stm32h5/src/bin/usart_dma_ringbuffer.rs diff --git a/examples/stm32h5/src/bin/usart_dma_ringbuffer.rs b/examples/stm32h5/src/bin/usart_dma_ringbuffer.rs new file mode 100644 index 0000000000..bf491d6b4c --- /dev/null +++ b/examples/stm32h5/src/bin/usart_dma_ringbuffer.rs @@ -0,0 +1,75 @@ + +#![no_std] +#![no_main] + +use {defmt_rtt as _, panic_probe as _}; + +use embassy_time::Timer; +use embassy_executor::Spawner; +use embassy_stm32::{bind_interrupts, peripherals, usart::{self, Uart}}; +use defmt::{debug, info, error}; +use static_cell::StaticCell; + +/*--- Main ---------------------------------------------------------------------------------------*/ + +bind_interrupts!( + struct Irqs { + USART3 => usart::InterruptHandler; + } +); + +/// this test will print all the bytes received in it's serial port +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + + let mut config = usart::Config::default(); + config.baudrate = 115_200; + info!("debug uart baudrate: {}", config.baudrate); + + let port = Uart::new( + p.USART3, // periph + p.PD9, // rx, + p.PD8, // tx, + Irqs, // prove irq bound + p.GPDMA2_CH0, // tx_dma + p.GPDMA2_CH1, // rx_dma + config, + ) + .unwrap(); + + const S: usize = 2000; + static BUFF: StaticCell<[u8; S]> = StaticCell::new(); + let b = BUFF.init_with(|| [0_u8; S]); + + let (mut port_tx, port_rx) = port.split(); + let mut port_rx = port_rx.into_ring_buffered(b); + + _ = port_tx.write(b"hello there\n").await; + + let mut read_buff = [0_u8; 256]; + let mut count = 0; + + loop { + if let Ok(c) = port_rx.read(&mut read_buff) + .await + .map_err(|e| { + error!("read error: {}", e); + }) + + { + if let Ok(s) = core::str::from_utf8(&read_buff) + .map_err(|e| { + error!("str parse error: {}", defmt::Debug2Format(&e)); + }) + { + count += c; + + debug!("total_rx: {} delta: {} -- rx: {}", count, c, s); + } + } + + // prove the dma is reading in the background by pausing the current read for a while + Timer::after_millis(500).await; + } +} From af2aedb588535aa2dd32105bc827913c741a3792 Mon Sep 17 00:00:00 2001 From: Lucas Martins Date: Wed, 30 Oct 2024 17:13:50 -0300 Subject: [PATCH 4/5] rustfmt --- embassy-stm32/src/dma/gpdma.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/embassy-stm32/src/dma/gpdma.rs b/embassy-stm32/src/dma/gpdma.rs index 0f40bdc809..f5f15ba7dc 100644 --- a/embassy-stm32/src/dma/gpdma.rs +++ b/embassy-stm32/src/dma/gpdma.rs @@ -2,13 +2,13 @@ use core::future::{poll_fn, Future}; use core::pin::Pin; -use core::sync::atomic::{fence, AtomicUsize, Ordering, AtomicBool}; +use core::sync::atomic::{fence, AtomicBool, AtomicUsize, Ordering}; use core::task::{Context, Poll, Waker}; use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; use embassy_sync::waitqueue::AtomicWaker; -use super::ringbuffer::{DmaCtrl, ReadableDmaRingBuffer, self}; +use super::ringbuffer::{self, DmaCtrl, ReadableDmaRingBuffer}; use super::word::{Word, WordSize}; use super::{AnyChannel, Channel, Dir, Request, STATE}; use crate::interrupt::typelevel::Interrupt; @@ -113,7 +113,9 @@ impl AnyChannel { if circ { // wake the future for the half-transfer event if sr.htf() { - ch.fcr().write(|w| { w.set_htf(true); }); + ch.fcr().write(|w| { + w.set_htf(true); + }); state.waker.wake(); return; } @@ -381,7 +383,6 @@ impl<'a> Future for Transfer<'a> { } } - /// Dma control interface for this DMA Type struct DmaCtrlImpl<'a> { channel: PeripheralRef<'a, AnyChannel>, @@ -444,12 +445,12 @@ impl RingBuffer { Dir::MemoryToPeripheral => { w.set_uda(false); w.set_usa(true); - }, + } Dir::PeripheralToMemory => { w.set_uda(true); w.set_usa(false); - }, + } } // lower 16 bits of the memory address @@ -600,7 +601,6 @@ impl<'a, W: Word> ReadableRingBuffer<'a, W> { RingBuffer::request_suspend(&info.dma.ch(info.num)); } - /// Request the transfer to stop. Use is_running() to see when the transfer is complete. pub fn request_stop(&mut self) { let info = self.channel.info(); From d6b67abefa28a33953c4c6d726ee347dff31e346 Mon Sep 17 00:00:00 2001 From: Lucas Martins Date: Wed, 30 Oct 2024 17:15:37 -0300 Subject: [PATCH 5/5] rustfmt example --- .../stm32h5/src/bin/usart_dma_ringbuffer.rs | 39 ++++++++----------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/examples/stm32h5/src/bin/usart_dma_ringbuffer.rs b/examples/stm32h5/src/bin/usart_dma_ringbuffer.rs index bf491d6b4c..24ba6f6f90 100644 --- a/examples/stm32h5/src/bin/usart_dma_ringbuffer.rs +++ b/examples/stm32h5/src/bin/usart_dma_ringbuffer.rs @@ -1,14 +1,13 @@ - #![no_std] #![no_main] -use {defmt_rtt as _, panic_probe as _}; - -use embassy_time::Timer; +use defmt::{debug, error, info}; use embassy_executor::Spawner; -use embassy_stm32::{bind_interrupts, peripherals, usart::{self, Uart}}; -use defmt::{debug, info, error}; +use embassy_stm32::usart::{self, Uart}; +use embassy_stm32::{bind_interrupts, peripherals}; +use embassy_time::Timer; use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; /*--- Main ---------------------------------------------------------------------------------------*/ @@ -28,15 +27,15 @@ async fn main(_spawner: Spawner) { info!("debug uart baudrate: {}", config.baudrate); let port = Uart::new( - p.USART3, // periph - p.PD9, // rx, - p.PD8, // tx, - Irqs, // prove irq bound + p.USART3, // periph + p.PD9, // rx, + p.PD8, // tx, + Irqs, // prove irq bound p.GPDMA2_CH0, // tx_dma p.GPDMA2_CH1, // rx_dma config, ) - .unwrap(); + .unwrap(); const S: usize = 2000; static BUFF: StaticCell<[u8; S]> = StaticCell::new(); @@ -51,18 +50,12 @@ async fn main(_spawner: Spawner) { let mut count = 0; loop { - if let Ok(c) = port_rx.read(&mut read_buff) - .await - .map_err(|e| { - error!("read error: {}", e); - }) - - { - if let Ok(s) = core::str::from_utf8(&read_buff) - .map_err(|e| { - error!("str parse error: {}", defmt::Debug2Format(&e)); - }) - { + if let Ok(c) = port_rx.read(&mut read_buff).await.map_err(|e| { + error!("read error: {}", e); + }) { + if let Ok(s) = core::str::from_utf8(&read_buff).map_err(|e| { + error!("str parse error: {}", defmt::Debug2Format(&e)); + }) { count += c; debug!("total_rx: {} delta: {} -- rx: {}", count, c, s);