|
1 | 1 | //! PIO driver. |
2 | 2 | use core::future::Future; |
3 | 3 | use core::marker::PhantomData; |
| 4 | +use core::ops::ControlFlow; |
4 | 5 | use core::pin::Pin as FuturePin; |
5 | 6 | use core::sync::atomic::{Ordering, compiler_fence}; |
6 | 7 | use core::task::{Context, Poll}; |
@@ -531,6 +532,95 @@ impl<'d, PIO: Instance, const SM: usize> StateMachineTx<'d, PIO, SM> { |
531 | 532 | pub fn dma_push_repeated<'a, C: Channel, W: Word>(&'a mut self, ch: Peri<'a, C>, len: usize) -> Transfer<'a, C> { |
532 | 533 | unsafe { dma::write_repeated(ch, PIO::PIO.txf(SM).as_ptr(), len, Self::dreq()) } |
533 | 534 | } |
| 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 | + } |
534 | 624 | } |
535 | 625 |
|
536 | 626 | /// A type representing a single PIO state machine. |
|
0 commit comments