From 2dd5229c8c4fea6de7a7d52cedc6b6490d567ecf Mon Sep 17 00:00:00 2001 From: James Munns Date: Tue, 9 Dec 2025 16:48:43 +0100 Subject: Use saddr read instead of interrupt to avoid double handler definition --- embassy-mcxa/src/dma.rs | 1 + examples/mcxa/src/bin/raw_dma_channel_link.rs | 1 - .../mcxa/src/bin/raw_dma_ping_pong_transfer.rs | 55 +++------- examples/mcxa/src/bin/raw_dma_scatter_gather.rs | 115 +++++++++------------ 4 files changed, 62 insertions(+), 110 deletions(-) diff --git a/embassy-mcxa/src/dma.rs b/embassy-mcxa/src/dma.rs index 1293f8445..ff9ebeb05 100644 --- a/embassy-mcxa/src/dma.rs +++ b/embassy-mcxa/src/dma.rs @@ -1659,6 +1659,7 @@ impl DmaChannel { /// This matches the hardware layout (32 bytes). #[repr(C, align(32))] #[derive(Clone, Copy, Debug, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Tcd { pub saddr: u32, pub soff: i16, diff --git a/examples/mcxa/src/bin/raw_dma_channel_link.rs b/examples/mcxa/src/bin/raw_dma_channel_link.rs index 987f1ba43..74785e4f3 100644 --- a/examples/mcxa/src/bin/raw_dma_channel_link.rs +++ b/examples/mcxa/src/bin/raw_dma_channel_link.rs @@ -15,7 +15,6 @@ //! - `DmaChannel::new()` for channel creation //! - `DmaChannel::is_done()` and `clear_done()` helper methods //! - Channel linking with `set_minor_link()` and `set_major_link()` -//! - Standard `DmaCh*InterruptHandler` with `bind_interrupts!` macro #![no_std] #![no_main] diff --git a/examples/mcxa/src/bin/raw_dma_ping_pong_transfer.rs b/examples/mcxa/src/bin/raw_dma_ping_pong_transfer.rs index 4a64b2498..51a7bc275 100644 --- a/examples/mcxa/src/bin/raw_dma_ping_pong_transfer.rs +++ b/examples/mcxa/src/bin/raw_dma_ping_pong_transfer.rs @@ -27,12 +27,10 @@ #![no_std] #![no_main] -use core::sync::atomic::{AtomicBool, Ordering}; - use embassy_executor::Spawner; use embassy_mcxa::clocks::config::Div8; -use embassy_mcxa::dma::{self, DmaChannel, Tcd, TransferOptions}; -use embassy_mcxa::{bind_interrupts, pac}; +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 _}; @@ -64,32 +62,6 @@ static TCD_POOL: ConstStaticCell = ConstStaticCell::new(TcdPool( }; 2], )); -// AtomicBool to track scatter/gather completion -// Note: With ESG=1, DONE bit is cleared by hardware when next TCD loads, -// so we need this flag to detect when each transfer completes -static TRANSFER_DONE: AtomicBool = AtomicBool::new(false); - -// Custom handler for scatter/gather that delegates to HAL's on_interrupt() -// This follows the "interrupts as threads" pattern - the handler does minimal work -// (delegates to HAL + sets a flag) and the main task does the actual processing -pub struct PingPongDmaHandler; - -impl embassy_mcxa::interrupt::typelevel::Handler for PingPongDmaHandler { - unsafe fn on_interrupt() { - // Delegate to HAL's on_interrupt() which clears INT flag and wakes wakers - dma::on_interrupt(0); - // Signal completion for polling (needed because ESG clears DONE bit) - TRANSFER_DONE.store(true, Ordering::Release); - } -} - -// Bind DMA channel interrupts -// CH0: Custom handler for scatter/gather (delegates to on_interrupt + sets flag) -// CH1: Standard handler for wait_half() demo -bind_interrupts!(struct Irqs { - DMA_CH0 => PingPongDmaHandler; -}); - #[embassy_executor::main] async fn main(_spawner: Spawner) { // Small delay to allow probe-rs to attach after reset @@ -121,12 +93,12 @@ async fn main(_spawner: Spawner) { // 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. - unsafe { - let tcds = &mut TCD_POOL.take().0; + let tcds = &mut TCD_POOL.take().0; - let half_len = 4usize; - let half_bytes = (half_len * 4) as u32; + 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; @@ -171,11 +143,13 @@ async fn main(_spawner: Spawner) { dma_ch0.trigger_start(); } + let tcd = dma_ch0.tcd(); // Wait for first half - while !TRANSFER_DONE.load(Ordering::Acquire) { - cortex_m::asm::nop(); + loop { + if tcd.tcd_saddr().read().bits() != src.as_ptr() as u32 { + break; + } } - TRANSFER_DONE.store(false, Ordering::Release); defmt::info!("First half transferred."); defmt::info!("Triggering second half transfer..."); @@ -186,10 +160,11 @@ async fn main(_spawner: Spawner) { } // Wait for second half - while !TRANSFER_DONE.load(Ordering::Acquire) { - cortex_m::asm::nop(); + loop { + if tcd.tcd_saddr().read().bits() != unsafe { src.as_ptr().add(half_len) } as u32 { + break; + } } - TRANSFER_DONE.store(false, Ordering::Release); defmt::info!("Second half transferred."); diff --git a/examples/mcxa/src/bin/raw_dma_scatter_gather.rs b/examples/mcxa/src/bin/raw_dma_scatter_gather.rs index 057e56826..eb9960764 100644 --- a/examples/mcxa/src/bin/raw_dma_scatter_gather.rs +++ b/examples/mcxa/src/bin/raw_dma_scatter_gather.rs @@ -16,18 +16,15 @@ #![no_std] #![no_main] -use core::sync::atomic::{AtomicBool, Ordering}; - use embassy_executor::Spawner; -use embassy_mcxa::bind_interrupts; use embassy_mcxa::clocks::config::Div8; -use embassy_mcxa::dma::{self, DmaChannel, Tcd}; +use embassy_mcxa::dma::{DmaChannel, Tcd}; use static_cell::ConstStaticCell; use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; // Source and destination buffers -static SRC: ConstStaticCell<[u32; 8]> = ConstStaticCell::new([1, 2, 3, 4, 5, 6, 7, 8]); -static DST: ConstStaticCell<[u32; 8]> = ConstStaticCell::new([0; 8]); +static SRC: ConstStaticCell<[u32; 12]> = ConstStaticCell::new([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); +static DST: ConstStaticCell<[u32; 12]> = ConstStaticCell::new([0; 12]); // TCD pool for scatter/gather - must be 32-byte aligned #[repr(C, align(32))] @@ -49,33 +46,6 @@ static TCD_POOL: ConstStaticCell = ConstStaticCell::new(TcdPool( }; 2], )); -// AtomicBool to track scatter/gather completion -// Note: With ESG=1, DONE bit is cleared by hardware when next TCD loads, -// so we need this flag to detect when each transfer completes -static TRANSFER_DONE: AtomicBool = AtomicBool::new(false); - -// Custom handler for scatter/gather that delegates to HAL's on_interrupt() -// This follows the "interrupts as threads" pattern - the handler does minimal work -// (delegates to HAL + sets a flag) and the main task does the actual processing -pub struct ScatterGatherDmaHandler; - -impl embassy_mcxa::interrupt::typelevel::Handler - for ScatterGatherDmaHandler -{ - unsafe fn on_interrupt() { - // Delegate to HAL's on_interrupt() which clears INT flag and wakes wakers - dma::on_interrupt(0); - // Signal completion for polling (needed because ESG clears DONE bit) - TRANSFER_DONE.store(true, Ordering::Release); - } -} - -// Bind DMA channel interrupt -// Custom handler for scatter/gather (delegates to on_interrupt + sets flag) -bind_interrupts!(struct Irqs { - DMA_CH0 => ScatterGatherDmaHandler; -}); - #[embassy_executor::main] async fn main(_spawner: Spawner) { // Small delay to allow probe-rs to attach after reset @@ -101,43 +71,46 @@ async fn main(_spawner: Spawner) { defmt::info!("Configuring scatter-gather DMA with Embassy-style API..."); let dma_ch0 = DmaChannel::new(p.DMA_CH0); + let src_ptr = src.as_ptr(); + let dst_ptr = dst.as_mut_ptr(); // Configure scatter-gather 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]), then loads TCD1. - // TCD1 transfers second half (SRC[4..8] -> DST[4..8]), last TCD. + // TCD1 transfers second half (SRC[4..12] -> DST[4..12]), last TCD. unsafe { let tcds = &mut TCD_POOL.take().0; - let src_ptr = src.as_ptr(); - let dst_ptr = dst.as_mut_ptr(); - - let num_tcds = 2usize; - let chunk_len = 4usize; // 8 / 2 - let chunk_bytes = (chunk_len * 4) as u32; - - for i in 0..num_tcds { - let is_last = i == num_tcds - 1; - let next_tcd_addr = if is_last { - 0 // No next TCD - } else { - &tcds[i + 1] as *const _ as u32 - }; - - tcds[i] = Tcd { - saddr: src_ptr.add(i * chunk_len) as u32, - soff: 4, - attr: 0x0202, // 32-bit src/dst - nbytes: chunk_bytes, - slast: 0, - daddr: dst_ptr.add(i * chunk_len) as u32, - doff: 4, - citer: 1, - dlast_sga: next_tcd_addr as i32, - // ESG (scatter/gather) for non-last, INTMAJOR for all - csr: if is_last { 0x0002 } else { 0x0012 }, - biter: 1, - }; - } + + // In the first transfer, copy + tcds[0] = Tcd { + saddr: src_ptr as u32, + soff: 4, + attr: 0x0202, // 32-bit src/dst + nbytes: 4 * 4, + slast: 0, + daddr: dst_ptr as u32, + doff: 4, + citer: 1, + dlast_sga: tcds.as_ptr().add(1) as i32, + // ESG (scatter/gather) for non-last, INTMAJOR for all + csr: 0x0012, + biter: 1, + }; + + tcds[1] = Tcd { + saddr: src_ptr.add(4) as u32, + soff: 4, + attr: 0x0202, // 32-bit src/dst + nbytes: 8 * 4, + slast: 0, + daddr: dst_ptr.add(4) as u32, + doff: 4, + citer: 1, + dlast_sga: 0, + // ESG (scatter/gather) for non-last, INTMAJOR for all + csr: 0x0002, + biter: 1, + }; // Load TCD0 into hardware registers dma_ch0.load_tcd(&tcds[0]); @@ -145,6 +118,8 @@ async fn main(_spawner: Spawner) { defmt::info!("Triggering first half transfer..."); + let tcd = dma_ch0.tcd(); + // Trigger first transfer (first half: SRC[0..4] -> DST[0..4]) // TCD0 is currently loaded. unsafe { @@ -152,10 +127,13 @@ async fn main(_spawner: Spawner) { } // Wait for first half - while !TRANSFER_DONE.load(Ordering::Acquire) { - cortex_m::asm::nop(); + loop { + if tcd.tcd_saddr().read().bits() != src_ptr as u32 { + defmt::info!("saddr: {=u32}", tcd.tcd_saddr().read().bits()); + defmt::info!("srptr: {=u32}", src_ptr as u32); + break; + } } - TRANSFER_DONE.store(false, Ordering::Release); defmt::info!("First half transferred."); defmt::info!("Triggering second half transfer..."); @@ -167,10 +145,9 @@ async fn main(_spawner: Spawner) { } // Wait for second half - while !TRANSFER_DONE.load(Ordering::Acquire) { + while !dma_ch0.is_done() { cortex_m::asm::nop(); } - TRANSFER_DONE.store(false, Ordering::Release); defmt::info!("Second half transferred."); -- cgit