Skip to content

Commit 1837155

Browse files
committed
Push DMA data to PIO TX FIFO through ping-pong
1 parent 217b683 commit 1837155

File tree

1 file changed

+90
-0
lines changed

1 file changed

+90
-0
lines changed

embassy-rp/src/pio/mod.rs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! PIO driver.
22
use core::future::Future;
33
use core::marker::PhantomData;
4+
use core::ops::ControlFlow;
45
use core::pin::Pin as FuturePin;
56
use core::sync::atomic::{AtomicU8, AtomicU32, Ordering, compiler_fence};
67
use core::task::{Context, Poll};
@@ -530,6 +531,95 @@ impl<'d, PIO: Instance, const SM: usize> StateMachineTx<'d, PIO, SM> {
530531
pub fn dma_push_repeated<'a, C: Channel, W: Word>(&'a mut self, ch: Peri<'a, C>, len: usize) -> Transfer<'a, C> {
531532
unsafe { dma::write_repeated(ch, PIO::PIO.txf(SM).as_ptr(), len, Self::dreq()) }
532533
}
534+
535+
/// Feed the TX FIFO a continuous stream of data using a 2 alternating buffers.
536+
///
537+
/// The initial data in each buffer isn't immediately sent. Instead, the callback will be called once before the DMA
538+
/// transfer starts, to initialize the first buffer. After this, the callback will be called each time a new
539+
/// transfer starts to provide the data that will be sent with the transfer after it. The user is responsible for
540+
/// ensuring that the callback finishes in time for the buffers to swap.
541+
pub async fn dma_push_ping_pong<'a, C1: Channel, C2: Channel, W: Word, F>(
542+
&'a mut self,
543+
mut ch1: Peri<'a, C1>,
544+
mut ch2: Peri<'a, C2>,
545+
data1: &'a mut [W],
546+
data2: &'a mut [W],
547+
mut fill_buffer_callback: F,
548+
) where
549+
F: FnMut(&mut [W]) -> ControlFlow<()>,
550+
{
551+
let init_dma_channel = |regs: pac::dma::Channel, chain_target: u8, buffer: &[W]| {
552+
regs.read_addr().write_value(buffer.as_ptr() as u32);
553+
regs.write_addr().write_value(PIO::PIO.txf(SM).as_ptr() as u32);
554+
555+
#[cfg(feature = "rp2040")]
556+
regs.trans_count().write(|w| *w = buffer.len() as u32);
557+
#[cfg(feature = "_rp235x")]
558+
regs.trans_count().write(|w| w.set_count(buffer.len() as u32));
559+
560+
// don't use trigger register since we don't want the channel to start yet
561+
regs.al1_ctrl().write(|w| {
562+
// SAFETY: this register is an alias for ctrl_trig, see embassy-rs/rp-pac#12
563+
let w: &mut rp_pac::dma::regs::CtrlTrig = unsafe { core::mem::transmute(w) };
564+
w.set_treq_sel(Self::dreq());
565+
w.set_data_size(W::size());
566+
w.set_incr_read(true);
567+
w.set_incr_write(false);
568+
w.set_en(true);
569+
570+
// trigger other channel when finished
571+
w.set_chain_to(chain_target);
572+
});
573+
};
574+
575+
// initialize both DMA channels
576+
init_dma_channel(ch1.regs(), ch2.number(), data1);
577+
init_dma_channel(ch2.regs(), ch1.number(), data2);
578+
579+
trace!("Fill initial ping buffer");
580+
if let ControlFlow::Break(()) = fill_buffer_callback(data1) {
581+
return;
582+
}
583+
584+
// trigger ping dma channel by writing to a TRIG register
585+
ch1.regs().ctrl_trig().modify(|_| {});
586+
587+
loop {
588+
trace!("Fill pong buffer");
589+
if let ControlFlow::Break(()) = fill_buffer_callback(data2) {
590+
break;
591+
}
592+
593+
trace!("Waiting for ping transfer to finish");
594+
Transfer::new(ch1.reborrow()).await;
595+
596+
// re-init DMA 1 (without triggering it)
597+
ch1.regs().read_addr().write_value(data1.as_ptr() as u32);
598+
599+
trace!("Fill ping buffer");
600+
if let ControlFlow::Break(()) = fill_buffer_callback(data1) {
601+
break;
602+
}
603+
604+
trace!("Waiting for pong transfer");
605+
Transfer::new(ch2.reborrow()).await;
606+
607+
// re-init DMA 2 (without triggering it)
608+
ch2.regs().read_addr().write_value(data2.as_ptr() as u32);
609+
}
610+
611+
// turn off DMA channels
612+
ch1.regs().al1_ctrl().modify(|w| {
613+
// SAFETY: this register is an alias for ctrl_trig, see embassy-rs/rp-pac#12
614+
let w: &mut rp_pac::dma::regs::CtrlTrig = unsafe { core::mem::transmute(w) };
615+
w.set_en(false);
616+
});
617+
ch2.regs().al1_ctrl().modify(|w| {
618+
// SAFETY: this register is an alias for ctrl_trig, see embassy-rs/rp-pac#12
619+
let w: &mut rp_pac::dma::regs::CtrlTrig = unsafe { core::mem::transmute(w) };
620+
w.set_en(false);
621+
});
622+
}
533623
}
534624

535625
/// A type representing a single PIO state machine.

0 commit comments

Comments
 (0)