From e962f5568a9f6433dcd6ad3e41d3faabb8b7c552 Mon Sep 17 00:00:00 2001 From: James Munns Date: Tue, 9 Dec 2025 15:52:12 +0100 Subject: Clean up remaining examples, move some to "raw" examples --- embassy-mcxa/src/adc.rs | 2 +- embassy-mcxa/src/clkout.rs | 2 +- embassy-mcxa/src/clocks/periph_helpers.rs | 6 +- embassy-mcxa/src/i2c/controller.rs | 6 +- embassy-mcxa/src/interrupt.rs | 2 +- embassy-mcxa/src/lpuart/buffered.rs | 2 +- embassy-mcxa/src/ostimer.rs | 8 +- examples/mcxa/src/bin/dma_channel_link.rs | 331 -------------------- examples/mcxa/src/bin/dma_interleave_transfer.rs | 169 ---------- examples/mcxa/src/bin/dma_memset.rs | 125 -------- examples/mcxa/src/bin/dma_ping_pong_transfer.rs | 347 --------------------- examples/mcxa/src/bin/dma_scatter_gather.rs | 234 -------------- .../mcxa/src/bin/dma_scatter_gather_builder.rs | 6 +- examples/mcxa/src/bin/raw_dma_channel_link.rs | 279 +++++++++++++++++ .../mcxa/src/bin/raw_dma_interleave_transfer.rs | 141 +++++++++ examples/mcxa/src/bin/raw_dma_memset.rs | 129 ++++++++ .../mcxa/src/bin/raw_dma_ping_pong_transfer.rs | 269 ++++++++++++++++ examples/mcxa/src/bin/raw_dma_scatter_gather.rs | 188 +++++++++++ 18 files changed, 1023 insertions(+), 1223 deletions(-) delete mode 100644 examples/mcxa/src/bin/dma_channel_link.rs delete mode 100644 examples/mcxa/src/bin/dma_interleave_transfer.rs delete mode 100644 examples/mcxa/src/bin/dma_memset.rs delete mode 100644 examples/mcxa/src/bin/dma_ping_pong_transfer.rs delete mode 100644 examples/mcxa/src/bin/dma_scatter_gather.rs create mode 100644 examples/mcxa/src/bin/raw_dma_channel_link.rs create mode 100644 examples/mcxa/src/bin/raw_dma_interleave_transfer.rs create mode 100644 examples/mcxa/src/bin/raw_dma_memset.rs create mode 100644 examples/mcxa/src/bin/raw_dma_ping_pong_transfer.rs create mode 100644 examples/mcxa/src/bin/raw_dma_scatter_gather.rs diff --git a/embassy-mcxa/src/adc.rs b/embassy-mcxa/src/adc.rs index 7475299ba..b5ec5983f 100644 --- a/embassy-mcxa/src/adc.rs +++ b/embassy-mcxa/src/adc.rs @@ -4,7 +4,7 @@ use core::sync::atomic::{AtomicBool, Ordering}; use embassy_hal_internal::{Peri, PeripheralType}; use crate::clocks::periph_helpers::{AdcClockSel, AdcConfig, Div4}; -use crate::clocks::{Gate, PoweredClock, enable_and_reset}; +use crate::clocks::{enable_and_reset, Gate, PoweredClock}; use crate::pac; use crate::pac::adc1::cfg::{HptExdi, Pwrsel, Refsel, Tcmdres, Tprictrl, Tres}; use crate::pac::adc1::cmdh1::{Avgs, Cmpen, Next, Sts}; diff --git a/embassy-mcxa/src/clkout.rs b/embassy-mcxa/src/clkout.rs index 5b21f24b0..88c731df1 100644 --- a/embassy-mcxa/src/clkout.rs +++ b/embassy-mcxa/src/clkout.rs @@ -9,7 +9,7 @@ use core::marker::PhantomData; use embassy_hal_internal::Peri; pub use crate::clocks::periph_helpers::Div4; -use crate::clocks::{ClockError, PoweredClock, with_clocks}; +use crate::clocks::{with_clocks, ClockError, PoweredClock}; use crate::pac::mrcc0::mrcc_clkout_clksel::Mux; use crate::peripherals::CLKOUT; diff --git a/embassy-mcxa/src/clocks/periph_helpers.rs b/embassy-mcxa/src/clocks/periph_helpers.rs index fed5e558e..1ea7a99ed 100644 --- a/embassy-mcxa/src/clocks/periph_helpers.rs +++ b/embassy-mcxa/src/clocks/periph_helpers.rs @@ -108,7 +108,11 @@ impl Div4 { /// by 1, and `Div4::from_raw(15)` will divide the source by /// 16. pub const fn from_raw(n: u8) -> Option { - if n > 0b1111 { None } else { Some(Self(n)) } + if n > 0b1111 { + None + } else { + Some(Self(n)) + } } /// Store a specific divisor value that will divide the source diff --git a/embassy-mcxa/src/i2c/controller.rs b/embassy-mcxa/src/i2c/controller.rs index c27d508b0..182a53aea 100644 --- a/embassy-mcxa/src/i2c/controller.rs +++ b/embassy-mcxa/src/i2c/controller.rs @@ -3,15 +3,15 @@ use core::future::Future; use core::marker::PhantomData; -use embassy_hal_internal::Peri; use embassy_hal_internal::drop::OnDrop; +use embassy_hal_internal::Peri; use mcxa_pac::lpi2c0::mtdr::Cmd; use super::{Async, Blocking, Error, Instance, InterruptHandler, Mode, Result, SclPin, SdaPin}; -use crate::AnyPin; use crate::clocks::periph_helpers::{Div4, Lpi2cClockSel, Lpi2cConfig}; -use crate::clocks::{PoweredClock, enable_and_reset}; +use crate::clocks::{enable_and_reset, PoweredClock}; use crate::interrupt::typelevel::Interrupt; +use crate::AnyPin; /// Bus speed (nominal SCL, no clock stretching) #[derive(Clone, Copy, Default, PartialEq)] diff --git a/embassy-mcxa/src/interrupt.rs b/embassy-mcxa/src/interrupt.rs index c960af7a2..1b468c6cf 100644 --- a/embassy-mcxa/src/interrupt.rs +++ b/embassy-mcxa/src/interrupt.rs @@ -40,7 +40,7 @@ mod generated { use core::sync::atomic::{AtomicU16, AtomicU32, Ordering}; -pub use generated::interrupt::{Priority, typelevel}; +pub use generated::interrupt::{typelevel, Priority}; use crate::pac::Interrupt; diff --git a/embassy-mcxa/src/lpuart/buffered.rs b/embassy-mcxa/src/lpuart/buffered.rs index 34fdbb76c..8eb443ca7 100644 --- a/embassy-mcxa/src/lpuart/buffered.rs +++ b/embassy-mcxa/src/lpuart/buffered.rs @@ -3,8 +3,8 @@ use core::marker::PhantomData; use core::sync::atomic::{AtomicBool, Ordering}; use core::task::Poll; -use embassy_hal_internal::Peri; use embassy_hal_internal::atomic_ring_buffer::RingBuffer; +use embassy_hal_internal::Peri; use embassy_sync::waitqueue::AtomicWaker; use super::*; diff --git a/embassy-mcxa/src/ostimer.rs b/embassy-mcxa/src/ostimer.rs index 9e66e82d8..c51812e3d 100644 --- a/embassy-mcxa/src/ostimer.rs +++ b/embassy-mcxa/src/ostimer.rs @@ -32,7 +32,7 @@ use core::sync::atomic::{AtomicBool, Ordering}; use embassy_hal_internal::{Peri, PeripheralType}; use crate::clocks::periph_helpers::{OsTimerConfig, OstimerClockSel}; -use crate::clocks::{Gate, PoweredClock, assert_reset, enable_and_reset, is_reset_released, release_reset}; +use crate::clocks::{assert_reset, enable_and_reset, is_reset_released, release_reset, Gate, PoweredClock}; use crate::interrupt::InterruptExt; use crate::pac; @@ -521,11 +521,11 @@ pub mod time_driver { use embassy_time_driver as etd; use super::{ - ALARM_ACTIVE, ALARM_CALLBACK, ALARM_FLAG, ALARM_TARGET_TIME, EVTIMER_HI_MASK, EVTIMER_HI_SHIFT, - LOW_32_BIT_MASK, Regs, bin_to_gray, now_ticks_read, + bin_to_gray, now_ticks_read, Regs, ALARM_ACTIVE, ALARM_CALLBACK, ALARM_FLAG, ALARM_TARGET_TIME, + EVTIMER_HI_MASK, EVTIMER_HI_SHIFT, LOW_32_BIT_MASK, }; use crate::clocks::periph_helpers::{OsTimerConfig, OstimerClockSel}; - use crate::clocks::{PoweredClock, enable_and_reset}; + use crate::clocks::{enable_and_reset, PoweredClock}; use crate::pac; #[allow(non_camel_case_types)] diff --git a/examples/mcxa/src/bin/dma_channel_link.rs b/examples/mcxa/src/bin/dma_channel_link.rs deleted file mode 100644 index 2d757a636..000000000 --- a/examples/mcxa/src/bin/dma_channel_link.rs +++ /dev/null @@ -1,331 +0,0 @@ -//! DMA channel linking example for MCXA276. -//! -//! This example demonstrates DMA channel linking (minor and major loop linking): -//! - Channel 0: Transfers SRC_BUFFER to DEST_BUFFER0, with: -//! - Minor Link to Channel 1 (triggers CH1 after each minor loop) -//! - Major Link to Channel 2 (triggers CH2 after major loop completes) -//! - Channel 1: Transfers SRC_BUFFER to DEST_BUFFER1 (triggered by CH0 minor link) -//! - Channel 2: Transfers SRC_BUFFER to DEST_BUFFER2 (triggered by CH0 major link) -//! -//! # Embassy-style features demonstrated: -//! - `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] - -use core::fmt::Write as _; - -use embassy_executor::Spawner; -use embassy_mcxa::clocks::config::Div8; -use embassy_mcxa::dma::DmaChannel; -use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; -use embassy_mcxa::pac; -use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; - -// Buffers -static mut SRC_BUFFER: [u32; 4] = [1, 2, 3, 4]; -static mut DEST_BUFFER0: [u32; 4] = [0; 4]; -static mut DEST_BUFFER1: [u32; 4] = [0; 4]; -static mut DEST_BUFFER2: [u32; 4] = [0; 4]; - -/// Helper to print a buffer to UART -fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { - write!(tx, "{:?}", unsafe { core::slice::from_raw_parts(buf_ptr, len) }).ok(); -} - -#[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 channel link example starting..."); - - // DMA is initialized during hal::init() - no need to call ensure_init() - - let pac_periphs = unsafe { pac::Peripherals::steal() }; - let dma0 = &pac_periphs.dma0; - let edma = unsafe { &*pac::Edma0Tcd0::ptr() }; - - // Clear any residual state - for i in 0..3 { - let t = edma.tcd(i); - t.ch_csr().write(|w| w.erq().disable().done().clear_bit_by_one()); - t.ch_int().write(|w| w.int().clear_bit_by_one()); - t.ch_es().write(|w| w.err().clear_bit_by_one()); - t.ch_mux().write(|w| unsafe { w.bits(0) }); - } - - // Clear Global Halt/Error state - dma0.mp_csr().modify(|_, w| { - w.halt() - .normal_operation() - .hae() - .normal_operation() - .ecx() - .normal_operation() - .cx() - .normal_operation() - }); - - let config = Config { - baudrate_bps: 115_200, - ..Default::default() - }; - - let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); - let (mut tx, _rx) = lpuart.split(); - - tx.blocking_write(b"EDMA channel link example begin.\r\n\r\n").unwrap(); - - // Initialize buffers - unsafe { - SRC_BUFFER = [1, 2, 3, 4]; - DEST_BUFFER0 = [0; 4]; - DEST_BUFFER1 = [0; 4]; - DEST_BUFFER2 = [0; 4]; - } - - tx.blocking_write(b"Source Buffer: ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(SRC_BUFFER) as *const u32, 4); - tx.blocking_write(b"\r\n").unwrap(); - - tx.blocking_write(b"DEST0 (before): ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER0) as *const u32, 4); - tx.blocking_write(b"\r\n").unwrap(); - - tx.blocking_write(b"DEST1 (before): ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER1) as *const u32, 4); - tx.blocking_write(b"\r\n").unwrap(); - - tx.blocking_write(b"DEST2 (before): ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER2) as *const u32, 4); - tx.blocking_write(b"\r\n\r\n").unwrap(); - - tx.blocking_write(b"Configuring DMA channels with Embassy-style API...\r\n") - .unwrap(); - - let ch0 = DmaChannel::new(p.DMA_CH0); - let ch1 = DmaChannel::new(p.DMA_CH1); - let ch2 = DmaChannel::new(p.DMA_CH2); - - // Configure channels using direct TCD access (advanced feature demo) - // This example demonstrates channel linking which requires direct TCD manipulation - - // Helper to configure TCD for memory-to-memory transfer - // Parameters: channel, src, dst, width, nbytes (minor loop), count (major loop), interrupt - #[allow(clippy::too_many_arguments)] - unsafe fn configure_tcd( - edma: &embassy_mcxa::pac::edma_0_tcd0::RegisterBlock, - ch: usize, - src: u32, - dst: u32, - width: u8, - nbytes: u32, - count: u16, - enable_int: bool, - ) { - let t = edma.tcd(ch); - - // Reset channel state - t.ch_csr().write(|w| { - w.erq() - .disable() - .earq() - .disable() - .eei() - .no_error() - .ebw() - .disable() - .done() - .clear_bit_by_one() - }); - t.ch_es().write(|w| w.bits(0)); - t.ch_int().write(|w| w.int().clear_bit_by_one()); - - // Source/destination addresses - t.tcd_saddr().write(|w| w.saddr().bits(src)); - t.tcd_daddr().write(|w| w.daddr().bits(dst)); - - // Offsets: increment by width - t.tcd_soff().write(|w| w.soff().bits(width as u16)); - t.tcd_doff().write(|w| w.doff().bits(width as u16)); - - // Attributes: size = log2(width) - let size = match width { - 1 => 0, - 2 => 1, - 4 => 2, - _ => 0, - }; - t.tcd_attr().write(|w| w.ssize().bits(size).dsize().bits(size)); - - // Number of bytes per minor loop - t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(nbytes)); - - // Major loop: reset source address after major loop - let total_bytes = nbytes * count as u32; - t.tcd_slast_sda() - .write(|w| w.slast_sda().bits(-(total_bytes as i32) as u32)); - t.tcd_dlast_sga() - .write(|w| w.dlast_sga().bits(-(total_bytes as i32) as u32)); - - // Major loop count - t.tcd_biter_elinkno().write(|w| w.biter().bits(count)); - t.tcd_citer_elinkno().write(|w| w.citer().bits(count)); - - // Control/status: enable interrupt if requested - if enable_int { - t.tcd_csr().write(|w| w.intmajor().set_bit()); - } else { - t.tcd_csr().write(|w| w.intmajor().clear_bit()); - } - - cortex_m::asm::dsb(); - } - - unsafe { - // Channel 0: Transfer 16 bytes total (8 bytes per minor loop, 2 major iterations) - // Minor Link -> Channel 1 - // Major Link -> Channel 2 - configure_tcd( - edma, - 0, - core::ptr::addr_of!(SRC_BUFFER) as u32, - core::ptr::addr_of_mut!(DEST_BUFFER0) as u32, - 4, // src width - 8, // nbytes (minor loop = 2 words) - 2, // count (major loop = 2 iterations) - false, // no interrupt - ); - ch0.set_minor_link(1); // Link to CH1 after each minor loop - ch0.set_major_link(2); // Link to CH2 after major loop - - // Channel 1: Transfer 16 bytes (triggered by CH0 minor link) - configure_tcd( - edma, - 1, - core::ptr::addr_of!(SRC_BUFFER) as u32, - core::ptr::addr_of_mut!(DEST_BUFFER1) as u32, - 4, - 16, // full buffer in one minor loop - 1, // 1 major iteration - false, - ); - - // Channel 2: Transfer 16 bytes (triggered by CH0 major link) - configure_tcd( - edma, - 2, - core::ptr::addr_of!(SRC_BUFFER) as u32, - core::ptr::addr_of_mut!(DEST_BUFFER2) as u32, - 4, - 16, // full buffer in one minor loop - 1, // 1 major iteration - true, // enable interrupt - ); - } - - tx.blocking_write(b"Triggering Channel 0 (1st minor loop)...\r\n") - .unwrap(); - - // Trigger first minor loop of CH0 - unsafe { - ch0.trigger_start(); - } - - // Wait for CH1 to complete (triggered by CH0 minor link) - while !ch1.is_done() { - cortex_m::asm::nop(); - } - unsafe { - ch1.clear_done(); - } - - tx.blocking_write(b"CH1 done (via minor link).\r\n").unwrap(); - tx.blocking_write(b"Triggering Channel 0 (2nd minor loop)...\r\n") - .unwrap(); - - // Trigger second minor loop of CH0 - unsafe { - ch0.trigger_start(); - } - - // Wait for CH0 major loop to complete - while !ch0.is_done() { - cortex_m::asm::nop(); - } - unsafe { - ch0.clear_done(); - } - - tx.blocking_write(b"CH0 major loop done.\r\n").unwrap(); - - // Wait for CH2 to complete (triggered by CH0 major link) - // Using is_done() instead of AtomicBool - the standard interrupt handler - // clears the interrupt flag and wakes wakers, but DONE bit remains set - while !ch2.is_done() { - cortex_m::asm::nop(); - } - unsafe { - ch2.clear_done(); - } - - tx.blocking_write(b"CH2 done (via major link).\r\n\r\n").unwrap(); - - tx.blocking_write(b"EDMA channel link example finish.\r\n\r\n").unwrap(); - - tx.blocking_write(b"DEST0 (after): ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER0) as *const u32, 4); - tx.blocking_write(b"\r\n").unwrap(); - - tx.blocking_write(b"DEST1 (after): ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER1) as *const u32, 4); - tx.blocking_write(b"\r\n").unwrap(); - - tx.blocking_write(b"DEST2 (after): ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER2) as *const u32, 4); - tx.blocking_write(b"\r\n\r\n").unwrap(); - - // Verify all buffers match source - let mut success = true; - unsafe { - let src_ptr = core::ptr::addr_of!(SRC_BUFFER) as *const u32; - let dst0_ptr = core::ptr::addr_of!(DEST_BUFFER0) as *const u32; - let dst1_ptr = core::ptr::addr_of!(DEST_BUFFER1) as *const u32; - let dst2_ptr = core::ptr::addr_of!(DEST_BUFFER2) as *const u32; - - for i in 0..4 { - if *dst0_ptr.add(i) != *src_ptr.add(i) { - success = false; - } - if *dst1_ptr.add(i) != *src_ptr.add(i) { - success = false; - } - if *dst2_ptr.add(i) != *src_ptr.add(i) { - success = false; - } - } - } - - if success { - tx.blocking_write(b"PASS: Data verified.\r\n").unwrap(); - defmt::info!("PASS: Data verified."); - } else { - tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); - defmt::error!("FAIL: Mismatch detected!"); - } - - loop { - cortex_m::asm::wfe(); - } -} diff --git a/examples/mcxa/src/bin/dma_interleave_transfer.rs b/examples/mcxa/src/bin/dma_interleave_transfer.rs deleted file mode 100644 index 03441fc32..000000000 --- a/examples/mcxa/src/bin/dma_interleave_transfer.rs +++ /dev/null @@ -1,169 +0,0 @@ -//! DMA interleaved transfer example for MCXA276. -//! -//! This example demonstrates using DMA with custom source/destination offsets -//! to interleave data during transfer. -//! -//! # Embassy-style features demonstrated: -//! - `TransferOptions::default()` for configuration (used internally) -//! - DMA channel with `DmaChannel::new()` - -#![no_std] -#![no_main] - -use core::fmt::Write as _; - -use embassy_executor::Spawner; -use embassy_mcxa::clocks::config::Div8; -use embassy_mcxa::dma::DmaChannel; -use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; -use static_cell::ConstStaticCell; -use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; - -const BUFFER_LENGTH: usize = 16; -const HALF_BUFF_LENGTH: usize = BUFFER_LENGTH / 2; - -// Buffers in RAM -static SRC_BUFFER: ConstStaticCell<[u32; HALF_BUFF_LENGTH]> = ConstStaticCell::new([0; HALF_BUFF_LENGTH]); -static DEST_BUFFER: ConstStaticCell<[u32; BUFFER_LENGTH]> = ConstStaticCell::new([0; BUFFER_LENGTH]); - -/// Helper to print a buffer to UART -fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf: &[u32]) { - write!(tx, "{:?}", buf).ok(); -} - -#[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 interleave transfer example starting..."); - - let config = Config { - baudrate_bps: 115_200, - ..Default::default() - }; - - let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); - let (mut tx, _rx) = lpuart.split(); - - tx.blocking_write(b"EDMA interleave transfer example begin.\r\n\r\n") - .unwrap(); - - // Initialize buffers - let src = SRC_BUFFER.take(); - *src = [1, 2, 3, 4, 5, 6, 7, 8]; - let dst = DEST_BUFFER.take(); - - tx.blocking_write(b"Source Buffer: ").unwrap(); - print_buffer(&mut tx, src); - tx.blocking_write(b"\r\n").unwrap(); - - tx.blocking_write(b"Destination Buffer (before): ").unwrap(); - print_buffer(&mut tx, dst); - tx.blocking_write(b"\r\n").unwrap(); - - tx.blocking_write(b"Configuring DMA with Embassy-style API...\r\n") - .unwrap(); - - // Create DMA channel using Embassy-style API - let dma_ch0 = DmaChannel::new(p.DMA_CH0); - - // Configure interleaved transfer using direct TCD access: - // - src_offset = 4: advance source by 4 bytes after each read - // - dst_offset = 8: advance dest by 8 bytes after each write - // This spreads source data across every other word in destination - unsafe { - let t = dma_ch0.tcd(); - - // Reset channel state - t.ch_csr().write(|w| { - w.erq() - .disable() - .earq() - .disable() - .eei() - .no_error() - .ebw() - .disable() - .done() - .clear_bit_by_one() - }); - t.ch_es().write(|w| w.bits(0)); - t.ch_int().write(|w| w.int().clear_bit_by_one()); - - // Source/destination addresses - t.tcd_saddr().write(|w| w.saddr().bits(src.as_ptr() as u32)); - t.tcd_daddr().write(|w| w.daddr().bits(dst.as_mut_ptr() as u32)); - - // Custom offsets for interleaving - t.tcd_soff().write(|w| w.soff().bits(4)); // src: +4 bytes per read - t.tcd_doff().write(|w| w.doff().bits(8)); // dst: +8 bytes per write - - // Attributes: 32-bit transfers (size = 2) - t.tcd_attr().write(|w| w.ssize().bits(2).dsize().bits(2)); - - // Transfer entire source buffer in one minor loop - let nbytes = (HALF_BUFF_LENGTH * 4) as u32; - t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(nbytes)); - - // Reset source address after major loop - t.tcd_slast_sda().write(|w| w.slast_sda().bits(-(nbytes as i32) as u32)); - // Destination uses 2x offset, so adjust accordingly - let dst_total = (HALF_BUFF_LENGTH * 8) as u32; - t.tcd_dlast_sga() - .write(|w| w.dlast_sga().bits(-(dst_total as i32) as u32)); - - // Major loop count = 1 - t.tcd_biter_elinkno().write(|w| w.biter().bits(1)); - t.tcd_citer_elinkno().write(|w| w.citer().bits(1)); - - // Enable interrupt on major loop completion - t.tcd_csr().write(|w| w.intmajor().set_bit()); - - cortex_m::asm::dsb(); - - tx.blocking_write(b"Triggering transfer...\r\n").unwrap(); - dma_ch0.trigger_start(); - } - - // Wait for completion using channel helper method - while !dma_ch0.is_done() { - cortex_m::asm::nop(); - } - unsafe { - dma_ch0.clear_done(); - } - - tx.blocking_write(b"\r\nEDMA interleave transfer example finish.\r\n\r\n") - .unwrap(); - tx.blocking_write(b"Destination Buffer (after): ").unwrap(); - print_buffer(&mut tx, dst); - tx.blocking_write(b"\r\n\r\n").unwrap(); - - // Verify: Even indices should match SRC_BUFFER[i/2], odd indices should be 0 - let mut mismatch = false; - let diter = dst.chunks_exact(2); - let siter = src.iter(); - for (ch, src) in diter.zip(siter) { - mismatch |= !matches!(ch, [a, 0] if a == src); - } - - if mismatch { - tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); - defmt::error!("FAIL: Mismatch detected!"); - } else { - tx.blocking_write(b"PASS: Data verified.\r\n").unwrap(); - defmt::info!("PASS: Data verified."); - } - - loop { - cortex_m::asm::wfe(); - } -} diff --git a/examples/mcxa/src/bin/dma_memset.rs b/examples/mcxa/src/bin/dma_memset.rs deleted file mode 100644 index d7b03e91b..000000000 --- a/examples/mcxa/src/bin/dma_memset.rs +++ /dev/null @@ -1,125 +0,0 @@ -//! DMA memset example for MCXA276. -//! -//! This example demonstrates using DMA to fill a buffer with a repeated pattern. -//! The source address stays fixed while the destination increments. -//! -//! # Embassy-style features demonstrated: -//! - `DmaChannel::is_done()` and `clear_done()` helper methods -//! - No need to pass register block around - -#![no_std] -#![no_main] - -use embassy_executor::Spawner; -use embassy_mcxa::clocks::config::Div8; -use embassy_mcxa::dma::DmaChannel; -use static_cell::ConstStaticCell; -use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; - -const BUFFER_LENGTH: usize = 4; - -// Buffers in RAM -static PATTERN: u32 = 0xDEADBEEF; -static DEST_BUFFER: ConstStaticCell<[u32; BUFFER_LENGTH]> = ConstStaticCell::new([0; BUFFER_LENGTH]); - -#[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 memset example starting..."); - defmt::info!("EDMA memset example begin."); - - // Initialize buffers - let pat = &PATTERN; - let dst = DEST_BUFFER.take(); - defmt::info!("Pattern Value: {=u32}", pat); - defmt::info!("Destination Buffer (before): {=[?]}", dst.as_slice()); - defmt::info!("Configuring DMA with Embassy-style API..."); - - // Create DMA channel using Embassy-style API - let dma_ch0 = DmaChannel::new(p.DMA_CH0); - - // Configure memset transfer using direct TCD access: - // Source stays fixed (soff = 0, reads same pattern repeatedly) - // Destination increments (doff = 4) - unsafe { - let t = dma_ch0.tcd(); - - // Reset channel state - t.ch_csr().write(|w| { - w.erq() - .disable() - .earq() - .disable() - .eei() - .no_error() - .ebw() - .disable() - .done() - .clear_bit_by_one() - }); - t.ch_es().write(|w| w.bits(0)); - t.ch_int().write(|w| w.int().clear_bit_by_one()); - - // Source address (pattern) - fixed - t.tcd_saddr().write(|w| w.saddr().bits(pat as *const _ as u32)); - // Destination address - increments - t.tcd_daddr().write(|w| w.daddr().bits(dst.as_mut_ptr() as u32)); - - // Source offset = 0 (stays fixed), Dest offset = 4 (increments) - t.tcd_soff().write(|w| w.soff().bits(0)); - t.tcd_doff().write(|w| w.doff().bits(4)); - - // Attributes: 32-bit transfers (size = 2) - t.tcd_attr().write(|w| w.ssize().bits(2).dsize().bits(2)); - - // Transfer entire buffer in one minor loop - let nbytes = (BUFFER_LENGTH * 4) as u32; - t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(nbytes)); - - // Source doesn't need adjustment (stays fixed) - t.tcd_slast_sda().write(|w| w.slast_sda().bits(0)); - // Reset dest address after major loop - t.tcd_dlast_sga().write(|w| w.dlast_sga().bits(-(nbytes as i32) as u32)); - - // Major loop count = 1 - t.tcd_biter_elinkno().write(|w| w.biter().bits(1)); - t.tcd_citer_elinkno().write(|w| w.citer().bits(1)); - - // Enable interrupt on major loop completion - t.tcd_csr().write(|w| w.intmajor().set_bit()); - - cortex_m::asm::dsb(); - - defmt::info!("Triggering transfer..."); - dma_ch0.trigger_start(); - } - - // Wait for completion using channel helper method - while !dma_ch0.is_done() { - cortex_m::asm::nop(); - } - unsafe { - dma_ch0.clear_done(); - } - - defmt::info!("EDMA memset example finish."); - defmt::info!("Destination Buffer (after): {=[?]}", dst.as_slice()); - - // Verify: All elements should equal PATTERN - let mismatch = dst.iter().any(|i| *i != *pat); - - if mismatch { - defmt::error!("FAIL: Mismatch detected!"); - } else { - defmt::info!("PASS: Data verified."); - } -} diff --git a/examples/mcxa/src/bin/dma_ping_pong_transfer.rs b/examples/mcxa/src/bin/dma_ping_pong_transfer.rs deleted file mode 100644 index 58f643b80..000000000 --- a/examples/mcxa/src/bin/dma_ping_pong_transfer.rs +++ /dev/null @@ -1,347 +0,0 @@ -//! DMA ping-pong/double-buffer transfer example for MCXA276. -//! -//! 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 core::fmt::Write as _; -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::lpuart::{Blocking, Config, Lpuart, LpuartTx}; -use embassy_mcxa::{bind_interrupts, pac}; -use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; - -// Source and destination buffers for Approach 1 (scatter/gather) -static mut SRC: [u32; 8] = [1, 2, 3, 4, 5, 6, 7, 8]; -static mut DST: [u32; 8] = [0; 8]; - -// Source and destination buffers for Approach 2 (wait_half) -static mut SRC2: [u32; 8] = [0xA1, 0xA2, 0xA3, 0xA4, 0xB1, 0xB2, 0xB3, 0xB4]; -static mut DST2: [u32; 8] = [0; 8]; - -// TCD pool for scatter/gather - must be 32-byte aligned -#[repr(C, align(32))] -struct TcdPool([Tcd; 2]); - -static mut TCD_POOL: TcdPool = 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], -); - -// 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; -}); - -/// Helper to print a buffer to UART -fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { - write!(tx, "{:?}", unsafe { core::slice::from_raw_parts(buf_ptr, len) }).ok(); -} - -#[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..."); - - // Enable DMA interrupt (DMA clock/reset/init is handled automatically by HAL) - unsafe { - cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); - } - - let config = Config { - baudrate_bps: 115_200, - ..Default::default() - }; - - let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); - let (mut tx, _rx) = lpuart.split(); - - tx.blocking_write(b"EDMA ping-pong transfer example begin.\r\n\r\n") - .unwrap(); - - // Initialize buffers - unsafe { - SRC = [1, 2, 3, 4, 5, 6, 7, 8]; - DST = [0; 8]; - } - - tx.blocking_write(b"Source Buffer: ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(SRC) as *const u32, 8); - tx.blocking_write(b"\r\n").unwrap(); - - tx.blocking_write(b"Destination Buffer (before): ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); - tx.blocking_write(b"\r\n").unwrap(); - - tx.blocking_write(b"Configuring ping-pong DMA with Embassy-style API...\r\n") - .unwrap(); - - 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. - unsafe { - let tcds = &mut *core::ptr::addr_of_mut!(TCD_POOL.0); - let src_ptr = core::ptr::addr_of!(SRC) as *const u32; - let dst_ptr = core::ptr::addr_of_mut!(DST) as *mut u32; - - let half_len = 4usize; - let half_bytes = (half_len * 4) as u32; - - 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_ptr as u32, - soff: 4, - attr: 0x0202, // 32-bit src/dst - nbytes: half_bytes, - slast: 0, - daddr: dst_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_ptr.add(half_len) as u32, - soff: 4, - attr: 0x0202, - nbytes: half_bytes, - slast: 0, - daddr: dst_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]); - } - - tx.blocking_write(b"Triggering first half transfer...\r\n").unwrap(); - - // Trigger first transfer (first half: SRC[0..4] -> DST[0..4]) - unsafe { - dma_ch0.trigger_start(); - } - - // Wait for first half - while !TRANSFER_DONE.load(Ordering::Acquire) { - cortex_m::asm::nop(); - } - TRANSFER_DONE.store(false, Ordering::Release); - - tx.blocking_write(b"First half transferred.\r\n").unwrap(); - tx.blocking_write(b"Triggering second half transfer...\r\n").unwrap(); - - // Trigger second transfer (second half: SRC[4..8] -> DST[4..8]) - unsafe { - dma_ch0.trigger_start(); - } - - // Wait for second half - while !TRANSFER_DONE.load(Ordering::Acquire) { - cortex_m::asm::nop(); - } - TRANSFER_DONE.store(false, Ordering::Release); - - tx.blocking_write(b"Second half transferred.\r\n\r\n").unwrap(); - - tx.blocking_write(b"EDMA ping-pong transfer example finish.\r\n\r\n") - .unwrap(); - tx.blocking_write(b"Destination Buffer (after): ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); - tx.blocking_write(b"\r\n\r\n").unwrap(); - - // Verify: DST should match SRC - let mut mismatch = false; - unsafe { - let src_ptr = core::ptr::addr_of!(SRC) as *const u32; - let dst_ptr = core::ptr::addr_of!(DST) as *const u32; - for i in 0..8 { - if *src_ptr.add(i) != *dst_ptr.add(i) { - mismatch = true; - break; - } - } - } - - if mismatch { - tx.blocking_write(b"FAIL: Approach 1 mismatch detected!\r\n").unwrap(); - defmt::error!("FAIL: Approach 1 mismatch detected!"); - } else { - tx.blocking_write(b"PASS: Approach 1 data verified.\r\n\r\n").unwrap(); - 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 - - tx.blocking_write(b"--- Approach 2: wait_half() demo ---\r\n\r\n") - .unwrap(); - - // Enable DMA CH1 interrupt - unsafe { - cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH1); - } - - // Initialize approach 2 buffers - unsafe { - SRC2 = [0xA1, 0xA2, 0xA3, 0xA4, 0xB1, 0xB2, 0xB3, 0xB4]; - DST2 = [0; 8]; - } - - tx.blocking_write(b"SRC2: ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(SRC2) as *const u32, 8); - tx.blocking_write(b"\r\n").unwrap(); - - 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; - - tx.blocking_write(b"Starting transfer with half_transfer_interrupt...\r\n") - .unwrap(); - - unsafe { - let src = &*core::ptr::addr_of!(SRC2); - let dst = &mut *core::ptr::addr_of_mut!(DST2); - - // Create the transfer - let mut transfer = dma_ch1.mem_to_mem(src, dst, options); - - // Wait for half-transfer (first 4 elements) - tx.blocking_write(b"Waiting for first half...\r\n").unwrap(); - let half_ok = transfer.wait_half().await; - - if half_ok { - tx.blocking_write(b"Half-transfer complete! First half of DST2: ") - .unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(DST2) as *const u32, 4); - tx.blocking_write(b"\r\n").unwrap(); - tx.blocking_write(b"(Processing first half while second half transfers...)\r\n") - .unwrap(); - } - - // Wait for complete transfer - tx.blocking_write(b"Waiting for second half...\r\n").unwrap(); - transfer.await; - } - - tx.blocking_write(b"Transfer complete! Full DST2: ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(DST2) as *const u32, 8); - tx.blocking_write(b"\r\n\r\n").unwrap(); - - // Verify approach 2 - let mut mismatch2 = false; - unsafe { - let src_ptr = core::ptr::addr_of!(SRC2) as *const u32; - let dst_ptr = core::ptr::addr_of!(DST2) as *const u32; - for i in 0..8 { - if *src_ptr.add(i) != *dst_ptr.add(i) { - mismatch2 = true; - break; - } - } - } - - if mismatch2 { - tx.blocking_write(b"FAIL: Approach 2 mismatch!\r\n").unwrap(); - defmt::error!("FAIL: Approach 2 mismatch!"); - } else { - tx.blocking_write(b"PASS: Approach 2 verified.\r\n").unwrap(); - defmt::info!("PASS: Approach 2 verified."); - } - - tx.blocking_write(b"\r\n=== All ping-pong demos complete ===\r\n") - .unwrap(); - - loop { - cortex_m::asm::wfe(); - } -} diff --git a/examples/mcxa/src/bin/dma_scatter_gather.rs b/examples/mcxa/src/bin/dma_scatter_gather.rs deleted file mode 100644 index 3e34e95b1..000000000 --- a/examples/mcxa/src/bin/dma_scatter_gather.rs +++ /dev/null @@ -1,234 +0,0 @@ -//! DMA scatter-gather transfer example for MCXA276. -//! -//! This example demonstrates using DMA with scatter/gather to chain multiple -//! transfer descriptors. The first TCD transfers the first half of the buffer, -//! then automatically loads the second TCD to transfer the second half. -//! -//! # Embassy-style features demonstrated: -//! - `DmaChannel::new()` for channel creation -//! - Scatter/gather with chained TCDs -//! - Custom handler that delegates to HAL's `on_interrupt()` (best practice) - -#![no_std] -#![no_main] - -use core::fmt::Write as _; -use core::sync::atomic::{AtomicBool, Ordering}; - -use embassy_executor::Spawner; -use embassy_mcxa::clocks::config::Div8; -use embassy_mcxa::dma::{self, DmaChannel, Tcd}; -use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; -use embassy_mcxa::{bind_interrupts, pac}; -use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; - -// Source and destination buffers -static mut SRC: [u32; 8] = [1, 2, 3, 4, 5, 6, 7, 8]; -static mut DST: [u32; 8] = [0; 8]; - -// TCD pool for scatter/gather - must be 32-byte aligned -#[repr(C, align(32))] -struct TcdPool([Tcd; 2]); - -static mut TCD_POOL: TcdPool = 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], -); - -// 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; -}); - -/// Helper to print a buffer to UART -fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { - write!(tx, "{:?}", unsafe { core::slice::from_raw_parts(buf_ptr, len) }).ok(); -} - -#[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 scatter-gather transfer example starting..."); - - // DMA is initialized during hal::init() - no need to call ensure_init() - - // Enable DMA interrupt - unsafe { - cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); - } - - let config = Config { - baudrate_bps: 115_200, - ..Default::default() - }; - - let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); - let (mut tx, _rx) = lpuart.split(); - - tx.blocking_write(b"EDMA scatter-gather transfer example begin.\r\n\r\n") - .unwrap(); - - // Initialize buffers - unsafe { - SRC = [1, 2, 3, 4, 5, 6, 7, 8]; - DST = [0; 8]; - } - - tx.blocking_write(b"Source Buffer: ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(SRC) as *const u32, 8); - tx.blocking_write(b"\r\n").unwrap(); - - tx.blocking_write(b"Destination Buffer (before): ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); - tx.blocking_write(b"\r\n").unwrap(); - - tx.blocking_write(b"Configuring scatter-gather DMA with Embassy-style API...\r\n") - .unwrap(); - - let dma_ch0 = DmaChannel::new(p.DMA_CH0); - - // 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. - unsafe { - let tcds = core::slice::from_raw_parts_mut(core::ptr::addr_of_mut!(TCD_POOL.0) as *mut Tcd, 2); - let src_ptr = core::ptr::addr_of!(SRC) as *const u32; - let dst_ptr = core::ptr::addr_of_mut!(DST) as *mut u32; - - 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, - }; - } - - // Load TCD0 into hardware registers - dma_ch0.load_tcd(&tcds[0]); - } - - tx.blocking_write(b"Triggering first half transfer...\r\n").unwrap(); - - // Trigger first transfer (first half: SRC[0..4] -> DST[0..4]) - // TCD0 is currently loaded. - unsafe { - dma_ch0.trigger_start(); - } - - // Wait for first half - while !TRANSFER_DONE.load(Ordering::Acquire) { - cortex_m::asm::nop(); - } - TRANSFER_DONE.store(false, Ordering::Release); - - tx.blocking_write(b"First half transferred.\r\n").unwrap(); - tx.blocking_write(b"Triggering second half transfer...\r\n").unwrap(); - - // Trigger second transfer (second half: SRC[4..8] -> DST[4..8]) - // TCD1 should have been loaded by the scatter/gather engine. - unsafe { - dma_ch0.trigger_start(); - } - - // Wait for second half - while !TRANSFER_DONE.load(Ordering::Acquire) { - cortex_m::asm::nop(); - } - TRANSFER_DONE.store(false, Ordering::Release); - - tx.blocking_write(b"Second half transferred.\r\n\r\n").unwrap(); - - tx.blocking_write(b"EDMA scatter-gather transfer example finish.\r\n\r\n") - .unwrap(); - tx.blocking_write(b"Destination Buffer (after): ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); - tx.blocking_write(b"\r\n\r\n").unwrap(); - - // Verify: DST should match SRC - let mut mismatch = false; - unsafe { - let src_ptr = core::ptr::addr_of!(SRC) as *const u32; - let dst_ptr = core::ptr::addr_of!(DST) as *const u32; - for i in 0..8 { - if *src_ptr.add(i) != *dst_ptr.add(i) { - mismatch = true; - break; - } - } - } - - if mismatch { - tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); - defmt::error!("FAIL: Mismatch detected!"); - } else { - tx.blocking_write(b"PASS: Data verified.\r\n").unwrap(); - defmt::info!("PASS: Data verified."); - } - - loop { - cortex_m::asm::wfe(); - } -} diff --git a/examples/mcxa/src/bin/dma_scatter_gather_builder.rs b/examples/mcxa/src/bin/dma_scatter_gather_builder.rs index 1691129f6..30ce20c96 100644 --- a/examples/mcxa/src/bin/dma_scatter_gather_builder.rs +++ b/examples/mcxa/src/bin/dma_scatter_gather_builder.rs @@ -112,11 +112,7 @@ async fn main(_spawner: Spawner) { defmt::info!(" DST2: {=[?]}", dst2.as_slice()); defmt::info!(" DST3: {=[?]}", dst3.as_slice()); - let comps = [ - (src1, dst1), - (src2, dst2), - (src3, dst3), - ]; + let comps = [(src1, dst1), (src2, dst2), (src3, dst3)]; // Verify all three segments let mut all_ok = true; diff --git a/examples/mcxa/src/bin/raw_dma_channel_link.rs b/examples/mcxa/src/bin/raw_dma_channel_link.rs new file mode 100644 index 000000000..987f1ba43 --- /dev/null +++ b/examples/mcxa/src/bin/raw_dma_channel_link.rs @@ -0,0 +1,279 @@ +//! DMA channel linking 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 DMA channel linking (minor and major loop linking): +//! - Channel 0: Transfers SRC_BUFFER to DEST_BUFFER0, with: +//! - Minor Link to Channel 1 (triggers CH1 after each minor loop) +//! - Major Link to Channel 2 (triggers CH2 after major loop completes) +//! - Channel 1: Transfers SRC_BUFFER to DEST_BUFFER1 (triggered by CH0 minor link) +//! - Channel 2: Transfers SRC_BUFFER to DEST_BUFFER2 (triggered by CH0 major link) +//! +//! # Embassy-style features demonstrated: +//! - `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] + +use embassy_executor::Spawner; +use embassy_mcxa::clocks::config::Div8; +use embassy_mcxa::dma::DmaChannel; +use embassy_mcxa::pac; +use static_cell::ConstStaticCell; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +// Buffers +static SRC_BUFFER: ConstStaticCell<[u32; 4]> = ConstStaticCell::new([1, 2, 3, 4]); +static DEST_BUFFER0: ConstStaticCell<[u32; 4]> = ConstStaticCell::new([0; 4]); +static DEST_BUFFER1: ConstStaticCell<[u32; 4]> = ConstStaticCell::new([0; 4]); +static DEST_BUFFER2: ConstStaticCell<[u32; 4]> = ConstStaticCell::new([0; 4]); + +#[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 channel link example starting..."); + + // DMA is initialized during hal::init() - no need to call ensure_init() + + let pac_periphs = unsafe { pac::Peripherals::steal() }; + let dma0 = &pac_periphs.dma0; + let edma = unsafe { &*pac::Edma0Tcd0::ptr() }; + + // Clear any residual state + for i in 0..3 { + let t = edma.tcd(i); + t.ch_csr().write(|w| w.erq().disable().done().clear_bit_by_one()); + t.ch_int().write(|w| w.int().clear_bit_by_one()); + t.ch_es().write(|w| w.err().clear_bit_by_one()); + t.ch_mux().write(|w| unsafe { w.bits(0) }); + } + + // Clear Global Halt/Error state + dma0.mp_csr().modify(|_, w| { + w.halt() + .normal_operation() + .hae() + .normal_operation() + .ecx() + .normal_operation() + .cx() + .normal_operation() + }); + + defmt::info!("EDMA channel link example begin."); + + // Initialize buffers + let src = SRC_BUFFER.take(); + let dst0 = DEST_BUFFER0.take(); + let dst1 = DEST_BUFFER1.take(); + let dst2 = DEST_BUFFER2.take(); + + defmt::info!("Source Buffer: {=[?]}", src.as_slice()); + defmt::info!("DEST0 (before): {=[?]}", dst0.as_slice()); + defmt::info!("DEST1 (before): {=[?]}", dst1.as_slice()); + defmt::info!("DEST2 (before): {=[?]}", dst2.as_slice()); + + defmt::info!("Configuring DMA channels with Embassy-style API..."); + + let ch0 = DmaChannel::new(p.DMA_CH0); + let ch1 = DmaChannel::new(p.DMA_CH1); + let ch2 = DmaChannel::new(p.DMA_CH2); + + // Configure channels using direct TCD access (advanced feature demo) + // This example demonstrates channel linking which requires direct TCD manipulation + + // Helper to configure TCD for memory-to-memory transfer + // Parameters: channel, src, dst, width, nbytes (minor loop), count (major loop), interrupt + #[allow(clippy::too_many_arguments)] + unsafe fn configure_tcd( + edma: &embassy_mcxa::pac::edma_0_tcd0::RegisterBlock, + ch: usize, + src: u32, + dst: u32, + width: u8, + nbytes: u32, + count: u16, + enable_int: bool, + ) { + let t = edma.tcd(ch); + + // Reset channel state + t.ch_csr().write(|w| { + w.erq() + .disable() + .earq() + .disable() + .eei() + .no_error() + .ebw() + .disable() + .done() + .clear_bit_by_one() + }); + t.ch_es().write(|w| w.bits(0)); + t.ch_int().write(|w| w.int().clear_bit_by_one()); + + // Source/destination addresses + t.tcd_saddr().write(|w| w.saddr().bits(src)); + t.tcd_daddr().write(|w| w.daddr().bits(dst)); + + // Offsets: increment by width + t.tcd_soff().write(|w| w.soff().bits(width as u16)); + t.tcd_doff().write(|w| w.doff().bits(width as u16)); + + // Attributes: size = log2(width) + let size = match width { + 1 => 0, + 2 => 1, + 4 => 2, + _ => 0, + }; + t.tcd_attr().write(|w| w.ssize().bits(size).dsize().bits(size)); + + // Number of bytes per minor loop + t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(nbytes)); + + // Major loop: reset source address after major loop + let total_bytes = nbytes * count as u32; + t.tcd_slast_sda() + .write(|w| w.slast_sda().bits(-(total_bytes as i32) as u32)); + t.tcd_dlast_sga() + .write(|w| w.dlast_sga().bits(-(total_bytes as i32) as u32)); + + // Major loop count + t.tcd_biter_elinkno().write(|w| w.biter().bits(count)); + t.tcd_citer_elinkno().write(|w| w.citer().bits(count)); + + // Control/status: enable interrupt if requested + if enable_int { + t.tcd_csr().write(|w| w.intmajor().set_bit()); + } else { + t.tcd_csr().write(|w| w.intmajor().clear_bit()); + } + + cortex_m::asm::dsb(); + } + + unsafe { + // Channel 0: Transfer 16 bytes total (8 bytes per minor loop, 2 major iterations) + // Minor Link -> Channel 1 + // Major Link -> Channel 2 + configure_tcd( + edma, + 0, + src.as_ptr() as u32, + dst0.as_mut_ptr() as u32, + 4, // src width + 8, // nbytes (minor loop = 2 words) + 2, // count (major loop = 2 iterations) + false, // no interrupt + ); + ch0.set_minor_link(1); // Link to CH1 after each minor loop + ch0.set_major_link(2); // Link to CH2 after major loop + + // Channel 1: Transfer 16 bytes (triggered by CH0 minor link) + configure_tcd( + edma, + 1, + src.as_ptr() as u32, + dst1.as_mut_ptr() as u32, + 4, + 16, // full buffer in one minor loop + 1, // 1 major iteration + false, + ); + + // Channel 2: Transfer 16 bytes (triggered by CH0 major link) + configure_tcd( + edma, + 2, + src.as_ptr() as u32, + dst2.as_mut_ptr() as u32, + 4, + 16, // full buffer in one minor loop + 1, // 1 major iteration + true, // enable interrupt + ); + } + + defmt::info!("Triggering Channel 0 (1st minor loop)..."); + + // Trigger first minor loop of CH0 + unsafe { + ch0.trigger_start(); + } + + // Wait for CH1 to complete (triggered by CH0 minor link) + while !ch1.is_done() { + cortex_m::asm::nop(); + } + unsafe { + ch1.clear_done(); + } + + defmt::info!("CH1 done (via minor link)."); + defmt::info!("Triggering Channel 0 (2nd minor loop)..."); + + // Trigger second minor loop of CH0 + unsafe { + ch0.trigger_start(); + } + + // Wait for CH0 major loop to complete + while !ch0.is_done() { + cortex_m::asm::nop(); + } + unsafe { + ch0.clear_done(); + } + + defmt::info!("CH0 major loop done."); + + // Wait for CH2 to complete (triggered by CH0 major link) + // Using is_done() instead of AtomicBool - the standard interrupt handler + // clears the interrupt flag and wakes wakers, but DONE bit remains set + while !ch2.is_done() { + cortex_m::asm::nop(); + } + unsafe { + ch2.clear_done(); + } + + defmt::info!("CH2 done (via major link)."); + + defmt::info!("EDMA channel link example finish."); + + defmt::info!("DEST0 (after): {=[?]}", dst0.as_slice()); + defmt::info!("DEST1 (after): {=[?]}", dst1.as_slice()); + defmt::info!("DEST2 (after): {=[?]}", dst2.as_slice()); + + // Verify all buffers match source + let mut success = true; + for sli in [dst0, dst1, dst2] { + success &= sli == src; + } + + if success { + defmt::info!("PASS: Data verified."); + } else { + defmt::error!("FAIL: Mismatch detected!"); + } + + loop { + cortex_m::asm::wfe(); + } +} diff --git a/examples/mcxa/src/bin/raw_dma_interleave_transfer.rs b/examples/mcxa/src/bin/raw_dma_interleave_transfer.rs new file mode 100644 index 000000000..a383b6cf4 --- /dev/null +++ b/examples/mcxa/src/bin/raw_dma_interleave_transfer.rs @@ -0,0 +1,141 @@ +//! DMA interleaved 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 using DMA with custom source/destination offsets +//! to interleave data during transfer. +//! +//! # Embassy-style features demonstrated: +//! - `TransferOptions::default()` for configuration (used internally) +//! - DMA channel with `DmaChannel::new()` + +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_mcxa::clocks::config::Div8; +use embassy_mcxa::dma::DmaChannel; +use static_cell::ConstStaticCell; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +const BUFFER_LENGTH: usize = 16; +const HALF_BUFF_LENGTH: usize = BUFFER_LENGTH / 2; + +// Buffers in RAM +static SRC_BUFFER: ConstStaticCell<[u32; HALF_BUFF_LENGTH]> = ConstStaticCell::new([0; HALF_BUFF_LENGTH]); +static DEST_BUFFER: ConstStaticCell<[u32; BUFFER_LENGTH]> = ConstStaticCell::new([0; BUFFER_LENGTH]); + +#[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 interleave transfer example starting..."); + + defmt::info!("EDMA interleave transfer example begin."); + + // Initialize buffers + let src = SRC_BUFFER.take(); + *src = [1, 2, 3, 4, 5, 6, 7, 8]; + let dst = DEST_BUFFER.take(); + + defmt::info!("Source Buffer: {=[?]}", src.as_slice()); + defmt::info!("Destination Buffer (before): {=[?]}", dst.as_slice()); + + defmt::info!("Configuring DMA with Embassy-style API..."); + + // Create DMA channel using Embassy-style API + let dma_ch0 = DmaChannel::new(p.DMA_CH0); + + // Configure interleaved transfer using direct TCD access: + // - src_offset = 4: advance source by 4 bytes after each read + // - dst_offset = 8: advance dest by 8 bytes after each write + // This spreads source data across every other word in destination + unsafe { + let t = dma_ch0.tcd(); + + // Reset channel state + t.ch_csr().write(|w| { + w.erq() + .disable() + .earq() + .disable() + .eei() + .no_error() + .ebw() + .disable() + .done() + .clear_bit_by_one() + }); + t.ch_es().write(|w| w.bits(0)); + t.ch_int().write(|w| w.int().clear_bit_by_one()); + + // Source/destination addresses + t.tcd_saddr().write(|w| w.saddr().bits(src.as_ptr() as u32)); + t.tcd_daddr().write(|w| w.daddr().bits(dst.as_mut_ptr() as u32)); + + // Custom offsets for interleaving + t.tcd_soff().write(|w| w.soff().bits(4)); // src: +4 bytes per read + t.tcd_doff().write(|w| w.doff().bits(8)); // dst: +8 bytes per write + + // Attributes: 32-bit transfers (size = 2) + t.tcd_attr().write(|w| w.ssize().bits(2).dsize().bits(2)); + + // Transfer entire source buffer in one minor loop + let nbytes = (HALF_BUFF_LENGTH * 4) as u32; + t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(nbytes)); + + // Reset source address after major loop + t.tcd_slast_sda().write(|w| w.slast_sda().bits(-(nbytes as i32) as u32)); + // Destination uses 2x offset, so adjust accordingly + let dst_total = (HALF_BUFF_LENGTH * 8) as u32; + t.tcd_dlast_sga() + .write(|w| w.dlast_sga().bits(-(dst_total as i32) as u32)); + + // Major loop count = 1 + t.tcd_biter_elinkno().write(|w| w.biter().bits(1)); + t.tcd_citer_elinkno().write(|w| w.citer().bits(1)); + + // Enable interrupt on major loop completion + t.tcd_csr().write(|w| w.intmajor().set_bit()); + + cortex_m::asm::dsb(); + + defmt::info!("Triggering transfer..."); + dma_ch0.trigger_start(); + } + + // Wait for completion using channel helper method + while !dma_ch0.is_done() { + cortex_m::asm::nop(); + } + unsafe { + dma_ch0.clear_done(); + } + + defmt::info!("EDMA interleave transfer example finish."); + defmt::info!("Destination Buffer (after): {=[?]}", dst.as_slice()); + + // Verify: Even indices should match SRC_BUFFER[i/2], odd indices should be 0 + let mut mismatch = false; + let diter = dst.chunks_exact(2); + let siter = src.iter(); + for (ch, src) in diter.zip(siter) { + mismatch |= !matches!(ch, [a, 0] if a == src); + } + + if mismatch { + defmt::error!("FAIL: Mismatch detected!"); + } else { + defmt::info!("PASS: Data verified."); + } +} diff --git a/examples/mcxa/src/bin/raw_dma_memset.rs b/examples/mcxa/src/bin/raw_dma_memset.rs new file mode 100644 index 000000000..7b3c06ffa --- /dev/null +++ b/examples/mcxa/src/bin/raw_dma_memset.rs @@ -0,0 +1,129 @@ +//! DMA memset 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 using DMA to fill a buffer with a repeated pattern. +//! The source address stays fixed while the destination increments. +//! +//! # Embassy-style features demonstrated: +//! - `DmaChannel::is_done()` and `clear_done()` helper methods +//! - No need to pass register block around + +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_mcxa::clocks::config::Div8; +use embassy_mcxa::dma::DmaChannel; +use static_cell::ConstStaticCell; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +const BUFFER_LENGTH: usize = 4; + +// Buffers in RAM +static PATTERN: u32 = 0xDEADBEEF; +static DEST_BUFFER: ConstStaticCell<[u32; BUFFER_LENGTH]> = ConstStaticCell::new([0; BUFFER_LENGTH]); + +#[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 memset example starting..."); + defmt::info!("EDMA memset example begin."); + + // Initialize buffers + let pat = &PATTERN; + let dst = DEST_BUFFER.take(); + defmt::info!("Pattern Value: {=u32}", pat); + defmt::info!("Destination Buffer (before): {=[?]}", dst.as_slice()); + defmt::info!("Configuring DMA with Embassy-style API..."); + + // Create DMA channel using Embassy-style API + let dma_ch0 = DmaChannel::new(p.DMA_CH0); + + // Configure memset transfer using direct TCD access: + // Source stays fixed (soff = 0, reads same pattern repeatedly) + // Destination increments (doff = 4) + unsafe { + let t = dma_ch0.tcd(); + + // Reset channel state + t.ch_csr().write(|w| { + w.erq() + .disable() + .earq() + .disable() + .eei() + .no_error() + .ebw() + .disable() + .done() + .clear_bit_by_one() + }); + t.ch_es().write(|w| w.bits(0)); + t.ch_int().write(|w| w.int().clear_bit_by_one()); + + // Source address (pattern) - fixed + t.tcd_saddr().write(|w| w.saddr().bits(pat as *const _ as u32)); + // Destination address - increments + t.tcd_daddr().write(|w| w.daddr().bits(dst.as_mut_ptr() as u32)); + + // Source offset = 0 (stays fixed), Dest offset = 4 (increments) + t.tcd_soff().write(|w| w.soff().bits(0)); + t.tcd_doff().write(|w| w.doff().bits(4)); + + // Attributes: 32-bit transfers (size = 2) + t.tcd_attr().write(|w| w.ssize().bits(2).dsize().bits(2)); + + // Transfer entire buffer in one minor loop + let nbytes = (BUFFER_LENGTH * 4) as u32; + t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(nbytes)); + + // Source doesn't need adjustment (stays fixed) + t.tcd_slast_sda().write(|w| w.slast_sda().bits(0)); + // Reset dest address after major loop + t.tcd_dlast_sga().write(|w| w.dlast_sga().bits(-(nbytes as i32) as u32)); + + // Major loop count = 1 + t.tcd_biter_elinkno().write(|w| w.biter().bits(1)); + t.tcd_citer_elinkno().write(|w| w.citer().bits(1)); + + // Enable interrupt on major loop completion + t.tcd_csr().write(|w| w.intmajor().set_bit()); + + cortex_m::asm::dsb(); + + defmt::info!("Triggering transfer..."); + dma_ch0.trigger_start(); + } + + // Wait for completion using channel helper method + while !dma_ch0.is_done() { + cortex_m::asm::nop(); + } + unsafe { + dma_ch0.clear_done(); + } + + defmt::info!("EDMA memset example finish."); + defmt::info!("Destination Buffer (after): {=[?]}", dst.as_slice()); + + // Verify: All elements should equal PATTERN + let mismatch = dst.iter().any(|i| *i != *pat); + + if mismatch { + defmt::error!("FAIL: Mismatch detected!"); + } else { + defmt::info!("PASS: Data verified."); + } +} diff --git a/examples/mcxa/src/bin/raw_dma_ping_pong_transfer.rs b/examples/mcxa/src/bin/raw_dma_ping_pong_transfer.rs new file mode 100644 index 000000000..4a64b2498 --- /dev/null +++ b/examples/mcxa/src/bin/raw_dma_ping_pong_transfer.rs @@ -0,0 +1,269 @@ +//! 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 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 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], +)); + +// 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 + 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. + unsafe { + let tcds = &mut TCD_POOL.take().0; + + let half_len = 4usize; + let half_bytes = (half_len * 4) as u32; + + 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(); + } + + // Wait for first half + while !TRANSFER_DONE.load(Ordering::Acquire) { + cortex_m::asm::nop(); + } + TRANSFER_DONE.store(false, Ordering::Release); + + 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 + while !TRANSFER_DONE.load(Ordering::Acquire) { + cortex_m::asm::nop(); + } + TRANSFER_DONE.store(false, Ordering::Release); + + 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); + + // Wait for half-transfer (first 4 elements) + defmt::info!("Waiting for first half..."); + let _ok = transfer.wait_half().await; + + defmt::info!("Half-transfer complete!"); + + // Wait for complete transfer + defmt::info!("Waiting for second half..."); + transfer.await; + + 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 ==="); +} diff --git a/examples/mcxa/src/bin/raw_dma_scatter_gather.rs b/examples/mcxa/src/bin/raw_dma_scatter_gather.rs new file mode 100644 index 000000000..057e56826 --- /dev/null +++ b/examples/mcxa/src/bin/raw_dma_scatter_gather.rs @@ -0,0 +1,188 @@ +//! DMA scatter-gather 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 using DMA with scatter/gather to chain multiple +//! transfer descriptors. The first TCD transfers the first half of the buffer, +//! then automatically loads the second TCD to transfer the second half. +//! +//! # Embassy-style features demonstrated: +//! - `DmaChannel::new()` for channel creation +//! - Scatter/gather with chained TCDs +//! - Custom handler that delegates to HAL's `on_interrupt()` (best practice) + +#![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 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]); + +// 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], +)); + +// 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 + 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 scatter-gather transfer example starting..."); + + defmt::info!("EDMA scatter-gather 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 scatter-gather DMA with Embassy-style API..."); + + let dma_ch0 = DmaChannel::new(p.DMA_CH0); + + // 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. + 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, + }; + } + + // 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]) + // TCD0 is currently loaded. + unsafe { + dma_ch0.trigger_start(); + } + + // Wait for first half + while !TRANSFER_DONE.load(Ordering::Acquire) { + cortex_m::asm::nop(); + } + TRANSFER_DONE.store(false, Ordering::Release); + + defmt::info!("First half transferred."); + defmt::info!("Triggering second half transfer..."); + + // Trigger second transfer (second half: SRC[4..8] -> DST[4..8]) + // TCD1 should have been loaded by the scatter/gather engine. + unsafe { + dma_ch0.trigger_start(); + } + + // Wait for second half + while !TRANSFER_DONE.load(Ordering::Acquire) { + cortex_m::asm::nop(); + } + TRANSFER_DONE.store(false, Ordering::Release); + + defmt::info!("Second half transferred."); + + defmt::info!("EDMA scatter-gather 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: Mismatch detected!"); + } else { + defmt::info!("PASS: Data verified."); + } +} -- cgit