Skip to content

Commit bd96479

Browse files
committed
Push DMA data to PIO TX FIFO through ping-pong
1 parent 2a79c55 commit bd96479

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

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

0 commit comments

Comments
 (0)