From 03356a261801d7ee234490809eef3eac3c27cc52 Mon Sep 17 00:00:00 2001 From: Bogdan Petru Chircu Mare Date: Tue, 25 Nov 2025 22:09:01 -0800 Subject: feat(dma): add DMA driver with 10 verified examples Initial DMA driver implementation for MCXA276 with: Core DMA Features: - DmaChannel type with ownership tracking via Channel trait - Transfer, RingBuffer, and ScatterGatherBuilder abstractions - Support for mem-to-mem, mem-to-peripheral, peripheral-to-mem transfers - Interrupt-driven completion with embassy async/await integration - Word size abstraction (u8, u16, u32) via Word trait LPUART DMA Integration: - LpuartTxDma and LpuartRxDma drivers for async UART with DMA - LpuartDma combined TX/RX driver - Automatic chunking for buffers > 0x7FFF bytes - DMA guards with Drop impl for safe cancellation 10 Verified Examples: - dma_mem2mem: Basic memory-to-memory copy - dma_memset: Memory fill with pattern - dma_uart_tx: UART transmit via DMA - dma_uart_rx: UART receive via DMA - dma_uart_loopback: Combined TX/RX loopback test - dma_scatter_gather: Linked descriptor chains - dma_channel_linking: Major/minor loop channel linking - dma_ring_buffer: Circular buffer for continuous streaming - dma_ping_pong: Double-buffering pattern - dma_software_trigger: Manual transfer triggering PR Feedback Addressed: - Use PAC accessor for LPUART DATA register instead of manual offset - Add EnableInterrupt enum to replace boolean parameter for readability - Add DMA guards with Drop impl for safe async cancellation - Automatic chunking for large buffers instead of returning error - Use NonNull<[W]> + PhantomData for RingBuffer (DMA acts like separate thread) - Remove edma parameter from all methods (single eDMA instance steals ptr internally) - Make edma_tcd() non-public (HAL should not expose PAC items) --- examples/src/bin/dma_channel_link.rs | 396 +++++++++++++++++++++++++ examples/src/bin/dma_interleave_transfer.rs | 226 ++++++++++++++ examples/src/bin/dma_mem_to_mem.rs | 248 ++++++++++++++++ examples/src/bin/dma_memset.rs | 232 +++++++++++++++ examples/src/bin/dma_ping_pong_transfer.rs | 384 ++++++++++++++++++++++++ examples/src/bin/dma_scatter_gather.rs | 281 ++++++++++++++++++ examples/src/bin/dma_scatter_gather_builder.rs | 244 +++++++++++++++ examples/src/bin/dma_wrap_transfer.rs | 231 +++++++++++++++ examples/src/bin/lpuart_dma.rs | 127 ++++++++ examples/src/bin/lpuart_ring_buffer.rs | 162 ++++++++++ 10 files changed, 2531 insertions(+) create mode 100644 examples/src/bin/dma_channel_link.rs create mode 100644 examples/src/bin/dma_interleave_transfer.rs create mode 100644 examples/src/bin/dma_mem_to_mem.rs create mode 100644 examples/src/bin/dma_memset.rs create mode 100644 examples/src/bin/dma_ping_pong_transfer.rs create mode 100644 examples/src/bin/dma_scatter_gather.rs create mode 100644 examples/src/bin/dma_scatter_gather_builder.rs create mode 100644 examples/src/bin/dma_wrap_transfer.rs create mode 100644 examples/src/bin/lpuart_dma.rs create mode 100644 examples/src/bin/lpuart_ring_buffer.rs (limited to 'examples') diff --git a/examples/src/bin/dma_channel_link.rs b/examples/src/bin/dma_channel_link.rs new file mode 100644 index 000000000..d585f8e3a --- /dev/null +++ b/examples/src/bin/dma_channel_link.rs @@ -0,0 +1,396 @@ +//! 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: +//! - `dma::edma_tcd()` accessor for simplified register access +//! - `DmaChannel::new()` for channel creation +//! - `DmaChannel::is_done()` and `clear_done()` helper methods +//! - Channel linking with `set_minor_link()` and `set_major_link()` + +#![no_std] +#![no_main] + +use core::sync::atomic::{AtomicBool, Ordering}; +use embassy_executor::Spawner; +use embassy_mcxa::clocks::config::Div8; +use embassy_mcxa::clocks::Gate; +use embassy_mcxa::dma::{edma_tcd, DmaChannel}; +use embassy_mcxa::{bind_interrupts, dma}; +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]; + +static DMA_CH2_DONE: AtomicBool = AtomicBool::new(false); + +// Custom DMA interrupt handlers for channel linking +// CH0 and CH1 just clear flags, CH2 signals completion + +pub struct Ch0Handler; +impl embassy_mcxa::interrupt::typelevel::Handler for Ch0Handler { + unsafe fn on_interrupt() { + let edma = edma_tcd(); + edma.tcd(0).ch_int().write(|w| w.int().clear_bit_by_one()); + if edma.tcd(0).ch_csr().read().done().bit_is_set() { + edma.tcd(0).ch_csr().write(|w| w.done().clear_bit_by_one()); + } + } +} + +pub struct Ch1Handler; +impl embassy_mcxa::interrupt::typelevel::Handler for Ch1Handler { + unsafe fn on_interrupt() { + let edma = edma_tcd(); + edma.tcd(1).ch_int().write(|w| w.int().clear_bit_by_one()); + if edma.tcd(1).ch_csr().read().done().bit_is_set() { + edma.tcd(1).ch_csr().write(|w| w.done().clear_bit_by_one()); + } + } +} + +pub struct Ch2Handler; +impl embassy_mcxa::interrupt::typelevel::Handler for Ch2Handler { + unsafe fn on_interrupt() { + let edma = edma_tcd(); + edma.tcd(2).ch_int().write(|w| w.int().clear_bit_by_one()); + if edma.tcd(2).ch_csr().read().done().bit_is_set() { + edma.tcd(2).ch_csr().write(|w| w.done().clear_bit_by_one()); + } + DMA_CH2_DONE.store(true, Ordering::Release); + } +} + +bind_interrupts!(struct Irqs { + DMA_CH0 => Ch0Handler; + DMA_CH1 => Ch1Handler; + DMA_CH2 => Ch2Handler; +}); + +/// Helper to write a u32 as decimal ASCII to UART +fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { + let mut buf = [0u8; 10]; + let mut n = val; + let mut i = buf.len(); + + if n == 0 { + tx.blocking_write(b"0").ok(); + return; + } + + while n > 0 { + i -= 1; + buf[i] = b'0' + (n % 10) as u8; + n /= 10; + } + + tx.blocking_write(&buf[i..]).ok(); +} + +/// Helper to print a buffer to UART +fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { + tx.blocking_write(b"[").ok(); + unsafe { + for i in 0..len { + write_u32(tx, *buf_ptr.add(i)); + if i < len - 1 { + tx.blocking_write(b", ").ok(); + } + } + } + tx.blocking_write(b"]").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..."); + + // Enable DMA0 clock and release reset + unsafe { + hal::peripherals::DMA0::enable_clock(); + hal::peripherals::DMA0::release_reset(); + } + + let pac_periphs = unsafe { pac::Peripherals::steal() }; + + unsafe { + dma::init(&pac_periphs); + } + + // Use edma_tcd() accessor instead of passing register block around + let edma = edma_tcd(); + let dma0 = &pac_periphs.dma0; + + // 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() + }); + + unsafe { + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH1); + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH2); + } + + let config = Config { + baudrate_bps: 115_200, + enable_tx: true, + enable_rx: false, + ..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(edma, 1); // Link to CH1 after each minor loop + ch0.set_major_link(edma, 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(edma); } + + // Wait for CH1 to complete (triggered by CH0 minor link) + while !ch1.is_done(edma) { + cortex_m::asm::nop(); + } + unsafe { ch1.clear_done(edma); } + + 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(edma); } + + // Wait for CH0 major loop to complete + while !ch0.is_done(edma) { + cortex_m::asm::nop(); + } + unsafe { ch0.clear_done(edma); } + + tx.blocking_write(b"CH0 major loop done.\r\n").unwrap(); + + // Wait for CH2 to complete (triggered by CH0 major link) + while !DMA_CH2_DONE.load(Ordering::Acquire) { + cortex_m::asm::nop(); + } + + 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/src/bin/dma_interleave_transfer.rs b/examples/src/bin/dma_interleave_transfer.rs new file mode 100644 index 000000000..710f18de3 --- /dev/null +++ b/examples/src/bin/dma_interleave_transfer.rs @@ -0,0 +1,226 @@ +//! 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: +//! - `dma::edma_tcd()` accessor for simplified register access +//! - `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::clocks::Gate; +use embassy_mcxa::dma::{edma_tcd, DmaChannel, DmaCh0InterruptHandler}; +use embassy_mcxa::{bind_interrupts, dma}; +use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; +use embassy_mcxa::pac; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +// Bind DMA channel 0 interrupt using Embassy-style macro +bind_interrupts!(struct Irqs { + DMA_CH0 => DmaCh0InterruptHandler; +}); + +const BUFFER_LENGTH: usize = 16; +const HALF_BUFF_LENGTH: usize = BUFFER_LENGTH / 2; + +// Buffers in RAM +static mut SRC_BUFFER: [u32; HALF_BUFF_LENGTH] = [0; HALF_BUFF_LENGTH]; +static mut DEST_BUFFER: [u32; BUFFER_LENGTH] = [0; BUFFER_LENGTH]; + +/// Helper to write a u32 as decimal ASCII to UART +fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { + let mut buf = [0u8; 10]; + let mut n = val; + let mut i = buf.len(); + + if n == 0 { + tx.blocking_write(b"0").ok(); + return; + } + + while n > 0 { + i -= 1; + buf[i] = b'0' + (n % 10) as u8; + n /= 10; + } + + tx.blocking_write(&buf[i..]).ok(); +} + +/// Helper to print a buffer to UART +fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { + tx.blocking_write(b"[").ok(); + unsafe { + for i in 0..len { + write_u32(tx, *buf_ptr.add(i)); + if i < len - 1 { + tx.blocking_write(b", ").ok(); + } + } + } + tx.blocking_write(b"]").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..."); + + // Enable DMA0 clock and release reset + unsafe { + hal::peripherals::DMA0::enable_clock(); + hal::peripherals::DMA0::release_reset(); + } + + let pac_periphs = unsafe { pac::Peripherals::steal() }; + + unsafe { + dma::init(&pac_periphs); + } + + // Enable DMA interrupt + unsafe { + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); + } + + let config = Config { + baudrate_bps: 115_200, + enable_tx: true, + enable_rx: false, + ..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 + unsafe { + SRC_BUFFER = [1, 2, 3, 4, 5, 6, 7, 8]; + DEST_BUFFER = [0; BUFFER_LENGTH]; + } + + tx.blocking_write(b"Source Buffer: ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(SRC_BUFFER) as *const u32, HALF_BUFF_LENGTH); + tx.blocking_write(b"\r\n").unwrap(); + + tx.blocking_write(b"Destination Buffer (before): ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER) as *const u32, BUFFER_LENGTH); + 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); + + // Use edma_tcd() accessor instead of passing register block around + let edma = edma_tcd(); + + // 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 = edma.tcd(0); + + // 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(core::ptr::addr_of_mut!(SRC_BUFFER) as u32)); + t.tcd_daddr().write(|w| w.daddr().bits(core::ptr::addr_of_mut!(DEST_BUFFER) 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(edma); + } + + // Wait for completion using channel helper method + while !dma_ch0.is_done(edma) { + cortex_m::asm::nop(); + } + unsafe { dma_ch0.clear_done(edma); } + + 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, core::ptr::addr_of!(DEST_BUFFER) as *const u32, BUFFER_LENGTH); + 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; + unsafe { + for i in 0..BUFFER_LENGTH { + if i % 2 == 0 { + if DEST_BUFFER[i] != SRC_BUFFER[i / 2] { + mismatch = true; + } + } else if DEST_BUFFER[i] != 0 { + mismatch = true; + } + } + } + + 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/src/bin/dma_mem_to_mem.rs b/examples/src/bin/dma_mem_to_mem.rs new file mode 100644 index 000000000..e193e8c6a --- /dev/null +++ b/examples/src/bin/dma_mem_to_mem.rs @@ -0,0 +1,248 @@ +//! DMA memory-to-memory transfer example for MCXA276. +//! +//! This example demonstrates using DMA to copy data between memory buffers +//! using the Embassy-style async API with type-safe transfers. +//! +//! # Embassy-style features demonstrated: +//! - `TransferOptions` for configuration +//! - Type-safe `mem_to_mem()` method with async `.await` +//! - `Transfer` Future that can be `.await`ed +//! - `Word` trait for automatic transfer width detection +//! - `memset()` method for filling memory with a pattern + +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_mcxa::clocks::config::Div8; +use embassy_mcxa::clocks::Gate; +use embassy_mcxa::dma::{DmaChannel, DmaCh0InterruptHandler, TransferOptions}; +use embassy_mcxa::{bind_interrupts, dma}; +use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; +use embassy_mcxa::pac; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +// Bind DMA channel 0 interrupt using Embassy-style macro +bind_interrupts!(struct Irqs { + DMA_CH0 => DmaCh0InterruptHandler; +}); + +const BUFFER_LENGTH: usize = 4; + +// Buffers in RAM (static mut is automatically placed in .bss/.data) +static mut SRC_BUFFER: [u32; BUFFER_LENGTH] = [0; BUFFER_LENGTH]; +static mut DEST_BUFFER: [u32; BUFFER_LENGTH] = [0; BUFFER_LENGTH]; +static mut MEMSET_BUFFER: [u32; BUFFER_LENGTH] = [0; BUFFER_LENGTH]; + +/// Helper to write a u32 as decimal ASCII to UART +fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { + let mut buf = [0u8; 10]; // u32 max is 4294967295 (10 digits) + let mut n = val; + let mut i = buf.len(); + + if n == 0 { + tx.blocking_write(b"0").ok(); + return; + } + + while n > 0 { + i -= 1; + buf[i] = b'0' + (n % 10) as u8; + n /= 10; + } + + tx.blocking_write(&buf[i..]).ok(); +} + +/// Helper to print a buffer as [v1, v2, v3, v4] to UART +/// Takes a raw pointer to avoid warnings about shared references to mutable statics +fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const [u32; BUFFER_LENGTH]) { + tx.blocking_write(b"[").ok(); + unsafe { + let buf = &*buf_ptr; + for (i, val) in buf.iter().enumerate() { + write_u32(tx, *val); + if i < buf.len() - 1 { + tx.blocking_write(b", ").ok(); + } + } + } + tx.blocking_write(b"]").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 memory-to-memory example starting..."); + + // Enable DMA0 clock and release reset + unsafe { + hal::peripherals::DMA0::enable_clock(); + hal::peripherals::DMA0::release_reset(); + } + + // Get PAC peripherals for DMA init + let pac_periphs = unsafe { pac::Peripherals::steal() }; + + // Initialize DMA + unsafe { + dma::init(&pac_periphs); + } + + // Enable DMA interrupt + unsafe { + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); + } + + // Create UART for debug output + let config = Config { + baudrate_bps: 115_200, + enable_tx: true, + enable_rx: false, + ..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 memory to memory example begin.\r\n\r\n") + .unwrap(); + + // Initialize buffers + unsafe { + SRC_BUFFER = [1, 2, 3, 4]; + DEST_BUFFER = [0; BUFFER_LENGTH]; + } + + tx.blocking_write(b"Source Buffer: ").unwrap(); + print_buffer(&mut tx, &raw const SRC_BUFFER); + tx.blocking_write(b"\r\n").unwrap(); + + tx.blocking_write(b"Destination Buffer (before): ").unwrap(); + print_buffer(&mut tx, &raw const DEST_BUFFER); + tx.blocking_write(b"\r\n").unwrap(); + + tx.blocking_write(b"Configuring DMA with Embassy-style API...\r\n") + .unwrap(); + + // Create DMA channel + let dma_ch0 = DmaChannel::new(p.DMA_CH0); + + // Configure transfer options (Embassy-style) + // TransferOptions defaults to: complete_transfer_interrupt = true + let options = TransferOptions::default(); + + // ========================================================================= + // Part 1: Embassy-style async API demonstration (mem_to_mem) + // ========================================================================= + // + // Use the new type-safe `mem_to_mem()` method: + // - Automatically determines transfer width from buffer element type (u32) + // - Returns a `Transfer` future that can be `.await`ed + // - Uses TransferOptions for consistent configuration + // + // Using async `.await` - the executor can run other tasks while waiting! + + // Perform type-safe memory-to-memory transfer using Embassy-style async API + unsafe { + let src = &*core::ptr::addr_of!(SRC_BUFFER); + let dst = &mut *core::ptr::addr_of_mut!(DEST_BUFFER); + + // Using async `.await` - the executor can run other tasks while waiting! + let transfer = dma_ch0.mem_to_mem(src, dst, options); + transfer.await; + } + + tx.blocking_write(b"DMA mem-to-mem transfer complete!\r\n\r\n") + .unwrap(); + tx.blocking_write(b"Destination Buffer (after): ").unwrap(); + print_buffer(&mut tx, &raw const DEST_BUFFER); + tx.blocking_write(b"\r\n").unwrap(); + + // Verify data + let mut mismatch = false; + unsafe { + for i in 0..BUFFER_LENGTH { + if SRC_BUFFER[i] != DEST_BUFFER[i] { + mismatch = true; + break; + } + } + } + + if mismatch { + tx.blocking_write(b"FAIL: mem_to_mem mismatch!\r\n").unwrap(); + defmt::error!("FAIL: mem_to_mem mismatch!"); + } else { + tx.blocking_write(b"PASS: mem_to_mem verified.\r\n\r\n").unwrap(); + defmt::info!("PASS: mem_to_mem verified."); + } + + // ========================================================================= + // Part 2: memset() demonstration + // ========================================================================= + // + // The `memset()` method fills a buffer with a pattern value: + // - Fixed source address (pattern is read repeatedly) + // - Incrementing destination address + // - Uses the same Transfer future pattern + + tx.blocking_write(b"--- Demonstrating memset() feature ---\r\n\r\n").unwrap(); + + tx.blocking_write(b"Memset Buffer (before): ").unwrap(); + print_buffer(&mut tx, &raw const MEMSET_BUFFER); + tx.blocking_write(b"\r\n").unwrap(); + + // Fill buffer with a pattern value using DMA memset + let pattern: u32 = 0xDEADBEEF; + tx.blocking_write(b"Filling with pattern 0xDEADBEEF...\r\n").unwrap(); + + unsafe { + let dst = &mut *core::ptr::addr_of_mut!(MEMSET_BUFFER); + + // Using blocking_wait() for demonstration - also shows non-async usage + let transfer = dma_ch0.memset(&pattern, dst, options); + transfer.blocking_wait(); + } + + tx.blocking_write(b"DMA memset complete!\r\n\r\n").unwrap(); + tx.blocking_write(b"Memset Buffer (after): ").unwrap(); + print_buffer(&mut tx, &raw const MEMSET_BUFFER); + tx.blocking_write(b"\r\n").unwrap(); + + // Verify memset result + let mut memset_ok = true; + unsafe { + #[allow(clippy::needless_range_loop)] + for i in 0..BUFFER_LENGTH { + if MEMSET_BUFFER[i] != pattern { + memset_ok = false; + break; + } + } + } + + if !memset_ok { + tx.blocking_write(b"FAIL: memset mismatch!\r\n").unwrap(); + defmt::error!("FAIL: memset mismatch!"); + } else { + tx.blocking_write(b"PASS: memset verified.\r\n\r\n").unwrap(); + defmt::info!("PASS: memset verified."); + } + + tx.blocking_write(b"=== All DMA tests complete ===\r\n").unwrap(); + + loop { + cortex_m::asm::wfe(); + } +} + diff --git a/examples/src/bin/dma_memset.rs b/examples/src/bin/dma_memset.rs new file mode 100644 index 000000000..b76ba988d --- /dev/null +++ b/examples/src/bin/dma_memset.rs @@ -0,0 +1,232 @@ +//! 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: +//! - `dma::edma_tcd()` accessor for simplified register access +//! - `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::clocks::Gate; +use embassy_mcxa::dma::{edma_tcd, DmaChannel, DmaCh0InterruptHandler}; +use embassy_mcxa::{bind_interrupts, dma}; +use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; +use embassy_mcxa::pac; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +// Bind DMA channel 0 interrupt using Embassy-style macro +bind_interrupts!(struct Irqs { + DMA_CH0 => DmaCh0InterruptHandler; +}); + +const BUFFER_LENGTH: usize = 4; + +// Buffers in RAM +static mut PATTERN: u32 = 0; +static mut DEST_BUFFER: [u32; BUFFER_LENGTH] = [0; BUFFER_LENGTH]; + +/// Helper to write a u32 as decimal ASCII to UART +fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { + let mut buf = [0u8; 10]; + let mut n = val; + let mut i = buf.len(); + + if n == 0 { + tx.blocking_write(b"0").ok(); + return; + } + + while n > 0 { + i -= 1; + buf[i] = b'0' + (n % 10) as u8; + n /= 10; + } + + tx.blocking_write(&buf[i..]).ok(); +} + +/// Helper to print a buffer to UART +fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { + tx.blocking_write(b"[").ok(); + unsafe { + for i in 0..len { + write_u32(tx, *buf_ptr.add(i)); + if i < len - 1 { + tx.blocking_write(b", ").ok(); + } + } + } + tx.blocking_write(b"]").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 memset example starting..."); + + // Enable DMA0 clock and release reset + unsafe { + hal::peripherals::DMA0::enable_clock(); + hal::peripherals::DMA0::release_reset(); + } + + let pac_periphs = unsafe { pac::Peripherals::steal() }; + + unsafe { + dma::init(&pac_periphs); + } + + // Enable DMA interrupt + unsafe { + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); + } + + let config = Config { + baudrate_bps: 115_200, + enable_tx: true, + enable_rx: false, + ..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 memset example begin.\r\n\r\n") + .unwrap(); + + // Initialize buffers + unsafe { + PATTERN = 0xDEADBEEF; + DEST_BUFFER = [0; BUFFER_LENGTH]; + } + + tx.blocking_write(b"Pattern value: 0x").unwrap(); + // Print pattern in hex + unsafe { + let hex_chars = b"0123456789ABCDEF"; + let mut hex_buf = [0u8; 8]; + let mut val = PATTERN; + for i in (0..8).rev() { + hex_buf[i] = hex_chars[(val & 0xF) as usize]; + val >>= 4; + } + tx.blocking_write(&hex_buf).ok(); + } + tx.blocking_write(b"\r\n").unwrap(); + + tx.blocking_write(b"Destination Buffer (before): ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER) as *const u32, BUFFER_LENGTH); + 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); + + // Use edma_tcd() accessor instead of passing register block around + let edma = edma_tcd(); + + // Configure memset transfer using direct TCD access: + // Source stays fixed (soff = 0, reads same pattern repeatedly) + // Destination increments (doff = 4) + unsafe { + let t = edma.tcd(0); + + // 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(core::ptr::addr_of_mut!(PATTERN) as u32)); + // Destination address - increments + t.tcd_daddr().write(|w| w.daddr().bits(core::ptr::addr_of_mut!(DEST_BUFFER) 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(); + + tx.blocking_write(b"Triggering transfer...\r\n").unwrap(); + dma_ch0.trigger_start(edma); + } + + // Wait for completion using channel helper method + while !dma_ch0.is_done(edma) { + cortex_m::asm::nop(); + } + unsafe { dma_ch0.clear_done(edma); } + + tx.blocking_write(b"\r\nEDMA memset example finish.\r\n\r\n") + .unwrap(); + tx.blocking_write(b"Destination Buffer (after): ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER) as *const u32, BUFFER_LENGTH); + tx.blocking_write(b"\r\n\r\n").unwrap(); + + // Verify: All elements should equal PATTERN + let mut mismatch = false; + unsafe { + #[allow(clippy::needless_range_loop)] + for i in 0..BUFFER_LENGTH { + if DEST_BUFFER[i] != PATTERN { + 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/src/bin/dma_ping_pong_transfer.rs b/examples/src/bin/dma_ping_pong_transfer.rs new file mode 100644 index 000000000..13ad9782d --- /dev/null +++ b/examples/src/bin/dma_ping_pong_transfer.rs @@ -0,0 +1,384 @@ +//! 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 interrupt handler with AtomicBool flag +//! +//! ## 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: +//! - `dma::edma_tcd()` accessor for simplified register access +//! - `DmaChannel::new()` for channel creation +//! - Scatter/gather with linked TCDs +//! - 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::clocks::Gate; +use embassy_mcxa::dma::{edma_tcd, DmaChannel, DmaCh1InterruptHandler, Tcd, TransferOptions}; +use embassy_mcxa::{bind_interrupts, dma}; +use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; +use embassy_mcxa::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]); + +static TRANSFER_DONE: AtomicBool = AtomicBool::new(false); + +// Custom DMA interrupt handler for ping-pong transfer +// We need a custom handler because we signal completion via TRANSFER_DONE flag +// and don't clear DONE bit when using Scatter/Gather (ESG=1) +pub struct PingPongDmaHandler; + +impl embassy_mcxa::interrupt::typelevel::Handler for PingPongDmaHandler { + unsafe fn on_interrupt() { + let edma = edma_tcd(); + + // Clear interrupt flag + edma.tcd(0).ch_int().write(|w| w.int().clear_bit_by_one()); + + // Do NOT clear DONE bit when using Scatter/Gather (ESG=1), + // as the hardware loads the next TCD which resets the status. + + TRANSFER_DONE.store(true, Ordering::Release); + } +} + +bind_interrupts!(struct Irqs { + DMA_CH0 => PingPongDmaHandler; + DMA_CH1 => DmaCh1InterruptHandler; // For wait_half() demo +}); + +/// Helper to write a u32 as decimal ASCII to UART +fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { + let mut buf = [0u8; 10]; + let mut n = val; + let mut i = buf.len(); + + if n == 0 { + tx.blocking_write(b"0").ok(); + return; + } + + while n > 0 { + i -= 1; + buf[i] = b'0' + (n % 10) as u8; + n /= 10; + } + + tx.blocking_write(&buf[i..]).ok(); +} + +/// Helper to print a buffer to UART +fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { + tx.blocking_write(b"[").ok(); + unsafe { + for i in 0..len { + write_u32(tx, *buf_ptr.add(i)); + if i < len - 1 { + tx.blocking_write(b", ").ok(); + } + } + } + tx.blocking_write(b"]").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 DMA0 clock and release reset + unsafe { + hal::peripherals::DMA0::enable_clock(); + hal::peripherals::DMA0::release_reset(); + } + + let pac_periphs = unsafe { pac::Peripherals::steal() }; + + unsafe { + dma::init(&pac_periphs); + } + + // Use edma_tcd() accessor instead of passing register block around + let edma = edma_tcd(); + + // Enable DMA interrupt + unsafe { + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); + } + + let config = Config { + baudrate_bps: 115_200, + enable_tx: true, + enable_rx: false, + ..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(edma, &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(edma); + } + + // 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(edma); + } + + // 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/src/bin/dma_scatter_gather.rs b/examples/src/bin/dma_scatter_gather.rs new file mode 100644 index 000000000..86dd881cd --- /dev/null +++ b/examples/src/bin/dma_scatter_gather.rs @@ -0,0 +1,281 @@ +//! 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: +//! - `dma::edma_tcd()` accessor for simplified register access +//! - `DmaChannel::new()` for channel creation +//! - Scatter/gather with chained TCDs + +#![no_std] +#![no_main] + +use core::sync::atomic::{AtomicBool, Ordering}; +use embassy_executor::Spawner; +use embassy_mcxa::clocks::config::Div8; +use embassy_mcxa::clocks::Gate; +use embassy_mcxa::dma::{edma_tcd, DmaChannel, Tcd}; +use embassy_mcxa::{bind_interrupts, dma}; +use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; +use embassy_mcxa::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]); + +static TRANSFER_DONE: AtomicBool = AtomicBool::new(false); + +// Custom DMA interrupt handler for scatter-gather transfer +// We need a custom handler because we signal completion via TRANSFER_DONE flag +// and need to conditionally clear DONE bit based on ESG status +pub struct ScatterGatherDmaHandler; + +impl embassy_mcxa::interrupt::typelevel::Handler for ScatterGatherDmaHandler { + unsafe fn on_interrupt() { + let edma = edma_tcd(); + + // Clear interrupt flag + edma.tcd(0).ch_int().write(|w| w.int().clear_bit_by_one()); + + // If ESG=1 (Scatter/Gather), the hardware loads the next TCD and clears DONE. + // If ESG=0 (Last TCD), DONE remains set and must be cleared. + if edma.tcd(0).ch_csr().read().done().bit_is_set() { + edma.tcd(0).ch_csr().write(|w| w.done().clear_bit_by_one()); + } + + TRANSFER_DONE.store(true, Ordering::Release); + } +} + +bind_interrupts!(struct Irqs { + DMA_CH0 => ScatterGatherDmaHandler; +}); + +/// Helper to write a u32 as decimal ASCII to UART +fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { + let mut buf = [0u8; 10]; + let mut n = val; + let mut i = buf.len(); + + if n == 0 { + tx.blocking_write(b"0").ok(); + return; + } + + while n > 0 { + i -= 1; + buf[i] = b'0' + (n % 10) as u8; + n /= 10; + } + + tx.blocking_write(&buf[i..]).ok(); +} + +/// Helper to print a buffer to UART +fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { + tx.blocking_write(b"[").ok(); + unsafe { + for i in 0..len { + write_u32(tx, *buf_ptr.add(i)); + if i < len - 1 { + tx.blocking_write(b", ").ok(); + } + } + } + tx.blocking_write(b"]").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..."); + + // Enable DMA0 clock and release reset + unsafe { + hal::peripherals::DMA0::enable_clock(); + hal::peripherals::DMA0::release_reset(); + } + + let pac_periphs = unsafe { pac::Peripherals::steal() }; + + unsafe { + dma::init(&pac_periphs); + } + + // Use edma_tcd() accessor instead of passing register block around + let edma = edma_tcd(); + + // Enable DMA interrupt + unsafe { + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); + } + + let config = Config { + baudrate_bps: 115_200, + enable_tx: true, + enable_rx: false, + ..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(edma, &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(edma); + } + + // 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(edma); + } + + // 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/src/bin/dma_scatter_gather_builder.rs b/examples/src/bin/dma_scatter_gather_builder.rs new file mode 100644 index 000000000..078e26c60 --- /dev/null +++ b/examples/src/bin/dma_scatter_gather_builder.rs @@ -0,0 +1,244 @@ +//! DMA Scatter-Gather Builder example for MCXA276. +//! +//! This example demonstrates using the new `ScatterGatherBuilder` API for +//! chaining multiple DMA transfers with a type-safe builder pattern. +//! +//! # Features demonstrated: +//! - `ScatterGatherBuilder::new()` for creating a builder +//! - `add_transfer()` for adding memory-to-memory segments +//! - `build()` to start the chained transfer +//! - Automatic TCD linking and ESG bit management +//! +//! # Comparison with manual scatter-gather: +//! The manual approach (see `dma_scatter_gather.rs`) requires: +//! - Manual TCD pool allocation and alignment +//! - Manual CSR/ESG/INTMAJOR bit manipulation +//! - Manual dlast_sga address calculations +//! +//! The builder approach handles all of this automatically! + +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_mcxa::clocks::config::Div8; +use embassy_mcxa::clocks::Gate; +use embassy_mcxa::dma::{DmaChannel, DmaCh0InterruptHandler, ScatterGatherBuilder}; +use embassy_mcxa::{bind_interrupts, dma}; +use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; +use embassy_mcxa::pac; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +// Bind DMA channel 0 interrupt +bind_interrupts!(struct Irqs { + DMA_CH0 => DmaCh0InterruptHandler; +}); + +// Source buffers (multiple segments) +static mut SRC1: [u32; 4] = [0x11111111, 0x22222222, 0x33333333, 0x44444444]; +static mut SRC2: [u32; 4] = [0xAAAAAAAA, 0xBBBBBBBB, 0xCCCCCCCC, 0xDDDDDDDD]; +static mut SRC3: [u32; 4] = [0x12345678, 0x9ABCDEF0, 0xFEDCBA98, 0x76543210]; + +// Destination buffers (one per segment) +static mut DST1: [u32; 4] = [0; 4]; +static mut DST2: [u32; 4] = [0; 4]; +static mut DST3: [u32; 4] = [0; 4]; + +/// Helper to write a u32 as hex to UART +fn write_hex(tx: &mut LpuartTx<'_, Blocking>, val: u32) { + const HEX: &[u8; 16] = b"0123456789ABCDEF"; + for i in (0..8).rev() { + let nibble = ((val >> (i * 4)) & 0xF) as usize; + tx.blocking_write(&[HEX[nibble]]).ok(); + } +} + +/// Helper to print a buffer to UART +fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { + tx.blocking_write(b"[").ok(); + unsafe { + for i in 0..len { + write_hex(tx, *buf_ptr.add(i)); + if i < len - 1 { + tx.blocking_write(b", ").ok(); + } + } + } + tx.blocking_write(b"]").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 Builder example starting..."); + + // Enable DMA0 clock and release reset + unsafe { + hal::peripherals::DMA0::enable_clock(); + hal::peripherals::DMA0::release_reset(); + } + + let pac_periphs = unsafe { pac::Peripherals::steal() }; + + // Initialize DMA + unsafe { + dma::init(&pac_periphs); + } + + // Enable DMA interrupt + unsafe { + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); + } + + // Create UART for debug output + let config = Config { + baudrate_bps: 115_200, + enable_tx: true, + enable_rx: false, + ..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"DMA Scatter-Gather Builder Example\r\n").unwrap(); + tx.blocking_write(b"===================================\r\n\r\n").unwrap(); + + // Show source buffers + tx.blocking_write(b"Source buffers:\r\n").unwrap(); + tx.blocking_write(b" SRC1: ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(SRC1) as *const u32, 4); + tx.blocking_write(b"\r\n").unwrap(); + tx.blocking_write(b" SRC2: ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(SRC2) as *const u32, 4); + tx.blocking_write(b"\r\n").unwrap(); + tx.blocking_write(b" SRC3: ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(SRC3) as *const u32, 4); + tx.blocking_write(b"\r\n\r\n").unwrap(); + + tx.blocking_write(b"Destination buffers (before):\r\n").unwrap(); + tx.blocking_write(b" DST1: ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DST1) as *const u32, 4); + tx.blocking_write(b"\r\n").unwrap(); + tx.blocking_write(b" 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" DST3: ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DST3) as *const u32, 4); + tx.blocking_write(b"\r\n\r\n").unwrap(); + + // Create DMA channel + let dma_ch0 = DmaChannel::new(p.DMA_CH0); + + tx.blocking_write(b"Building scatter-gather chain with builder API...\r\n").unwrap(); + + // ========================================================================= + // ScatterGatherBuilder API demonstration + // ========================================================================= + // + // The builder pattern makes scatter-gather transfers much easier: + // 1. Create a builder + // 2. Add transfer segments with add_transfer() + // 3. Call build() to start the entire chain + // No manual TCD manipulation required! + + let mut builder = ScatterGatherBuilder::::new(); + + // Add three transfer segments - the builder handles TCD linking automatically + unsafe { + let src1 = &*core::ptr::addr_of!(SRC1); + let dst1 = &mut *core::ptr::addr_of_mut!(DST1); + builder.add_transfer(src1, dst1); + } + + unsafe { + let src2 = &*core::ptr::addr_of!(SRC2); + let dst2 = &mut *core::ptr::addr_of_mut!(DST2); + builder.add_transfer(src2, dst2); + } + + unsafe { + let src3 = &*core::ptr::addr_of!(SRC3); + let dst3 = &mut *core::ptr::addr_of_mut!(DST3); + builder.add_transfer(src3, dst3); + } + + tx.blocking_write(b"Added 3 transfer segments to chain.\r\n").unwrap(); + tx.blocking_write(b"Starting scatter-gather transfer with .await...\r\n\r\n").unwrap(); + + // Build and execute the scatter-gather chain + // The build() method: + // - Links all TCDs together with ESG bit + // - Sets INTMAJOR on all TCDs + // - Loads the first TCD into hardware + // - Returns a Transfer future + unsafe { + let transfer = builder.build(&dma_ch0).expect("Failed to build scatter-gather"); + transfer.blocking_wait(); + } + + tx.blocking_write(b"Scatter-gather transfer complete!\r\n\r\n").unwrap(); + + // Show results + tx.blocking_write(b"Destination buffers (after):\r\n").unwrap(); + tx.blocking_write(b" DST1: ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DST1) as *const u32, 4); + tx.blocking_write(b"\r\n").unwrap(); + tx.blocking_write(b" 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" DST3: ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DST3) as *const u32, 4); + tx.blocking_write(b"\r\n\r\n").unwrap(); + + // Verify all three segments + let mut all_ok = true; + unsafe { + let src1 = core::ptr::addr_of!(SRC1) as *const u32; + let dst1 = core::ptr::addr_of!(DST1) as *const u32; + for i in 0..4 { + if *src1.add(i) != *dst1.add(i) { + all_ok = false; + } + } + + let src2 = core::ptr::addr_of!(SRC2) as *const u32; + let dst2 = core::ptr::addr_of!(DST2) as *const u32; + for i in 0..4 { + if *src2.add(i) != *dst2.add(i) { + all_ok = false; + } + } + + let src3 = core::ptr::addr_of!(SRC3) as *const u32; + let dst3 = core::ptr::addr_of!(DST3) as *const u32; + for i in 0..4 { + if *src3.add(i) != *dst3.add(i) { + all_ok = false; + } + } + } + + if all_ok { + tx.blocking_write(b"PASS: All segments verified!\r\n").unwrap(); + defmt::info!("PASS: All segments verified!"); + } else { + tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); + defmt::error!("FAIL: Mismatch detected!"); + } + + tx.blocking_write(b"\r\n=== Scatter-Gather Builder example complete ===\r\n").unwrap(); + + loop { + cortex_m::asm::wfe(); + } +} diff --git a/examples/src/bin/dma_wrap_transfer.rs b/examples/src/bin/dma_wrap_transfer.rs new file mode 100644 index 000000000..b115a2c19 --- /dev/null +++ b/examples/src/bin/dma_wrap_transfer.rs @@ -0,0 +1,231 @@ +//! DMA wrap transfer example for MCXA276. +//! +//! This example demonstrates using DMA with modulo addressing to wrap around +//! a source buffer, effectively repeating the source data in the destination. +//! +//! # Embassy-style features demonstrated: +//! - `dma::edma_tcd()` accessor for simplified register access +//! - `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::clocks::Gate; +use embassy_mcxa::dma::{edma_tcd, DmaChannel, DmaCh0InterruptHandler}; +use embassy_mcxa::{bind_interrupts, dma}; +use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; +use embassy_mcxa::pac; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +// Bind DMA channel 0 interrupt using Embassy-style macro +bind_interrupts!(struct Irqs { + DMA_CH0 => DmaCh0InterruptHandler; +}); + +// Source buffer: 4 words (16 bytes), aligned to 16 bytes for modulo +#[repr(align(16))] +struct AlignedSrc([u32; 4]); + +static mut SRC: AlignedSrc = AlignedSrc([0; 4]); +static mut DST: [u32; 8] = [0; 8]; + +/// Helper to write a u32 as decimal ASCII to UART +fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { + let mut buf = [0u8; 10]; + let mut n = val; + let mut i = buf.len(); + + if n == 0 { + tx.blocking_write(b"0").ok(); + return; + } + + while n > 0 { + i -= 1; + buf[i] = b'0' + (n % 10) as u8; + n /= 10; + } + + tx.blocking_write(&buf[i..]).ok(); +} + +/// Helper to print a buffer to UART +fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { + tx.blocking_write(b"[").ok(); + unsafe { + for i in 0..len { + write_u32(tx, *buf_ptr.add(i)); + if i < len - 1 { + tx.blocking_write(b", ").ok(); + } + } + } + tx.blocking_write(b"]").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 wrap transfer example starting..."); + + // Enable DMA0 clock and release reset + unsafe { + hal::peripherals::DMA0::enable_clock(); + hal::peripherals::DMA0::release_reset(); + } + + let pac_periphs = unsafe { pac::Peripherals::steal() }; + + unsafe { + dma::init(&pac_periphs); + } + + // Enable DMA interrupt + unsafe { + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); + } + + let config = Config { + baudrate_bps: 115_200, + enable_tx: true, + enable_rx: false, + ..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 wrap transfer example begin.\r\n\r\n") + .unwrap(); + + // Initialize buffers + unsafe { + SRC.0 = [1, 2, 3, 4]; + DST = [0; 8]; + } + + tx.blocking_write(b"Source Buffer: ").unwrap(); + print_buffer(&mut tx, unsafe { core::ptr::addr_of!(SRC.0) } as *const u32, 4); + 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 DMA with Embassy-style API...\r\n") + .unwrap(); + + // Create DMA channel using Embassy-style API + let dma_ch0 = DmaChannel::new(p.DMA_CH0); + + // Use edma_tcd() accessor instead of passing register block around + let edma = edma_tcd(); + + // Configure wrap transfer using direct TCD access: + // SRC is 16 bytes (4 * u32). We want to transfer 32 bytes (8 * u32). + // SRC modulo is 16 bytes (2^4 = 16) - wraps source address. + // DST modulo is 0 (disabled). + // This causes the source address to wrap around after 16 bytes, + // effectively repeating the source data. + unsafe { + let t = edma.tcd(0); + + // 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(core::ptr::addr_of!(SRC.0) as u32)); + t.tcd_daddr().write(|w| w.daddr().bits(core::ptr::addr_of_mut!(DST) as u32)); + + // Offsets: both increment by 4 bytes + t.tcd_soff().write(|w| w.soff().bits(4)); + t.tcd_doff().write(|w| w.doff().bits(4)); + + // Attributes: 32-bit transfers (size = 2) + // SMOD = 4 (2^4 = 16 byte modulo for source), DMOD = 0 (disabled) + t.tcd_attr().write(|w| { + w.ssize().bits(2) + .dsize().bits(2) + .smod().bits(4) // Source modulo: 2^4 = 16 bytes + .dmod().bits(0) // Dest modulo: disabled + }); + + // Transfer 32 bytes total in one minor loop + let nbytes = 32u32; + t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(nbytes)); + + // Source wraps via modulo, no adjustment needed + 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(); + + tx.blocking_write(b"Triggering transfer...\r\n").unwrap(); + dma_ch0.trigger_start(edma); + } + + // Wait for completion using channel helper method + while !dma_ch0.is_done(edma) { + cortex_m::asm::nop(); + } + unsafe { dma_ch0.clear_done(edma); } + + tx.blocking_write(b"\r\nEDMA wrap 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 be [1, 2, 3, 4, 1, 2, 3, 4] + let expected = [1u32, 2, 3, 4, 1, 2, 3, 4]; + let mut mismatch = false; + unsafe { + for i in 0..8 { + if DST[i] != expected[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/src/bin/lpuart_dma.rs b/examples/src/bin/lpuart_dma.rs new file mode 100644 index 000000000..5ccf97ecc --- /dev/null +++ b/examples/src/bin/lpuart_dma.rs @@ -0,0 +1,127 @@ +//! LPUART DMA example for MCXA276. +//! +//! This example demonstrates using DMA for UART TX and RX operations. +//! It sends a message using DMA, then waits for 16 characters to be received +//! via DMA and echoes them back. + +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_mcxa::clocks::config::Div8; +use embassy_mcxa::clocks::Gate; +use embassy_mcxa::dma::{self, DMA_REQ_LPUART2_RX, DMA_REQ_LPUART2_TX}; +use embassy_mcxa::lpuart::{Config, LpuartDma}; +use embassy_mcxa::pac; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +// DMA interrupt handlers +#[no_mangle] +pub extern "C" fn DMA_CH0() { + unsafe { dma::on_interrupt(0) }; +} + +#[no_mangle] +pub extern "C" fn DMA_CH1() { + unsafe { dma::on_interrupt(1) }; +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + 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!("LPUART DMA example starting..."); + + // Enable DMA0 clock and release reset + unsafe { + hal::peripherals::DMA0::enable_clock(); + hal::peripherals::DMA0::release_reset(); + } + + // Get PAC peripherals for DMA init + let pac_periphs = unsafe { pac::Peripherals::steal() }; + + // Initialize DMA + unsafe { + dma::init(&pac_periphs); + } + + // Get EDMA TCD register block for transfers + let edma = &pac_periphs.edma_0_tcd0; + + // Enable DMA interrupts + unsafe { + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH1); + } + + // Create UART configuration + let config = Config { + baudrate_bps: 115_200, + enable_tx: true, + enable_rx: true, + ..Default::default() + }; + + // Create UART instance with DMA channels + let mut lpuart = LpuartDma::new( + p.LPUART2, + p.P2_2, // TX pin + p.P2_3, // RX pin + p.DMA_CH0, // TX DMA channel + p.DMA_CH1, // RX DMA channel + config, + ) + .unwrap(); + + // Send a message using DMA + let tx_msg = b"Hello from LPUART2 DMA TX!\r\n"; + lpuart + .write_dma(edma, DMA_REQ_LPUART2_TX, tx_msg) + .await + .unwrap(); + + defmt::info!("TX DMA complete"); + + // Send prompt + let prompt = b"Type 16 characters to echo via DMA:\r\n"; + lpuart + .write_dma(edma, DMA_REQ_LPUART2_TX, prompt) + .await + .unwrap(); + + // Receive 16 characters using DMA + let mut rx_buf = [0u8; 16]; + lpuart + .read_dma(edma, DMA_REQ_LPUART2_RX, &mut rx_buf) + .await + .unwrap(); + + defmt::info!("RX DMA complete"); + + // Echo back the received data + let echo_prefix = b"\r\nReceived: "; + lpuart + .write_dma(edma, DMA_REQ_LPUART2_TX, echo_prefix) + .await + .unwrap(); + lpuart + .write_dma(edma, DMA_REQ_LPUART2_TX, &rx_buf) + .await + .unwrap(); + let done_msg = b"\r\nDone!\r\n"; + lpuart + .write_dma(edma, DMA_REQ_LPUART2_TX, done_msg) + .await + .unwrap(); + + defmt::info!("Example complete"); + + loop { + cortex_m::asm::wfe(); + } +} + diff --git a/examples/src/bin/lpuart_ring_buffer.rs b/examples/src/bin/lpuart_ring_buffer.rs new file mode 100644 index 000000000..bc666560c --- /dev/null +++ b/examples/src/bin/lpuart_ring_buffer.rs @@ -0,0 +1,162 @@ +//! LPUART Ring Buffer DMA example for MCXA276. +//! +//! This example demonstrates using the new `RingBuffer` API for continuous +//! circular DMA reception from a UART peripheral. +//! +//! # Features demonstrated: +//! - `setup_circular_read()` for continuous peripheral-to-memory DMA +//! - `RingBuffer` for async reading of received data +//! - Handling of potential overrun conditions +//! - Half-transfer and complete-transfer interrupts for timely wakeups +//! +//! # How it works: +//! 1. Set up a circular DMA transfer from LPUART RX to a ring buffer +//! 2. DMA continuously writes received bytes into the buffer, wrapping around +//! 3. Application asynchronously reads data as it arrives +//! 4. Both half-transfer and complete-transfer interrupts wake the reader + +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_mcxa::clocks::config::Div8; +use embassy_mcxa::clocks::Gate; +use embassy_mcxa::dma::{self, DmaChannel, DmaCh0InterruptHandler, DmaCh1InterruptHandler, DMA_REQ_LPUART2_RX}; +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 _}; + +// Bind DMA channel interrupts +bind_interrupts!(struct Irqs { + DMA_CH0 => DmaCh0InterruptHandler; + DMA_CH1 => DmaCh1InterruptHandler; +}); + +// Ring buffer for RX - power of 2 is ideal for modulo efficiency +static mut RX_RING_BUFFER: [u8; 64] = [0; 64]; + +/// Helper to write a byte as hex to UART +fn write_hex(tx: &mut LpuartTx<'_, Blocking>, byte: u8) { + const HEX: &[u8; 16] = b"0123456789ABCDEF"; + let buf = [HEX[(byte >> 4) as usize], HEX[(byte & 0x0F) as usize]]; + tx.blocking_write(&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!("LPUART Ring Buffer DMA example starting..."); + + // Enable DMA0 clock and release reset + unsafe { + hal::peripherals::DMA0::enable_clock(); + hal::peripherals::DMA0::release_reset(); + } + + let pac_periphs = unsafe { pac::Peripherals::steal() }; + + // Initialize DMA + unsafe { + dma::init(&pac_periphs); + } + + // Enable DMA interrupts + unsafe { + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH1); + } + + // Create UART configuration + let config = Config { + baudrate_bps: 115_200, + enable_tx: true, + enable_rx: true, + ..Default::default() + }; + + // Create blocking UART for TX (we'll use DMA for RX only) + 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"LPUART Ring Buffer DMA Example\r\n").unwrap(); + tx.blocking_write(b"==============================\r\n\r\n").unwrap(); + + // Get LPUART2 RX data register address for DMA + let lpuart2 = unsafe { &*pac::Lpuart2::ptr() }; + let rx_data_addr = lpuart2.data().as_ptr() as *const u8; + + // Enable RX DMA request in LPUART + lpuart2.baud().modify(|_, w| w.rdmae().enabled()); + + // Create DMA channel for RX + let dma_ch_rx = DmaChannel::new(p.DMA_CH0); + let edma = dma::edma_tcd(); + + // Configure the DMA mux for LPUART2 RX + unsafe { + dma_ch_rx.set_request_source(edma, DMA_REQ_LPUART2_RX); + } + + tx.blocking_write(b"Setting up circular DMA for UART RX...\r\n").unwrap(); + + // Set up the ring buffer with circular DMA + // This configures the DMA for continuous reception + let ring_buf = unsafe { + let buf = &mut *core::ptr::addr_of_mut!(RX_RING_BUFFER); + dma_ch_rx.setup_circular_read(rx_data_addr, buf) + }; + + // Enable DMA requests to start continuous reception + unsafe { + dma_ch_rx.enable_request(edma); + } + + tx.blocking_write(b"Ring buffer ready! Type characters to see them echoed.\r\n").unwrap(); + tx.blocking_write(b"The DMA continuously receives in the background.\r\n\r\n").unwrap(); + + // Main loop: read from ring buffer and echo back + let mut read_buf = [0u8; 16]; + let mut total_received: usize = 0; + + loop { + // Async read - waits until data is available + match ring_buf.read(&mut read_buf).await { + Ok(n) if n > 0 => { + total_received += n; + + // Echo back what we received + tx.blocking_write(b"RX[").unwrap(); + for (i, &byte) in read_buf.iter().enumerate().take(n) { + write_hex(&mut tx, byte); + if i < n - 1 { + tx.blocking_write(b" ").unwrap(); + } + } + tx.blocking_write(b"]: ").unwrap(); + tx.blocking_write(&read_buf[..n]).unwrap(); + tx.blocking_write(b"\r\n").unwrap(); + + defmt::info!("Received {} bytes, total: {}", n, total_received); + } + Ok(_) => { + // No data, shouldn't happen with async read + } + Err(_) => { + // Overrun detected + tx.blocking_write(b"ERROR: Ring buffer overrun!\r\n").unwrap(); + defmt::error!("Ring buffer overrun!"); + ring_buf.clear(); + } + } + } +} + -- cgit