//! DMA ping-pong/double-buffer transfer example for MCXA276. //! //! NOTE: this is a "raw dma" example! It exists as a proof of concept, as we don't have //! a high-level and safe API for. It should not be taken as typical, recommended, or //! stable usage! //! //! This example demonstrates two approaches for ping-pong/double-buffering: //! //! ## Approach 1: Scatter/Gather with linked TCDs (manual) //! - Two TCDs link to each other for alternating transfers //! - Uses custom handler that delegates to on_interrupt() then signals completion //! - Note: With ESG=1, DONE bit is cleared by hardware when next TCD loads, //! so we need an AtomicBool to track completion //! //! ## Approach 2: Half-transfer interrupt with wait_half() (NEW!) //! - Single continuous transfer over entire buffer //! - Uses half-transfer interrupt to know when first half is ready //! - Application can process first half while second half is being filled //! //! # Embassy-style features demonstrated: //! - `DmaChannel::new()` for channel creation //! - Scatter/gather with linked TCDs //! - Custom handler that delegates to HAL's `on_interrupt()` (best practice) //! - Standard `DmaCh1InterruptHandler` with `bind_interrupts!` macro //! - NEW: `wait_half()` for half-transfer interrupt handling #![no_std] #![no_main] use embassy_executor::Spawner; use embassy_mcxa::clocks::config::Div8; use embassy_mcxa::dma::{DmaChannel, Tcd, TransferOptions}; use embassy_mcxa::pac; use static_cell::ConstStaticCell; use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; // Source and destination buffers for Approach 1 (scatter/gather) static SRC: ConstStaticCell<[u32; 8]> = ConstStaticCell::new([1, 2, 3, 4, 5, 6, 7, 8]); static DST: ConstStaticCell<[u32; 8]> = ConstStaticCell::new([0; 8]); // Source and destination buffers for Approach 2 (wait_half) static SRC2: ConstStaticCell<[u32; 8]> = ConstStaticCell::new([0xA1, 0xA2, 0xA3, 0xA4, 0xB1, 0xB2, 0xB3, 0xB4]); static DST2: ConstStaticCell<[u32; 8]> = ConstStaticCell::new([0; 8]); // TCD pool for scatter/gather - must be 32-byte aligned #[repr(C, align(32))] struct TcdPool([Tcd; 2]); static TCD_POOL: ConstStaticCell = ConstStaticCell::new(TcdPool( [Tcd { saddr: 0, soff: 0, attr: 0, nbytes: 0, slast: 0, daddr: 0, doff: 0, citer: 0, dlast_sga: 0, csr: 0, biter: 0, }; 2], )); #[embassy_executor::main] async fn main(_spawner: Spawner) { // Small delay to allow probe-rs to attach after reset for _ in 0..100_000 { cortex_m::asm::nop(); } let mut cfg = hal::config::Config::default(); cfg.clock_cfg.sirc.fro_12m_enabled = true; cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); let p = hal::init(cfg); defmt::info!("DMA ping-pong transfer example starting..."); defmt::info!("EDMA ping-pong transfer example begin."); // Initialize buffers let src = SRC.take(); let dst = DST.take(); defmt::info!("Source Buffer: {=[?]}", src.as_slice()); defmt::info!("Destination Buffer (before): {=[?]}", dst.as_slice()); defmt::info!("Configuring ping-pong DMA with Embassy-style API..."); let dma_ch0 = DmaChannel::new(p.DMA_CH0); // Configure ping-pong transfer using direct TCD access: // This sets up TCD0 and TCD1 in RAM, and loads TCD0 into the channel. // TCD0 transfers first half (SRC[0..4] -> DST[0..4]), links to TCD1. // TCD1 transfers second half (SRC[4..8] -> DST[4..8]), links to TCD0. let tcds = &mut TCD_POOL.take().0; let half_len = 4usize; let half_bytes = (half_len * 4) as u32; unsafe { let tcd0_addr = &tcds[0] as *const _ as u32; let tcd1_addr = &tcds[1] as *const _ as u32; // TCD0: First half -> Links to TCD1 tcds[0] = Tcd { saddr: src.as_ptr() as u32, soff: 4, attr: 0x0202, // 32-bit src/dst nbytes: half_bytes, slast: 0, daddr: dst.as_mut_ptr() as u32, doff: 4, citer: 1, dlast_sga: tcd1_addr as i32, csr: 0x0012, // ESG | INTMAJOR biter: 1, }; // TCD1: Second half -> Links to TCD0 tcds[1] = Tcd { saddr: src.as_ptr().add(half_len) as u32, soff: 4, attr: 0x0202, nbytes: half_bytes, slast: 0, daddr: dst.as_mut_ptr().add(half_len) as u32, doff: 4, citer: 1, dlast_sga: tcd0_addr as i32, csr: 0x0012, biter: 1, }; // Load TCD0 into hardware registers dma_ch0.load_tcd(&tcds[0]); } defmt::info!("Triggering first half transfer..."); // Trigger first transfer (first half: SRC[0..4] -> DST[0..4]) unsafe { dma_ch0.trigger_start(); } let tcd = dma_ch0.tcd(); // Wait for first half loop { if tcd.tcd_saddr().read().bits() != src.as_ptr() as u32 { break; } } defmt::info!("First half transferred."); defmt::info!("Triggering second half transfer..."); // Trigger second transfer (second half: SRC[4..8] -> DST[4..8]) unsafe { dma_ch0.trigger_start(); } // Wait for second half loop { if tcd.tcd_saddr().read().bits() != unsafe { src.as_ptr().add(half_len) } as u32 { break; } } defmt::info!("Second half transferred."); defmt::info!("EDMA ping-pong transfer example finish."); defmt::info!("Destination Buffer (after): {=[?]}", dst.as_slice()); // Verify: DST should match SRC let mismatch = src != dst; if mismatch { defmt::error!("FAIL: Approach 1 mismatch detected!"); } else { defmt::info!("PASS: Approach 1 data verified."); } // ========================================================================= // Approach 2: Half-Transfer Interrupt with wait_half() (NEW!) // ========================================================================= // // This approach uses a single continuous DMA transfer with half-transfer // interrupt enabled. The wait_half() method allows you to be notified // when the first half of the buffer is complete, so you can process it // while the second half is still being filled. // // Benefits: // - Simpler setup (no TCD pool needed) // - True async/await support // - Good for streaming data processing defmt::info!("--- Approach 2: wait_half() demo ---"); // Enable DMA CH1 interrupt unsafe { cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH1); } // Initialize approach 2 buffers let src2 = SRC2.take(); let dst2 = DST2.take(); defmt::info!("SRC2: {=[?]}", src2.as_slice()); let dma_ch1 = DmaChannel::new(p.DMA_CH1); // Configure transfer with half-transfer interrupt enabled let mut options = TransferOptions::default(); options.half_transfer_interrupt = true; // Enable half-transfer interrupt options.complete_transfer_interrupt = true; defmt::info!("Starting transfer with half_transfer_interrupt..."); // Create the transfer let mut transfer = dma_ch1.mem_to_mem(src2, dst2, options).unwrap(); // Wait for half-transfer (first 4 elements) defmt::info!("Waiting for first half..."); let _ok = transfer.wait_half().await.unwrap(); defmt::info!("Half-transfer complete!"); // Wait for complete transfer defmt::info!("Waiting for second half..."); transfer.await.unwrap(); defmt::info!("Transfer complete! Full DST2: {=[?]}", dst2.as_slice()); // Verify approach 2 let mismatch2 = src2 != dst2; if mismatch2 { defmt::error!("FAIL: Approach 2 mismatch!"); } else { defmt::info!("PASS: Approach 2 verified."); } defmt::info!("=== All ping-pong demos complete ==="); }