diff options
| author | James Munns <[email protected]> | 2025-12-09 17:40:22 +0000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-12-09 17:40:22 +0000 |
| commit | 23623d634b88da7bc398f092ac4ab9e571c6e6e1 (patch) | |
| tree | d631103d213340780c6bd4e7b8925df491ad4a1d /examples/mcxa/src | |
| parent | 4f322f4a03d336e90d530045255f46cce93e6252 (diff) | |
| parent | 2d7328d5839e196f7b6c275283a50fd4ac019440 (diff) | |
Merge pull request #5015 from jamesmunns/james/dma-suggestions
[MCXA]: Extend DMA interface
Diffstat (limited to 'examples/mcxa/src')
| -rw-r--r-- | examples/mcxa/src/bin/dma_mem_to_mem.rs | 118 | ||||
| -rw-r--r-- | examples/mcxa/src/bin/dma_scatter_gather_builder.rs | 130 | ||||
| -rw-r--r-- | examples/mcxa/src/bin/dma_wrap_transfer.rs | 184 | ||||
| -rw-r--r-- | examples/mcxa/src/bin/lpuart_dma.rs | 68 | ||||
| -rw-r--r-- | examples/mcxa/src/bin/lpuart_ring_buffer.rs | 115 | ||||
| -rw-r--r-- | examples/mcxa/src/bin/raw_dma_channel_link.rs | 278 | ||||
| -rw-r--r-- | examples/mcxa/src/bin/raw_dma_interleave_transfer.rs | 141 | ||||
| -rw-r--r-- | examples/mcxa/src/bin/raw_dma_memset.rs | 129 | ||||
| -rw-r--r-- | examples/mcxa/src/bin/raw_dma_ping_pong_transfer.rs | 244 | ||||
| -rw-r--r-- | examples/mcxa/src/bin/raw_dma_scatter_gather.rs | 165 |
10 files changed, 1572 insertions, 0 deletions
diff --git a/examples/mcxa/src/bin/dma_mem_to_mem.rs b/examples/mcxa/src/bin/dma_mem_to_mem.rs new file mode 100644 index 000000000..b38baccb5 --- /dev/null +++ b/examples/mcxa/src/bin/dma_mem_to_mem.rs | |||
| @@ -0,0 +1,118 @@ | |||
| 1 | //! DMA memory-to-memory transfer example for MCXA276. | ||
| 2 | //! | ||
| 3 | //! This example demonstrates using DMA to copy data between memory buffers | ||
| 4 | //! using the Embassy-style async API with type-safe transfers. | ||
| 5 | //! | ||
| 6 | //! # Embassy-style features demonstrated: | ||
| 7 | //! - `TransferOptions` for configuration | ||
| 8 | //! - Type-safe `mem_to_mem<u32>()` method with async `.await` | ||
| 9 | //! - `Transfer` Future that can be `.await`ed | ||
| 10 | //! - `Word` trait for automatic transfer width detection | ||
| 11 | //! - `memset()` method for filling memory with a pattern | ||
| 12 | |||
| 13 | #![no_std] | ||
| 14 | #![no_main] | ||
| 15 | |||
| 16 | use embassy_executor::Spawner; | ||
| 17 | use embassy_mcxa::clocks::config::Div8; | ||
| 18 | use embassy_mcxa::dma::{DmaChannel, TransferOptions}; | ||
| 19 | use static_cell::ConstStaticCell; | ||
| 20 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 21 | |||
| 22 | const BUFFER_LENGTH: usize = 4; | ||
| 23 | |||
| 24 | // Buffers in RAM (static mut is automatically placed in .bss/.data) | ||
| 25 | static SRC_BUFFER: ConstStaticCell<[u32; BUFFER_LENGTH]> = ConstStaticCell::new([1, 2, 3, 4]); | ||
| 26 | static DEST_BUFFER: ConstStaticCell<[u32; BUFFER_LENGTH]> = ConstStaticCell::new([0; BUFFER_LENGTH]); | ||
| 27 | static MEMSET_BUFFER: ConstStaticCell<[u32; BUFFER_LENGTH]> = ConstStaticCell::new([0; BUFFER_LENGTH]); | ||
| 28 | |||
| 29 | #[embassy_executor::main] | ||
| 30 | async fn main(_spawner: Spawner) { | ||
| 31 | // Small delay to allow probe-rs to attach after reset | ||
| 32 | for _ in 0..100_000 { | ||
| 33 | cortex_m::asm::nop(); | ||
| 34 | } | ||
| 35 | |||
| 36 | let mut cfg = hal::config::Config::default(); | ||
| 37 | cfg.clock_cfg.sirc.fro_12m_enabled = true; | ||
| 38 | cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); | ||
| 39 | let p = hal::init(cfg); | ||
| 40 | |||
| 41 | defmt::info!("DMA memory-to-memory example starting..."); | ||
| 42 | |||
| 43 | defmt::info!("EDMA memory to memory example begin."); | ||
| 44 | |||
| 45 | let src = SRC_BUFFER.take(); | ||
| 46 | let dst = DEST_BUFFER.take(); | ||
| 47 | let mst = MEMSET_BUFFER.take(); | ||
| 48 | |||
| 49 | defmt::info!("Source Buffer: {=[?]}", src.as_slice()); | ||
| 50 | defmt::info!("Destination Buffer (before): {=[?]}", dst.as_slice()); | ||
| 51 | defmt::info!("Configuring DMA with Embassy-style API..."); | ||
| 52 | |||
| 53 | // Create DMA channel | ||
| 54 | let dma_ch0 = DmaChannel::new(p.DMA_CH0); | ||
| 55 | |||
| 56 | // Configure transfer options (Embassy-style) | ||
| 57 | // TransferOptions defaults to: complete_transfer_interrupt = true | ||
| 58 | let options = TransferOptions::default(); | ||
| 59 | |||
| 60 | // ========================================================================= | ||
| 61 | // Part 1: Embassy-style async API demonstration (mem_to_mem) | ||
| 62 | // ========================================================================= | ||
| 63 | // | ||
| 64 | // Use the new type-safe `mem_to_mem<u32>()` method: | ||
| 65 | // - Automatically determines transfer width from buffer element type (u32) | ||
| 66 | // - Returns a `Transfer` future that can be `.await`ed | ||
| 67 | // - Uses TransferOptions for consistent configuration | ||
| 68 | // | ||
| 69 | // Using async `.await` - the executor can run other tasks while waiting! | ||
| 70 | |||
| 71 | // Perform type-safe memory-to-memory transfer using Embassy-style async API | ||
| 72 | // Using async `.await` - the executor can run other tasks while waiting! | ||
| 73 | let transfer = dma_ch0.mem_to_mem(src, dst, options).unwrap(); | ||
| 74 | transfer.await.unwrap(); | ||
| 75 | |||
| 76 | defmt::info!("DMA mem-to-mem transfer complete!"); | ||
| 77 | defmt::info!("Destination Buffer (after): {=[?]}", dst.as_slice()); | ||
| 78 | |||
| 79 | // Verify data | ||
| 80 | if src != dst { | ||
| 81 | defmt::error!("FAIL: mem_to_mem mismatch!"); | ||
| 82 | } else { | ||
| 83 | defmt::info!("PASS: mem_to_mem verified."); | ||
| 84 | } | ||
| 85 | |||
| 86 | // ========================================================================= | ||
| 87 | // Part 2: memset() demonstration | ||
| 88 | // ========================================================================= | ||
| 89 | // | ||
| 90 | // The `memset()` method fills a buffer with a pattern value: | ||
| 91 | // - Fixed source address (pattern is read repeatedly) | ||
| 92 | // - Incrementing destination address | ||
| 93 | // - Uses the same Transfer future pattern | ||
| 94 | |||
| 95 | defmt::info!("--- Demonstrating memset() feature ---"); | ||
| 96 | |||
| 97 | defmt::info!("Memset Buffer (before): {=[?]}", mst.as_slice()); | ||
| 98 | |||
| 99 | // Fill buffer with a pattern value using DMA memset | ||
| 100 | let pattern: u32 = 0xDEADBEEF; | ||
| 101 | defmt::info!("Filling with pattern 0xDEADBEEF..."); | ||
| 102 | |||
| 103 | // Using blocking_wait() for demonstration - also shows non-async usage | ||
| 104 | let transfer = dma_ch0.memset(&pattern, mst, options); | ||
| 105 | transfer.blocking_wait(); | ||
| 106 | |||
| 107 | defmt::info!("DMA memset complete!"); | ||
| 108 | defmt::info!("Memset Buffer (after): {=[?]}", mst.as_slice()); | ||
| 109 | |||
| 110 | // Verify memset result | ||
| 111 | if !mst.iter().all(|&v| v == pattern) { | ||
| 112 | defmt::error!("FAIL: memset mismatch!"); | ||
| 113 | } else { | ||
| 114 | defmt::info!("PASS: memset verified."); | ||
| 115 | } | ||
| 116 | |||
| 117 | defmt::info!("=== All DMA tests complete ==="); | ||
| 118 | } | ||
diff --git a/examples/mcxa/src/bin/dma_scatter_gather_builder.rs b/examples/mcxa/src/bin/dma_scatter_gather_builder.rs new file mode 100644 index 000000000..30ce20c96 --- /dev/null +++ b/examples/mcxa/src/bin/dma_scatter_gather_builder.rs | |||
| @@ -0,0 +1,130 @@ | |||
| 1 | //! DMA Scatter-Gather Builder example for MCXA276. | ||
| 2 | //! | ||
| 3 | //! This example demonstrates using the new `ScatterGatherBuilder` API for | ||
| 4 | //! chaining multiple DMA transfers with a type-safe builder pattern. | ||
| 5 | //! | ||
| 6 | //! # Features demonstrated: | ||
| 7 | //! - `ScatterGatherBuilder::new()` for creating a builder | ||
| 8 | //! - `add_transfer()` for adding memory-to-memory segments | ||
| 9 | //! - `build()` to start the chained transfer | ||
| 10 | //! - Automatic TCD linking and ESG bit management | ||
| 11 | //! | ||
| 12 | //! # Comparison with manual scatter-gather: | ||
| 13 | //! The manual approach (see `dma_scatter_gather.rs`) requires: | ||
| 14 | //! - Manual TCD pool allocation and alignment | ||
| 15 | //! - Manual CSR/ESG/INTMAJOR bit manipulation | ||
| 16 | //! - Manual dlast_sga address calculations | ||
| 17 | //! | ||
| 18 | //! The builder approach handles all of this automatically! | ||
| 19 | |||
| 20 | #![no_std] | ||
| 21 | #![no_main] | ||
| 22 | |||
| 23 | use embassy_executor::Spawner; | ||
| 24 | use embassy_mcxa::clocks::config::Div8; | ||
| 25 | use embassy_mcxa::dma::{DmaChannel, ScatterGatherBuilder}; | ||
| 26 | use static_cell::ConstStaticCell; | ||
| 27 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 28 | |||
| 29 | // Source buffers (multiple segments) | ||
| 30 | static SRC1: ConstStaticCell<[u32; 4]> = ConstStaticCell::new([0x11111111, 0x22222222, 0x33333333, 0x44444444]); | ||
| 31 | static SRC2: ConstStaticCell<[u32; 4]> = ConstStaticCell::new([0xAAAAAAAA, 0xBBBBBBBB, 0xCCCCCCCC, 0xDDDDDDDD]); | ||
| 32 | static SRC3: ConstStaticCell<[u32; 4]> = ConstStaticCell::new([0x12345678, 0x9ABCDEF0, 0xFEDCBA98, 0x76543210]); | ||
| 33 | |||
| 34 | // Destination buffers (one per segment) | ||
| 35 | static DST1: ConstStaticCell<[u32; 4]> = ConstStaticCell::new([0; 4]); | ||
| 36 | static DST2: ConstStaticCell<[u32; 4]> = ConstStaticCell::new([0; 4]); | ||
| 37 | static DST3: ConstStaticCell<[u32; 4]> = ConstStaticCell::new([0; 4]); | ||
| 38 | |||
| 39 | #[embassy_executor::main] | ||
| 40 | async fn main(_spawner: Spawner) { | ||
| 41 | // Small delay to allow probe-rs to attach after reset | ||
| 42 | for _ in 0..100_000 { | ||
| 43 | cortex_m::asm::nop(); | ||
| 44 | } | ||
| 45 | |||
| 46 | let mut cfg = hal::config::Config::default(); | ||
| 47 | cfg.clock_cfg.sirc.fro_12m_enabled = true; | ||
| 48 | cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); | ||
| 49 | let p = hal::init(cfg); | ||
| 50 | |||
| 51 | defmt::info!("DMA Scatter-Gather Builder example starting..."); | ||
| 52 | |||
| 53 | defmt::info!("DMA Scatter-Gather Builder Example"); | ||
| 54 | defmt::info!("==================================="); | ||
| 55 | let src1 = SRC1.take(); | ||
| 56 | let src2 = SRC2.take(); | ||
| 57 | let src3 = SRC3.take(); | ||
| 58 | let dst1 = DST1.take(); | ||
| 59 | let dst2 = DST2.take(); | ||
| 60 | let dst3 = DST3.take(); | ||
| 61 | |||
| 62 | // Show source buffers | ||
| 63 | defmt::info!("Source buffers:"); | ||
| 64 | defmt::info!(" SRC1: {=[?]}", src1.as_slice()); | ||
| 65 | defmt::info!(" SRC2: {=[?]}", src2.as_slice()); | ||
| 66 | defmt::info!(" SRC3: {=[?]}", src3.as_slice()); | ||
| 67 | |||
| 68 | defmt::info!("Destination buffers (before):"); | ||
| 69 | defmt::info!(" DST1: {=[?]}", dst1.as_slice()); | ||
| 70 | defmt::info!(" DST2: {=[?]}", dst2.as_slice()); | ||
| 71 | defmt::info!(" DST3: {=[?]}", dst3.as_slice()); | ||
| 72 | |||
| 73 | // Create DMA channel | ||
| 74 | let dma_ch0 = DmaChannel::new(p.DMA_CH0); | ||
| 75 | |||
| 76 | defmt::info!("Building scatter-gather chain with builder API..."); | ||
| 77 | |||
| 78 | // ========================================================================= | ||
| 79 | // ScatterGatherBuilder API demonstration | ||
| 80 | // ========================================================================= | ||
| 81 | // | ||
| 82 | // The builder pattern makes scatter-gather transfers much easier: | ||
| 83 | // 1. Create a builder | ||
| 84 | // 2. Add transfer segments with add_transfer() | ||
| 85 | // 3. Call build() to start the entire chain | ||
| 86 | // No manual TCD manipulation required! | ||
| 87 | |||
| 88 | let mut builder = ScatterGatherBuilder::<u32>::new(); | ||
| 89 | |||
| 90 | // Add three transfer segments - the builder handles TCD linking automatically | ||
| 91 | builder.add_transfer(src1, dst1); | ||
| 92 | builder.add_transfer(src2, dst2); | ||
| 93 | builder.add_transfer(src3, dst3); | ||
| 94 | |||
| 95 | defmt::info!("Added 3 transfer segments to chain."); | ||
| 96 | defmt::info!("Starting scatter-gather transfer with .await..."); | ||
| 97 | |||
| 98 | // Build and execute the scatter-gather chain | ||
| 99 | // The build() method: | ||
| 100 | // - Links all TCDs together with ESG bit | ||
| 101 | // - Sets INTMAJOR on all TCDs | ||
| 102 | // - Loads the first TCD into hardware | ||
| 103 | // - Returns a Transfer future | ||
| 104 | let transfer = builder.build(&dma_ch0).expect("Failed to build scatter-gather"); | ||
| 105 | transfer.blocking_wait(); | ||
| 106 | |||
| 107 | defmt::info!("Scatter-gather transfer complete!"); | ||
| 108 | |||
| 109 | // Show results | ||
| 110 | defmt::info!("Destination buffers (after):"); | ||
| 111 | defmt::info!(" DST1: {=[?]}", dst1.as_slice()); | ||
| 112 | defmt::info!(" DST2: {=[?]}", dst2.as_slice()); | ||
| 113 | defmt::info!(" DST3: {=[?]}", dst3.as_slice()); | ||
| 114 | |||
| 115 | let comps = [(src1, dst1), (src2, dst2), (src3, dst3)]; | ||
| 116 | |||
| 117 | // Verify all three segments | ||
| 118 | let mut all_ok = true; | ||
| 119 | for (src, dst) in comps { | ||
| 120 | all_ok &= src == dst; | ||
| 121 | } | ||
| 122 | |||
| 123 | if all_ok { | ||
| 124 | defmt::info!("PASS: All segments verified!"); | ||
| 125 | } else { | ||
| 126 | defmt::error!("FAIL: Mismatch detected!"); | ||
| 127 | } | ||
| 128 | |||
| 129 | defmt::info!("=== Scatter-Gather Builder example complete ==="); | ||
| 130 | } | ||
diff --git a/examples/mcxa/src/bin/dma_wrap_transfer.rs b/examples/mcxa/src/bin/dma_wrap_transfer.rs new file mode 100644 index 000000000..acfd29f08 --- /dev/null +++ b/examples/mcxa/src/bin/dma_wrap_transfer.rs | |||
| @@ -0,0 +1,184 @@ | |||
| 1 | //! DMA wrap transfer example for MCXA276. | ||
| 2 | //! | ||
| 3 | //! This example demonstrates using DMA with modulo addressing to wrap around | ||
| 4 | //! a source buffer, effectively repeating the source data in the destination. | ||
| 5 | //! | ||
| 6 | //! # Embassy-style features demonstrated: | ||
| 7 | //! - `DmaChannel::is_done()` and `clear_done()` helper methods | ||
| 8 | //! - No need to pass register block around | ||
| 9 | |||
| 10 | #![no_std] | ||
| 11 | #![no_main] | ||
| 12 | |||
| 13 | use core::fmt::Write as _; | ||
| 14 | |||
| 15 | use embassy_executor::Spawner; | ||
| 16 | use embassy_mcxa::clocks::config::Div8; | ||
| 17 | use embassy_mcxa::dma::DmaChannel; | ||
| 18 | use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; | ||
| 19 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 20 | |||
| 21 | // Source buffer: 4 words (16 bytes), aligned to 16 bytes for modulo | ||
| 22 | #[repr(align(16))] | ||
| 23 | struct AlignedSrc([u32; 4]); | ||
| 24 | |||
| 25 | static mut SRC: AlignedSrc = AlignedSrc([0; 4]); | ||
| 26 | static mut DST: [u32; 8] = [0; 8]; | ||
| 27 | |||
| 28 | /// Helper to print a buffer to UART | ||
| 29 | fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { | ||
| 30 | write!(tx, "{:?}", unsafe { core::slice::from_raw_parts(buf_ptr, len) }).ok(); | ||
| 31 | } | ||
| 32 | |||
| 33 | #[embassy_executor::main] | ||
| 34 | async fn main(_spawner: Spawner) { | ||
| 35 | // Small delay to allow probe-rs to attach after reset | ||
| 36 | for _ in 0..100_000 { | ||
| 37 | cortex_m::asm::nop(); | ||
| 38 | } | ||
| 39 | |||
| 40 | let mut cfg = hal::config::Config::default(); | ||
| 41 | cfg.clock_cfg.sirc.fro_12m_enabled = true; | ||
| 42 | cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); | ||
| 43 | let p = hal::init(cfg); | ||
| 44 | |||
| 45 | defmt::info!("DMA wrap transfer example starting..."); | ||
| 46 | |||
| 47 | let config = Config { | ||
| 48 | baudrate_bps: 115_200, | ||
| 49 | ..Default::default() | ||
| 50 | }; | ||
| 51 | |||
| 52 | let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); | ||
| 53 | let (mut tx, _rx) = lpuart.split(); | ||
| 54 | |||
| 55 | tx.blocking_write(b"EDMA wrap transfer example begin.\r\n\r\n").unwrap(); | ||
| 56 | |||
| 57 | // Initialize buffers | ||
| 58 | unsafe { | ||
| 59 | SRC.0 = [1, 2, 3, 4]; | ||
| 60 | DST = [0; 8]; | ||
| 61 | } | ||
| 62 | |||
| 63 | tx.blocking_write(b"Source Buffer: ").unwrap(); | ||
| 64 | print_buffer(&mut tx, unsafe { core::ptr::addr_of!(SRC.0) } as *const u32, 4); | ||
| 65 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 66 | |||
| 67 | tx.blocking_write(b"Destination Buffer (before): ").unwrap(); | ||
| 68 | print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); | ||
| 69 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 70 | |||
| 71 | tx.blocking_write(b"Configuring DMA with Embassy-style API...\r\n") | ||
| 72 | .unwrap(); | ||
| 73 | |||
| 74 | // Create DMA channel using Embassy-style API | ||
| 75 | let dma_ch0 = DmaChannel::new(p.DMA_CH0); | ||
| 76 | |||
| 77 | // Configure wrap transfer using direct TCD access: | ||
| 78 | // SRC is 16 bytes (4 * u32). We want to transfer 32 bytes (8 * u32). | ||
| 79 | // SRC modulo is 16 bytes (2^4 = 16) - wraps source address. | ||
| 80 | // DST modulo is 0 (disabled). | ||
| 81 | // This causes the source address to wrap around after 16 bytes, | ||
| 82 | // effectively repeating the source data. | ||
| 83 | unsafe { | ||
| 84 | let t = dma_ch0.tcd(); | ||
| 85 | |||
| 86 | // Reset channel state | ||
| 87 | t.ch_csr().write(|w| { | ||
| 88 | w.erq() | ||
| 89 | .disable() | ||
| 90 | .earq() | ||
| 91 | .disable() | ||
| 92 | .eei() | ||
| 93 | .no_error() | ||
| 94 | .ebw() | ||
| 95 | .disable() | ||
| 96 | .done() | ||
| 97 | .clear_bit_by_one() | ||
| 98 | }); | ||
| 99 | t.ch_es().write(|w| w.bits(0)); | ||
| 100 | t.ch_int().write(|w| w.int().clear_bit_by_one()); | ||
| 101 | |||
| 102 | // Source/destination addresses | ||
| 103 | t.tcd_saddr() | ||
| 104 | .write(|w| w.saddr().bits(core::ptr::addr_of!(SRC.0) as u32)); | ||
| 105 | t.tcd_daddr() | ||
| 106 | .write(|w| w.daddr().bits(core::ptr::addr_of_mut!(DST) as u32)); | ||
| 107 | |||
| 108 | // Offsets: both increment by 4 bytes | ||
| 109 | t.tcd_soff().write(|w| w.soff().bits(4)); | ||
| 110 | t.tcd_doff().write(|w| w.doff().bits(4)); | ||
| 111 | |||
| 112 | // Attributes: 32-bit transfers (size = 2) | ||
| 113 | // SMOD = 4 (2^4 = 16 byte modulo for source), DMOD = 0 (disabled) | ||
| 114 | t.tcd_attr().write(|w| { | ||
| 115 | w.ssize() | ||
| 116 | .bits(2) | ||
| 117 | .dsize() | ||
| 118 | .bits(2) | ||
| 119 | .smod() | ||
| 120 | .bits(4) // Source modulo: 2^4 = 16 bytes | ||
| 121 | .dmod() | ||
| 122 | .bits(0) // Dest modulo: disabled | ||
| 123 | }); | ||
| 124 | |||
| 125 | // Transfer 32 bytes total in one minor loop | ||
| 126 | let nbytes = 32u32; | ||
| 127 | t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(nbytes)); | ||
| 128 | |||
| 129 | // Source wraps via modulo, no adjustment needed | ||
| 130 | t.tcd_slast_sda().write(|w| w.slast_sda().bits(0)); | ||
| 131 | // Reset dest address after major loop | ||
| 132 | t.tcd_dlast_sga().write(|w| w.dlast_sga().bits(-(nbytes as i32) as u32)); | ||
| 133 | |||
| 134 | // Major loop count = 1 | ||
| 135 | t.tcd_biter_elinkno().write(|w| w.biter().bits(1)); | ||
| 136 | t.tcd_citer_elinkno().write(|w| w.citer().bits(1)); | ||
| 137 | |||
| 138 | // Enable interrupt on major loop completion | ||
| 139 | t.tcd_csr().write(|w| w.intmajor().set_bit()); | ||
| 140 | |||
| 141 | cortex_m::asm::dsb(); | ||
| 142 | |||
| 143 | tx.blocking_write(b"Triggering transfer...\r\n").unwrap(); | ||
| 144 | dma_ch0.trigger_start(); | ||
| 145 | } | ||
| 146 | |||
| 147 | // Wait for completion using channel helper method | ||
| 148 | while !dma_ch0.is_done() { | ||
| 149 | cortex_m::asm::nop(); | ||
| 150 | } | ||
| 151 | unsafe { | ||
| 152 | dma_ch0.clear_done(); | ||
| 153 | } | ||
| 154 | |||
| 155 | tx.blocking_write(b"\r\nEDMA wrap transfer example finish.\r\n\r\n") | ||
| 156 | .unwrap(); | ||
| 157 | tx.blocking_write(b"Destination Buffer (after): ").unwrap(); | ||
| 158 | print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); | ||
| 159 | tx.blocking_write(b"\r\n\r\n").unwrap(); | ||
| 160 | |||
| 161 | // Verify: DST should be [1, 2, 3, 4, 1, 2, 3, 4] | ||
| 162 | let expected = [1u32, 2, 3, 4, 1, 2, 3, 4]; | ||
| 163 | let mut mismatch = false; | ||
| 164 | unsafe { | ||
| 165 | for i in 0..8 { | ||
| 166 | if DST[i] != expected[i] { | ||
| 167 | mismatch = true; | ||
| 168 | break; | ||
| 169 | } | ||
| 170 | } | ||
| 171 | } | ||
| 172 | |||
| 173 | if mismatch { | ||
| 174 | tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); | ||
| 175 | defmt::error!("FAIL: Mismatch detected!"); | ||
| 176 | } else { | ||
| 177 | tx.blocking_write(b"PASS: Data verified.\r\n").unwrap(); | ||
| 178 | defmt::info!("PASS: Data verified."); | ||
| 179 | } | ||
| 180 | |||
| 181 | loop { | ||
| 182 | cortex_m::asm::wfe(); | ||
| 183 | } | ||
| 184 | } | ||
diff --git a/examples/mcxa/src/bin/lpuart_dma.rs b/examples/mcxa/src/bin/lpuart_dma.rs new file mode 100644 index 000000000..cc86f6a40 --- /dev/null +++ b/examples/mcxa/src/bin/lpuart_dma.rs | |||
| @@ -0,0 +1,68 @@ | |||
| 1 | //! LPUART DMA example for MCXA276. | ||
| 2 | //! | ||
| 3 | //! This example demonstrates using DMA for UART TX and RX operations. | ||
| 4 | //! It sends a message using DMA, then waits for 16 characters to be received | ||
| 5 | //! via DMA and echoes them back. | ||
| 6 | //! | ||
| 7 | //! The DMA request sources are automatically derived from the LPUART instance type. | ||
| 8 | //! DMA clock/reset/init is handled automatically by the HAL. | ||
| 9 | |||
| 10 | #![no_std] | ||
| 11 | #![no_main] | ||
| 12 | |||
| 13 | use embassy_executor::Spawner; | ||
| 14 | use embassy_mcxa::clocks::config::Div8; | ||
| 15 | use embassy_mcxa::lpuart::{Config, LpuartDma}; | ||
| 16 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 17 | |||
| 18 | #[embassy_executor::main] | ||
| 19 | async fn main(_spawner: Spawner) { | ||
| 20 | let mut cfg = hal::config::Config::default(); | ||
| 21 | cfg.clock_cfg.sirc.fro_12m_enabled = true; | ||
| 22 | cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); | ||
| 23 | let p = hal::init(cfg); | ||
| 24 | |||
| 25 | defmt::info!("LPUART DMA example starting..."); | ||
| 26 | |||
| 27 | // Create UART configuration | ||
| 28 | let config = Config { | ||
| 29 | baudrate_bps: 115_200, | ||
| 30 | ..Default::default() | ||
| 31 | }; | ||
| 32 | |||
| 33 | // Create UART instance with DMA channels | ||
| 34 | let mut lpuart = LpuartDma::new( | ||
| 35 | p.LPUART2, // Instance | ||
| 36 | p.P2_2, // TX pin | ||
| 37 | p.P2_3, // RX pin | ||
| 38 | p.DMA_CH0, // TX DMA channel | ||
| 39 | p.DMA_CH1, // RX DMA channel | ||
| 40 | config, | ||
| 41 | ) | ||
| 42 | .unwrap(); | ||
| 43 | |||
| 44 | // Send a message using DMA (DMA request source is automatically derived from LPUART2) | ||
| 45 | let tx_msg = b"Hello from LPUART2 DMA TX!\r\n"; | ||
| 46 | lpuart.write_dma(tx_msg).await.unwrap(); | ||
| 47 | |||
| 48 | defmt::info!("TX DMA complete"); | ||
| 49 | |||
| 50 | // Send prompt | ||
| 51 | let prompt = b"Type 16 characters to echo via DMA:\r\n"; | ||
| 52 | lpuart.write_dma(prompt).await.unwrap(); | ||
| 53 | |||
| 54 | // Receive 16 characters using DMA | ||
| 55 | let mut rx_buf = [0u8; 16]; | ||
| 56 | lpuart.read_dma(&mut rx_buf).await.unwrap(); | ||
| 57 | |||
| 58 | defmt::info!("RX DMA complete"); | ||
| 59 | |||
| 60 | // Echo back the received data | ||
| 61 | let echo_prefix = b"\r\nReceived: "; | ||
| 62 | lpuart.write_dma(echo_prefix).await.unwrap(); | ||
| 63 | lpuart.write_dma(&rx_buf).await.unwrap(); | ||
| 64 | let done_msg = b"\r\nDone!\r\n"; | ||
| 65 | lpuart.write_dma(done_msg).await.unwrap(); | ||
| 66 | |||
| 67 | defmt::info!("Example complete"); | ||
| 68 | } | ||
diff --git a/examples/mcxa/src/bin/lpuart_ring_buffer.rs b/examples/mcxa/src/bin/lpuart_ring_buffer.rs new file mode 100644 index 000000000..be7fd4534 --- /dev/null +++ b/examples/mcxa/src/bin/lpuart_ring_buffer.rs | |||
| @@ -0,0 +1,115 @@ | |||
| 1 | //! LPUART Ring Buffer DMA example for MCXA276. | ||
| 2 | //! | ||
| 3 | //! This example demonstrates using the high-level `LpuartRxDma::setup_ring_buffer()` | ||
| 4 | //! API for continuous circular DMA reception from a UART peripheral. | ||
| 5 | //! | ||
| 6 | //! # Features demonstrated: | ||
| 7 | //! - `LpuartRxDma::setup_ring_buffer()` for continuous peripheral-to-memory DMA | ||
| 8 | //! - `RingBuffer` for async reading of received data | ||
| 9 | //! - Handling of potential overrun conditions | ||
| 10 | //! - Half-transfer and complete-transfer interrupts for timely wakeups | ||
| 11 | //! | ||
| 12 | //! # How it works: | ||
| 13 | //! 1. Create an `LpuartRxDma` driver with a DMA channel | ||
| 14 | //! 2. Call `setup_ring_buffer()` which handles all low-level DMA configuration | ||
| 15 | //! 3. Application asynchronously reads data as it arrives via `ring_buf.read()` | ||
| 16 | //! 4. Both half-transfer and complete-transfer interrupts wake the reader | ||
| 17 | |||
| 18 | #![no_std] | ||
| 19 | #![no_main] | ||
| 20 | |||
| 21 | use embassy_executor::Spawner; | ||
| 22 | use embassy_mcxa::clocks::config::Div8; | ||
| 23 | use embassy_mcxa::lpuart::{Config, LpuartDma, LpuartTxDma}; | ||
| 24 | use static_cell::ConstStaticCell; | ||
| 25 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 26 | |||
| 27 | // Ring buffer for RX - power of 2 is ideal for modulo efficiency | ||
| 28 | static RX_RING_BUFFER: ConstStaticCell<[u8; 64]> = ConstStaticCell::new([0; 64]); | ||
| 29 | |||
| 30 | /// Helper to write a byte as hex to UART | ||
| 31 | fn write_hex<T: embassy_mcxa::lpuart::Instance, C: embassy_mcxa::dma::Channel>( | ||
| 32 | tx: &mut LpuartTxDma<'_, T, C>, | ||
| 33 | byte: u8, | ||
| 34 | ) { | ||
| 35 | const HEX: &[u8; 16] = b"0123456789ABCDEF"; | ||
| 36 | let buf = [HEX[(byte >> 4) as usize], HEX[(byte & 0x0F) as usize]]; | ||
| 37 | tx.blocking_write(&buf).ok(); | ||
| 38 | } | ||
| 39 | |||
| 40 | #[embassy_executor::main] | ||
| 41 | async fn main(_spawner: Spawner) { | ||
| 42 | // Small delay to allow probe-rs to attach after reset | ||
| 43 | for _ in 0..100_000 { | ||
| 44 | cortex_m::asm::nop(); | ||
| 45 | } | ||
| 46 | |||
| 47 | let mut cfg = hal::config::Config::default(); | ||
| 48 | cfg.clock_cfg.sirc.fro_12m_enabled = true; | ||
| 49 | cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); | ||
| 50 | let p = hal::init(cfg); | ||
| 51 | |||
| 52 | defmt::info!("LPUART Ring Buffer DMA example starting..."); | ||
| 53 | |||
| 54 | // Create UART configuration | ||
| 55 | let config = Config { | ||
| 56 | baudrate_bps: 115_200, | ||
| 57 | ..Default::default() | ||
| 58 | }; | ||
| 59 | |||
| 60 | // Create LPUART with DMA support for both TX and RX, then split | ||
| 61 | // This is the proper Embassy pattern - create once, split into TX and RX | ||
| 62 | let lpuart = LpuartDma::new(p.LPUART2, p.P2_2, p.P2_3, p.DMA_CH1, p.DMA_CH0, config).unwrap(); | ||
| 63 | let (mut tx, rx) = lpuart.split(); | ||
| 64 | |||
| 65 | tx.blocking_write(b"LPUART Ring Buffer DMA Example\r\n").unwrap(); | ||
| 66 | tx.blocking_write(b"==============================\r\n\r\n").unwrap(); | ||
| 67 | |||
| 68 | tx.blocking_write(b"Setting up circular DMA for UART RX...\r\n") | ||
| 69 | .unwrap(); | ||
| 70 | |||
| 71 | let buf = RX_RING_BUFFER.take(); | ||
| 72 | // Set up the ring buffer with circular DMA | ||
| 73 | let mut ring_buf = rx.into_ring_dma_rx(buf); | ||
| 74 | |||
| 75 | tx.blocking_write(b"Ring buffer ready! Type characters to see them echoed.\r\n") | ||
| 76 | .unwrap(); | ||
| 77 | tx.blocking_write(b"The DMA continuously receives in the background.\r\n\r\n") | ||
| 78 | .unwrap(); | ||
| 79 | |||
| 80 | // Main loop: read from ring buffer and echo back | ||
| 81 | let mut read_buf = [0u8; 16]; | ||
| 82 | let mut total_received: usize = 0; | ||
| 83 | |||
| 84 | loop { | ||
| 85 | // Async read - waits until data is available | ||
| 86 | match ring_buf.read(&mut read_buf).await { | ||
| 87 | Ok(n) if n > 0 => { | ||
| 88 | total_received += n; | ||
| 89 | |||
| 90 | // Echo back what we received | ||
| 91 | tx.blocking_write(b"RX[").unwrap(); | ||
| 92 | for (i, &byte) in read_buf.iter().enumerate().take(n) { | ||
| 93 | write_hex(&mut tx, byte); | ||
| 94 | if i < n - 1 { | ||
| 95 | tx.blocking_write(b" ").unwrap(); | ||
| 96 | } | ||
| 97 | } | ||
| 98 | tx.blocking_write(b"]: ").unwrap(); | ||
| 99 | tx.blocking_write(&read_buf[..n]).unwrap(); | ||
| 100 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 101 | |||
| 102 | defmt::info!("Received {} bytes, total: {}", n, total_received); | ||
| 103 | } | ||
| 104 | Ok(_) => { | ||
| 105 | // No data, shouldn't happen with async read | ||
| 106 | } | ||
| 107 | Err(_) => { | ||
| 108 | // Overrun detected | ||
| 109 | tx.blocking_write(b"ERROR: Ring buffer overrun!\r\n").unwrap(); | ||
| 110 | defmt::error!("Ring buffer overrun!"); | ||
| 111 | ring_buf.clear(); | ||
| 112 | } | ||
| 113 | } | ||
| 114 | } | ||
| 115 | } | ||
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..74785e4f3 --- /dev/null +++ b/examples/mcxa/src/bin/raw_dma_channel_link.rs | |||
| @@ -0,0 +1,278 @@ | |||
| 1 | //! DMA channel linking example for MCXA276. | ||
| 2 | //! | ||
| 3 | //! NOTE: this is a "raw dma" example! It exists as a proof of concept, as we don't have | ||
| 4 | //! a high-level and safe API for. It should not be taken as typical, recommended, or | ||
| 5 | //! stable usage! | ||
| 6 | //! | ||
| 7 | //! This example demonstrates DMA channel linking (minor and major loop linking): | ||
| 8 | //! - Channel 0: Transfers SRC_BUFFER to DEST_BUFFER0, with: | ||
| 9 | //! - Minor Link to Channel 1 (triggers CH1 after each minor loop) | ||
| 10 | //! - Major Link to Channel 2 (triggers CH2 after major loop completes) | ||
| 11 | //! - Channel 1: Transfers SRC_BUFFER to DEST_BUFFER1 (triggered by CH0 minor link) | ||
| 12 | //! - Channel 2: Transfers SRC_BUFFER to DEST_BUFFER2 (triggered by CH0 major link) | ||
| 13 | //! | ||
| 14 | //! # Embassy-style features demonstrated: | ||
| 15 | //! - `DmaChannel::new()` for channel creation | ||
| 16 | //! - `DmaChannel::is_done()` and `clear_done()` helper methods | ||
| 17 | //! - Channel linking with `set_minor_link()` and `set_major_link()` | ||
| 18 | |||
| 19 | #![no_std] | ||
| 20 | #![no_main] | ||
| 21 | |||
| 22 | use embassy_executor::Spawner; | ||
| 23 | use embassy_mcxa::clocks::config::Div8; | ||
| 24 | use embassy_mcxa::dma::DmaChannel; | ||
| 25 | use embassy_mcxa::pac; | ||
| 26 | use static_cell::ConstStaticCell; | ||
| 27 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 28 | |||
| 29 | // Buffers | ||
| 30 | static SRC_BUFFER: ConstStaticCell<[u32; 4]> = ConstStaticCell::new([1, 2, 3, 4]); | ||
| 31 | static DEST_BUFFER0: ConstStaticCell<[u32; 4]> = ConstStaticCell::new([0; 4]); | ||
| 32 | static DEST_BUFFER1: ConstStaticCell<[u32; 4]> = ConstStaticCell::new([0; 4]); | ||
| 33 | static DEST_BUFFER2: ConstStaticCell<[u32; 4]> = ConstStaticCell::new([0; 4]); | ||
| 34 | |||
| 35 | #[embassy_executor::main] | ||
| 36 | async fn main(_spawner: Spawner) { | ||
| 37 | // Small delay to allow probe-rs to attach after reset | ||
| 38 | for _ in 0..100_000 { | ||
| 39 | cortex_m::asm::nop(); | ||
| 40 | } | ||
| 41 | |||
| 42 | let mut cfg = hal::config::Config::default(); | ||
| 43 | cfg.clock_cfg.sirc.fro_12m_enabled = true; | ||
| 44 | cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); | ||
| 45 | let p = hal::init(cfg); | ||
| 46 | |||
| 47 | defmt::info!("DMA channel link example starting..."); | ||
| 48 | |||
| 49 | // DMA is initialized during hal::init() - no need to call ensure_init() | ||
| 50 | |||
| 51 | let pac_periphs = unsafe { pac::Peripherals::steal() }; | ||
| 52 | let dma0 = &pac_periphs.dma0; | ||
| 53 | let edma = unsafe { &*pac::Edma0Tcd0::ptr() }; | ||
| 54 | |||
| 55 | // Clear any residual state | ||
| 56 | for i in 0..3 { | ||
| 57 | let t = edma.tcd(i); | ||
| 58 | t.ch_csr().write(|w| w.erq().disable().done().clear_bit_by_one()); | ||
| 59 | t.ch_int().write(|w| w.int().clear_bit_by_one()); | ||
| 60 | t.ch_es().write(|w| w.err().clear_bit_by_one()); | ||
| 61 | t.ch_mux().write(|w| unsafe { w.bits(0) }); | ||
| 62 | } | ||
| 63 | |||
| 64 | // Clear Global Halt/Error state | ||
| 65 | dma0.mp_csr().modify(|_, w| { | ||
| 66 | w.halt() | ||
| 67 | .normal_operation() | ||
| 68 | .hae() | ||
| 69 | .normal_operation() | ||
| 70 | .ecx() | ||
| 71 | .normal_operation() | ||
| 72 | .cx() | ||
| 73 | .normal_operation() | ||
| 74 | }); | ||
| 75 | |||
| 76 | defmt::info!("EDMA channel link example begin."); | ||
| 77 | |||
| 78 | // Initialize buffers | ||
| 79 | let src = SRC_BUFFER.take(); | ||
| 80 | let dst0 = DEST_BUFFER0.take(); | ||
| 81 | let dst1 = DEST_BUFFER1.take(); | ||
| 82 | let dst2 = DEST_BUFFER2.take(); | ||
| 83 | |||
| 84 | defmt::info!("Source Buffer: {=[?]}", src.as_slice()); | ||
| 85 | defmt::info!("DEST0 (before): {=[?]}", dst0.as_slice()); | ||
| 86 | defmt::info!("DEST1 (before): {=[?]}", dst1.as_slice()); | ||
| 87 | defmt::info!("DEST2 (before): {=[?]}", dst2.as_slice()); | ||
| 88 | |||
| 89 | defmt::info!("Configuring DMA channels with Embassy-style API..."); | ||
| 90 | |||
| 91 | let ch0 = DmaChannel::new(p.DMA_CH0); | ||
| 92 | let ch1 = DmaChannel::new(p.DMA_CH1); | ||
| 93 | let ch2 = DmaChannel::new(p.DMA_CH2); | ||
| 94 | |||
| 95 | // Configure channels using direct TCD access (advanced feature demo) | ||
| 96 | // This example demonstrates channel linking which requires direct TCD manipulation | ||
| 97 | |||
| 98 | // Helper to configure TCD for memory-to-memory transfer | ||
| 99 | // Parameters: channel, src, dst, width, nbytes (minor loop), count (major loop), interrupt | ||
| 100 | #[allow(clippy::too_many_arguments)] | ||
| 101 | unsafe fn configure_tcd( | ||
| 102 | edma: &embassy_mcxa::pac::edma_0_tcd0::RegisterBlock, | ||
| 103 | ch: usize, | ||
| 104 | src: u32, | ||
| 105 | dst: u32, | ||
| 106 | width: u8, | ||
| 107 | nbytes: u32, | ||
| 108 | count: u16, | ||
| 109 | enable_int: bool, | ||
| 110 | ) { | ||
| 111 | let t = edma.tcd(ch); | ||
| 112 | |||
| 113 | // Reset channel state | ||
| 114 | t.ch_csr().write(|w| { | ||
| 115 | w.erq() | ||
| 116 | .disable() | ||
| 117 | .earq() | ||
| 118 | .disable() | ||
| 119 | .eei() | ||
| 120 | .no_error() | ||
| 121 | .ebw() | ||
| 122 | .disable() | ||
| 123 | .done() | ||
| 124 | .clear_bit_by_one() | ||
| 125 | }); | ||
| 126 | t.ch_es().write(|w| w.bits(0)); | ||
| 127 | t.ch_int().write(|w| w.int().clear_bit_by_one()); | ||
| 128 | |||
| 129 | // Source/destination addresses | ||
| 130 | t.tcd_saddr().write(|w| w.saddr().bits(src)); | ||
| 131 | t.tcd_daddr().write(|w| w.daddr().bits(dst)); | ||
| 132 | |||
| 133 | // Offsets: increment by width | ||
| 134 | t.tcd_soff().write(|w| w.soff().bits(width as u16)); | ||
| 135 | t.tcd_doff().write(|w| w.doff().bits(width as u16)); | ||
| 136 | |||
| 137 | // Attributes: size = log2(width) | ||
| 138 | let size = match width { | ||
| 139 | 1 => 0, | ||
| 140 | 2 => 1, | ||
| 141 | 4 => 2, | ||
| 142 | _ => 0, | ||
| 143 | }; | ||
| 144 | t.tcd_attr().write(|w| w.ssize().bits(size).dsize().bits(size)); | ||
| 145 | |||
| 146 | // Number of bytes per minor loop | ||
| 147 | t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(nbytes)); | ||
| 148 | |||
| 149 | // Major loop: reset source address after major loop | ||
| 150 | let total_bytes = nbytes * count as u32; | ||
| 151 | t.tcd_slast_sda() | ||
| 152 | .write(|w| w.slast_sda().bits(-(total_bytes as i32) as u32)); | ||
| 153 | t.tcd_dlast_sga() | ||
| 154 | .write(|w| w.dlast_sga().bits(-(total_bytes as i32) as u32)); | ||
| 155 | |||
| 156 | // Major loop count | ||
| 157 | t.tcd_biter_elinkno().write(|w| w.biter().bits(count)); | ||
| 158 | t.tcd_citer_elinkno().write(|w| w.citer().bits(count)); | ||
| 159 | |||
| 160 | // Control/status: enable interrupt if requested | ||
| 161 | if enable_int { | ||
| 162 | t.tcd_csr().write(|w| w.intmajor().set_bit()); | ||
| 163 | } else { | ||
| 164 | t.tcd_csr().write(|w| w.intmajor().clear_bit()); | ||
| 165 | } | ||
| 166 | |||
| 167 | cortex_m::asm::dsb(); | ||
| 168 | } | ||
| 169 | |||
| 170 | unsafe { | ||
| 171 | // Channel 0: Transfer 16 bytes total (8 bytes per minor loop, 2 major iterations) | ||
| 172 | // Minor Link -> Channel 1 | ||
| 173 | // Major Link -> Channel 2 | ||
| 174 | configure_tcd( | ||
| 175 | edma, | ||
| 176 | 0, | ||
| 177 | src.as_ptr() as u32, | ||
| 178 | dst0.as_mut_ptr() as u32, | ||
| 179 | 4, // src width | ||
| 180 | 8, // nbytes (minor loop = 2 words) | ||
| 181 | 2, // count (major loop = 2 iterations) | ||
| 182 | false, // no interrupt | ||
| 183 | ); | ||
| 184 | ch0.set_minor_link(1); // Link to CH1 after each minor loop | ||
| 185 | ch0.set_major_link(2); // Link to CH2 after major loop | ||
| 186 | |||
| 187 | // Channel 1: Transfer 16 bytes (triggered by CH0 minor link) | ||
| 188 | configure_tcd( | ||
| 189 | edma, | ||
| 190 | 1, | ||
| 191 | src.as_ptr() as u32, | ||
| 192 | dst1.as_mut_ptr() as u32, | ||
| 193 | 4, | ||
| 194 | 16, // full buffer in one minor loop | ||
| 195 | 1, // 1 major iteration | ||
| 196 | false, | ||
| 197 | ); | ||
| 198 | |||
| 199 | // Channel 2: Transfer 16 bytes (triggered by CH0 major link) | ||
| 200 | configure_tcd( | ||
| 201 | edma, | ||
| 202 | 2, | ||
| 203 | src.as_ptr() as u32, | ||
| 204 | dst2.as_mut_ptr() as u32, | ||
| 205 | 4, | ||
| 206 | 16, // full buffer in one minor loop | ||
| 207 | 1, // 1 major iteration | ||
| 208 | true, // enable interrupt | ||
| 209 | ); | ||
| 210 | } | ||
| 211 | |||
| 212 | defmt::info!("Triggering Channel 0 (1st minor loop)..."); | ||
| 213 | |||
| 214 | // Trigger first minor loop of CH0 | ||
| 215 | unsafe { | ||
| 216 | ch0.trigger_start(); | ||
| 217 | } | ||
| 218 | |||
| 219 | // Wait for CH1 to complete (triggered by CH0 minor link) | ||
| 220 | while !ch1.is_done() { | ||
| 221 | cortex_m::asm::nop(); | ||
| 222 | } | ||
| 223 | unsafe { | ||
| 224 | ch1.clear_done(); | ||
| 225 | } | ||
| 226 | |||
| 227 | defmt::info!("CH1 done (via minor link)."); | ||
| 228 | defmt::info!("Triggering Channel 0 (2nd minor loop)..."); | ||
| 229 | |||
| 230 | // Trigger second minor loop of CH0 | ||
| 231 | unsafe { | ||
| 232 | ch0.trigger_start(); | ||
| 233 | } | ||
| 234 | |||
| 235 | // Wait for CH0 major loop to complete | ||
| 236 | while !ch0.is_done() { | ||
| 237 | cortex_m::asm::nop(); | ||
| 238 | } | ||
| 239 | unsafe { | ||
| 240 | ch0.clear_done(); | ||
| 241 | } | ||
| 242 | |||
| 243 | defmt::info!("CH0 major loop done."); | ||
| 244 | |||
| 245 | // Wait for CH2 to complete (triggered by CH0 major link) | ||
| 246 | // Using is_done() instead of AtomicBool - the standard interrupt handler | ||
| 247 | // clears the interrupt flag and wakes wakers, but DONE bit remains set | ||
| 248 | while !ch2.is_done() { | ||
| 249 | cortex_m::asm::nop(); | ||
| 250 | } | ||
| 251 | unsafe { | ||
| 252 | ch2.clear_done(); | ||
| 253 | } | ||
| 254 | |||
| 255 | defmt::info!("CH2 done (via major link)."); | ||
| 256 | |||
| 257 | defmt::info!("EDMA channel link example finish."); | ||
| 258 | |||
| 259 | defmt::info!("DEST0 (after): {=[?]}", dst0.as_slice()); | ||
| 260 | defmt::info!("DEST1 (after): {=[?]}", dst1.as_slice()); | ||
| 261 | defmt::info!("DEST2 (after): {=[?]}", dst2.as_slice()); | ||
| 262 | |||
| 263 | // Verify all buffers match source | ||
| 264 | let mut success = true; | ||
| 265 | for sli in [dst0, dst1, dst2] { | ||
| 266 | success &= sli == src; | ||
| 267 | } | ||
| 268 | |||
| 269 | if success { | ||
| 270 | defmt::info!("PASS: Data verified."); | ||
| 271 | } else { | ||
| 272 | defmt::error!("FAIL: Mismatch detected!"); | ||
| 273 | } | ||
| 274 | |||
| 275 | loop { | ||
| 276 | cortex_m::asm::wfe(); | ||
| 277 | } | ||
| 278 | } | ||
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 @@ | |||
| 1 | //! DMA interleaved transfer example for MCXA276. | ||
| 2 | //! | ||
| 3 | //! NOTE: this is a "raw dma" example! It exists as a proof of concept, as we don't have | ||
| 4 | //! a high-level and safe API for. It should not be taken as typical, recommended, or | ||
| 5 | //! stable usage! | ||
| 6 | //! | ||
| 7 | //! This example demonstrates using DMA with custom source/destination offsets | ||
| 8 | //! to interleave data during transfer. | ||
| 9 | //! | ||
| 10 | //! # Embassy-style features demonstrated: | ||
| 11 | //! - `TransferOptions::default()` for configuration (used internally) | ||
| 12 | //! - DMA channel with `DmaChannel::new()` | ||
| 13 | |||
| 14 | #![no_std] | ||
| 15 | #![no_main] | ||
| 16 | |||
| 17 | use embassy_executor::Spawner; | ||
| 18 | use embassy_mcxa::clocks::config::Div8; | ||
| 19 | use embassy_mcxa::dma::DmaChannel; | ||
| 20 | use static_cell::ConstStaticCell; | ||
| 21 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 22 | |||
| 23 | const BUFFER_LENGTH: usize = 16; | ||
| 24 | const HALF_BUFF_LENGTH: usize = BUFFER_LENGTH / 2; | ||
| 25 | |||
| 26 | // Buffers in RAM | ||
| 27 | static SRC_BUFFER: ConstStaticCell<[u32; HALF_BUFF_LENGTH]> = ConstStaticCell::new([0; HALF_BUFF_LENGTH]); | ||
| 28 | static DEST_BUFFER: ConstStaticCell<[u32; BUFFER_LENGTH]> = ConstStaticCell::new([0; BUFFER_LENGTH]); | ||
| 29 | |||
| 30 | #[embassy_executor::main] | ||
| 31 | async fn main(_spawner: Spawner) { | ||
| 32 | // Small delay to allow probe-rs to attach after reset | ||
| 33 | for _ in 0..100_000 { | ||
| 34 | cortex_m::asm::nop(); | ||
| 35 | } | ||
| 36 | |||
| 37 | let mut cfg = hal::config::Config::default(); | ||
| 38 | cfg.clock_cfg.sirc.fro_12m_enabled = true; | ||
| 39 | cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); | ||
| 40 | let p = hal::init(cfg); | ||
| 41 | |||
| 42 | defmt::info!("DMA interleave transfer example starting..."); | ||
| 43 | |||
| 44 | defmt::info!("EDMA interleave transfer example begin."); | ||
| 45 | |||
| 46 | // Initialize buffers | ||
| 47 | let src = SRC_BUFFER.take(); | ||
| 48 | *src = [1, 2, 3, 4, 5, 6, 7, 8]; | ||
| 49 | let dst = DEST_BUFFER.take(); | ||
| 50 | |||
| 51 | defmt::info!("Source Buffer: {=[?]}", src.as_slice()); | ||
| 52 | defmt::info!("Destination Buffer (before): {=[?]}", dst.as_slice()); | ||
| 53 | |||
| 54 | defmt::info!("Configuring DMA with Embassy-style API..."); | ||
| 55 | |||
| 56 | // Create DMA channel using Embassy-style API | ||
| 57 | let dma_ch0 = DmaChannel::new(p.DMA_CH0); | ||
| 58 | |||
| 59 | // Configure interleaved transfer using direct TCD access: | ||
| 60 | // - src_offset = 4: advance source by 4 bytes after each read | ||
| 61 | // - dst_offset = 8: advance dest by 8 bytes after each write | ||
| 62 | // This spreads source data across every other word in destination | ||
| 63 | unsafe { | ||
| 64 | let t = dma_ch0.tcd(); | ||
| 65 | |||
| 66 | // Reset channel state | ||
| 67 | t.ch_csr().write(|w| { | ||
| 68 | w.erq() | ||
| 69 | .disable() | ||
| 70 | .earq() | ||
| 71 | .disable() | ||
| 72 | .eei() | ||
| 73 | .no_error() | ||
| 74 | .ebw() | ||
| 75 | .disable() | ||
| 76 | .done() | ||
| 77 | .clear_bit_by_one() | ||
| 78 | }); | ||
| 79 | t.ch_es().write(|w| w.bits(0)); | ||
| 80 | t.ch_int().write(|w| w.int().clear_bit_by_one()); | ||
| 81 | |||
| 82 | // Source/destination addresses | ||
| 83 | t.tcd_saddr().write(|w| w.saddr().bits(src.as_ptr() as u32)); | ||
| 84 | t.tcd_daddr().write(|w| w.daddr().bits(dst.as_mut_ptr() as u32)); | ||
| 85 | |||
| 86 | // Custom offsets for interleaving | ||
| 87 | t.tcd_soff().write(|w| w.soff().bits(4)); // src: +4 bytes per read | ||
| 88 | t.tcd_doff().write(|w| w.doff().bits(8)); // dst: +8 bytes per write | ||
| 89 | |||
| 90 | // Attributes: 32-bit transfers (size = 2) | ||
| 91 | t.tcd_attr().write(|w| w.ssize().bits(2).dsize().bits(2)); | ||
| 92 | |||
| 93 | // Transfer entire source buffer in one minor loop | ||
| 94 | let nbytes = (HALF_BUFF_LENGTH * 4) as u32; | ||
| 95 | t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(nbytes)); | ||
| 96 | |||
| 97 | // Reset source address after major loop | ||
| 98 | t.tcd_slast_sda().write(|w| w.slast_sda().bits(-(nbytes as i32) as u32)); | ||
| 99 | // Destination uses 2x offset, so adjust accordingly | ||
| 100 | let dst_total = (HALF_BUFF_LENGTH * 8) as u32; | ||
| 101 | t.tcd_dlast_sga() | ||
| 102 | .write(|w| w.dlast_sga().bits(-(dst_total as i32) as u32)); | ||
| 103 | |||
| 104 | // Major loop count = 1 | ||
| 105 | t.tcd_biter_elinkno().write(|w| w.biter().bits(1)); | ||
| 106 | t.tcd_citer_elinkno().write(|w| w.citer().bits(1)); | ||
| 107 | |||
| 108 | // Enable interrupt on major loop completion | ||
| 109 | t.tcd_csr().write(|w| w.intmajor().set_bit()); | ||
| 110 | |||
| 111 | cortex_m::asm::dsb(); | ||
| 112 | |||
| 113 | defmt::info!("Triggering transfer..."); | ||
| 114 | dma_ch0.trigger_start(); | ||
| 115 | } | ||
| 116 | |||
| 117 | // Wait for completion using channel helper method | ||
| 118 | while !dma_ch0.is_done() { | ||
| 119 | cortex_m::asm::nop(); | ||
| 120 | } | ||
| 121 | unsafe { | ||
| 122 | dma_ch0.clear_done(); | ||
| 123 | } | ||
| 124 | |||
| 125 | defmt::info!("EDMA interleave transfer example finish."); | ||
| 126 | defmt::info!("Destination Buffer (after): {=[?]}", dst.as_slice()); | ||
| 127 | |||
| 128 | // Verify: Even indices should match SRC_BUFFER[i/2], odd indices should be 0 | ||
| 129 | let mut mismatch = false; | ||
| 130 | let diter = dst.chunks_exact(2); | ||
| 131 | let siter = src.iter(); | ||
| 132 | for (ch, src) in diter.zip(siter) { | ||
| 133 | mismatch |= !matches!(ch, [a, 0] if a == src); | ||
| 134 | } | ||
| 135 | |||
| 136 | if mismatch { | ||
| 137 | defmt::error!("FAIL: Mismatch detected!"); | ||
| 138 | } else { | ||
| 139 | defmt::info!("PASS: Data verified."); | ||
| 140 | } | ||
| 141 | } | ||
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 @@ | |||
| 1 | //! DMA memset example for MCXA276. | ||
| 2 | //! | ||
| 3 | //! NOTE: this is a "raw dma" example! It exists as a proof of concept, as we don't have | ||
| 4 | //! a high-level and safe API for. It should not be taken as typical, recommended, or | ||
| 5 | //! stable usage! | ||
| 6 | //! | ||
| 7 | //! This example demonstrates using DMA to fill a buffer with a repeated pattern. | ||
| 8 | //! The source address stays fixed while the destination increments. | ||
| 9 | //! | ||
| 10 | //! # Embassy-style features demonstrated: | ||
| 11 | //! - `DmaChannel::is_done()` and `clear_done()` helper methods | ||
| 12 | //! - No need to pass register block around | ||
| 13 | |||
| 14 | #![no_std] | ||
| 15 | #![no_main] | ||
| 16 | |||
| 17 | use embassy_executor::Spawner; | ||
| 18 | use embassy_mcxa::clocks::config::Div8; | ||
| 19 | use embassy_mcxa::dma::DmaChannel; | ||
| 20 | use static_cell::ConstStaticCell; | ||
| 21 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 22 | |||
| 23 | const BUFFER_LENGTH: usize = 4; | ||
| 24 | |||
| 25 | // Buffers in RAM | ||
| 26 | static PATTERN: u32 = 0xDEADBEEF; | ||
| 27 | static DEST_BUFFER: ConstStaticCell<[u32; BUFFER_LENGTH]> = ConstStaticCell::new([0; BUFFER_LENGTH]); | ||
| 28 | |||
| 29 | #[embassy_executor::main] | ||
| 30 | async fn main(_spawner: Spawner) { | ||
| 31 | // Small delay to allow probe-rs to attach after reset | ||
| 32 | for _ in 0..100_000 { | ||
| 33 | cortex_m::asm::nop(); | ||
| 34 | } | ||
| 35 | |||
| 36 | let mut cfg = hal::config::Config::default(); | ||
| 37 | cfg.clock_cfg.sirc.fro_12m_enabled = true; | ||
| 38 | cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); | ||
| 39 | let p = hal::init(cfg); | ||
| 40 | |||
| 41 | defmt::info!("DMA memset example starting..."); | ||
| 42 | defmt::info!("EDMA memset example begin."); | ||
| 43 | |||
| 44 | // Initialize buffers | ||
| 45 | let pat = &PATTERN; | ||
| 46 | let dst = DEST_BUFFER.take(); | ||
| 47 | defmt::info!("Pattern Value: {=u32}", pat); | ||
| 48 | defmt::info!("Destination Buffer (before): {=[?]}", dst.as_slice()); | ||
| 49 | defmt::info!("Configuring DMA with Embassy-style API..."); | ||
| 50 | |||
| 51 | // Create DMA channel using Embassy-style API | ||
| 52 | let dma_ch0 = DmaChannel::new(p.DMA_CH0); | ||
| 53 | |||
| 54 | // Configure memset transfer using direct TCD access: | ||
| 55 | // Source stays fixed (soff = 0, reads same pattern repeatedly) | ||
| 56 | // Destination increments (doff = 4) | ||
| 57 | unsafe { | ||
| 58 | let t = dma_ch0.tcd(); | ||
| 59 | |||
| 60 | // Reset channel state | ||
| 61 | t.ch_csr().write(|w| { | ||
| 62 | w.erq() | ||
| 63 | .disable() | ||
| 64 | .earq() | ||
| 65 | .disable() | ||
| 66 | .eei() | ||
| 67 | .no_error() | ||
| 68 | .ebw() | ||
| 69 | .disable() | ||
| 70 | .done() | ||
| 71 | .clear_bit_by_one() | ||
| 72 | }); | ||
| 73 | t.ch_es().write(|w| w.bits(0)); | ||
| 74 | t.ch_int().write(|w| w.int().clear_bit_by_one()); | ||
| 75 | |||
| 76 | // Source address (pattern) - fixed | ||
| 77 | t.tcd_saddr().write(|w| w.saddr().bits(pat as *const _ as u32)); | ||
| 78 | // Destination address - increments | ||
| 79 | t.tcd_daddr().write(|w| w.daddr().bits(dst.as_mut_ptr() as u32)); | ||
| 80 | |||
| 81 | // Source offset = 0 (stays fixed), Dest offset = 4 (increments) | ||
| 82 | t.tcd_soff().write(|w| w.soff().bits(0)); | ||
| 83 | t.tcd_doff().write(|w| w.doff().bits(4)); | ||
| 84 | |||
| 85 | // Attributes: 32-bit transfers (size = 2) | ||
| 86 | t.tcd_attr().write(|w| w.ssize().bits(2).dsize().bits(2)); | ||
| 87 | |||
| 88 | // Transfer entire buffer in one minor loop | ||
| 89 | let nbytes = (BUFFER_LENGTH * 4) as u32; | ||
| 90 | t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(nbytes)); | ||
| 91 | |||
| 92 | // Source doesn't need adjustment (stays fixed) | ||
| 93 | t.tcd_slast_sda().write(|w| w.slast_sda().bits(0)); | ||
| 94 | // Reset dest address after major loop | ||
| 95 | t.tcd_dlast_sga().write(|w| w.dlast_sga().bits(-(nbytes as i32) as u32)); | ||
| 96 | |||
| 97 | // Major loop count = 1 | ||
| 98 | t.tcd_biter_elinkno().write(|w| w.biter().bits(1)); | ||
| 99 | t.tcd_citer_elinkno().write(|w| w.citer().bits(1)); | ||
| 100 | |||
| 101 | // Enable interrupt on major loop completion | ||
| 102 | t.tcd_csr().write(|w| w.intmajor().set_bit()); | ||
| 103 | |||
| 104 | cortex_m::asm::dsb(); | ||
| 105 | |||
| 106 | defmt::info!("Triggering transfer..."); | ||
| 107 | dma_ch0.trigger_start(); | ||
| 108 | } | ||
| 109 | |||
| 110 | // Wait for completion using channel helper method | ||
| 111 | while !dma_ch0.is_done() { | ||
| 112 | cortex_m::asm::nop(); | ||
| 113 | } | ||
| 114 | unsafe { | ||
| 115 | dma_ch0.clear_done(); | ||
| 116 | } | ||
| 117 | |||
| 118 | defmt::info!("EDMA memset example finish."); | ||
| 119 | defmt::info!("Destination Buffer (after): {=[?]}", dst.as_slice()); | ||
| 120 | |||
| 121 | // Verify: All elements should equal PATTERN | ||
| 122 | let mismatch = dst.iter().any(|i| *i != *pat); | ||
| 123 | |||
| 124 | if mismatch { | ||
| 125 | defmt::error!("FAIL: Mismatch detected!"); | ||
| 126 | } else { | ||
| 127 | defmt::info!("PASS: Data verified."); | ||
| 128 | } | ||
| 129 | } | ||
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..80df40449 --- /dev/null +++ b/examples/mcxa/src/bin/raw_dma_ping_pong_transfer.rs | |||
| @@ -0,0 +1,244 @@ | |||
| 1 | //! DMA ping-pong/double-buffer transfer example for MCXA276. | ||
| 2 | //! | ||
| 3 | //! NOTE: this is a "raw dma" example! It exists as a proof of concept, as we don't have | ||
| 4 | //! a high-level and safe API for. It should not be taken as typical, recommended, or | ||
| 5 | //! stable usage! | ||
| 6 | //! | ||
| 7 | //! This example demonstrates two approaches for ping-pong/double-buffering: | ||
| 8 | //! | ||
| 9 | //! ## Approach 1: Scatter/Gather with linked TCDs (manual) | ||
| 10 | //! - Two TCDs link to each other for alternating transfers | ||
| 11 | //! - Uses custom handler that delegates to on_interrupt() then signals completion | ||
| 12 | //! - Note: With ESG=1, DONE bit is cleared by hardware when next TCD loads, | ||
| 13 | //! so we need an AtomicBool to track completion | ||
| 14 | //! | ||
| 15 | //! ## Approach 2: Half-transfer interrupt with wait_half() (NEW!) | ||
| 16 | //! - Single continuous transfer over entire buffer | ||
| 17 | //! - Uses half-transfer interrupt to know when first half is ready | ||
| 18 | //! - Application can process first half while second half is being filled | ||
| 19 | //! | ||
| 20 | //! # Embassy-style features demonstrated: | ||
| 21 | //! - `DmaChannel::new()` for channel creation | ||
| 22 | //! - Scatter/gather with linked TCDs | ||
| 23 | //! - Custom handler that delegates to HAL's `on_interrupt()` (best practice) | ||
| 24 | //! - Standard `DmaCh1InterruptHandler` with `bind_interrupts!` macro | ||
| 25 | //! - NEW: `wait_half()` for half-transfer interrupt handling | ||
| 26 | |||
| 27 | #![no_std] | ||
| 28 | #![no_main] | ||
| 29 | |||
| 30 | use embassy_executor::Spawner; | ||
| 31 | use embassy_mcxa::clocks::config::Div8; | ||
| 32 | use embassy_mcxa::dma::{DmaChannel, Tcd, TransferOptions}; | ||
| 33 | use embassy_mcxa::pac; | ||
| 34 | use static_cell::ConstStaticCell; | ||
| 35 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 36 | |||
| 37 | // Source and destination buffers for Approach 1 (scatter/gather) | ||
| 38 | static SRC: ConstStaticCell<[u32; 8]> = ConstStaticCell::new([1, 2, 3, 4, 5, 6, 7, 8]); | ||
| 39 | static DST: ConstStaticCell<[u32; 8]> = ConstStaticCell::new([0; 8]); | ||
| 40 | |||
| 41 | // Source and destination buffers for Approach 2 (wait_half) | ||
| 42 | static SRC2: ConstStaticCell<[u32; 8]> = ConstStaticCell::new([0xA1, 0xA2, 0xA3, 0xA4, 0xB1, 0xB2, 0xB3, 0xB4]); | ||
| 43 | static DST2: ConstStaticCell<[u32; 8]> = ConstStaticCell::new([0; 8]); | ||
| 44 | |||
| 45 | // TCD pool for scatter/gather - must be 32-byte aligned | ||
| 46 | #[repr(C, align(32))] | ||
| 47 | struct TcdPool([Tcd; 2]); | ||
| 48 | |||
| 49 | static TCD_POOL: ConstStaticCell<TcdPool> = ConstStaticCell::new(TcdPool( | ||
| 50 | [Tcd { | ||
| 51 | saddr: 0, | ||
| 52 | soff: 0, | ||
| 53 | attr: 0, | ||
| 54 | nbytes: 0, | ||
| 55 | slast: 0, | ||
| 56 | daddr: 0, | ||
| 57 | doff: 0, | ||
| 58 | citer: 0, | ||
| 59 | dlast_sga: 0, | ||
| 60 | csr: 0, | ||
| 61 | biter: 0, | ||
| 62 | }; 2], | ||
| 63 | )); | ||
| 64 | |||
| 65 | #[embassy_executor::main] | ||
| 66 | async fn main(_spawner: Spawner) { | ||
| 67 | // Small delay to allow probe-rs to attach after reset | ||
| 68 | for _ in 0..100_000 { | ||
| 69 | cortex_m::asm::nop(); | ||
| 70 | } | ||
| 71 | |||
| 72 | let mut cfg = hal::config::Config::default(); | ||
| 73 | cfg.clock_cfg.sirc.fro_12m_enabled = true; | ||
| 74 | cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); | ||
| 75 | let p = hal::init(cfg); | ||
| 76 | |||
| 77 | defmt::info!("DMA ping-pong transfer example starting..."); | ||
| 78 | |||
| 79 | defmt::info!("EDMA ping-pong transfer example begin."); | ||
| 80 | |||
| 81 | // Initialize buffers | ||
| 82 | let src = SRC.take(); | ||
| 83 | let dst = DST.take(); | ||
| 84 | |||
| 85 | defmt::info!("Source Buffer: {=[?]}", src.as_slice()); | ||
| 86 | defmt::info!("Destination Buffer (before): {=[?]}", dst.as_slice()); | ||
| 87 | |||
| 88 | defmt::info!("Configuring ping-pong DMA with Embassy-style API..."); | ||
| 89 | |||
| 90 | let dma_ch0 = DmaChannel::new(p.DMA_CH0); | ||
| 91 | |||
| 92 | // Configure ping-pong transfer using direct TCD access: | ||
| 93 | // This sets up TCD0 and TCD1 in RAM, and loads TCD0 into the channel. | ||
| 94 | // TCD0 transfers first half (SRC[0..4] -> DST[0..4]), links to TCD1. | ||
| 95 | // TCD1 transfers second half (SRC[4..8] -> DST[4..8]), links to TCD0. | ||
| 96 | let tcds = &mut TCD_POOL.take().0; | ||
| 97 | |||
| 98 | let half_len = 4usize; | ||
| 99 | let half_bytes = (half_len * 4) as u32; | ||
| 100 | |||
| 101 | unsafe { | ||
| 102 | let tcd0_addr = &tcds[0] as *const _ as u32; | ||
| 103 | let tcd1_addr = &tcds[1] as *const _ as u32; | ||
| 104 | |||
| 105 | // TCD0: First half -> Links to TCD1 | ||
| 106 | tcds[0] = Tcd { | ||
| 107 | saddr: src.as_ptr() as u32, | ||
| 108 | soff: 4, | ||
| 109 | attr: 0x0202, // 32-bit src/dst | ||
| 110 | nbytes: half_bytes, | ||
| 111 | slast: 0, | ||
| 112 | daddr: dst.as_mut_ptr() as u32, | ||
| 113 | doff: 4, | ||
| 114 | citer: 1, | ||
| 115 | dlast_sga: tcd1_addr as i32, | ||
| 116 | csr: 0x0012, // ESG | INTMAJOR | ||
| 117 | biter: 1, | ||
| 118 | }; | ||
| 119 | |||
| 120 | // TCD1: Second half -> Links to TCD0 | ||
| 121 | tcds[1] = Tcd { | ||
| 122 | saddr: src.as_ptr().add(half_len) as u32, | ||
| 123 | soff: 4, | ||
| 124 | attr: 0x0202, | ||
| 125 | nbytes: half_bytes, | ||
| 126 | slast: 0, | ||
| 127 | daddr: dst.as_mut_ptr().add(half_len) as u32, | ||
| 128 | doff: 4, | ||
| 129 | citer: 1, | ||
| 130 | dlast_sga: tcd0_addr as i32, | ||
| 131 | csr: 0x0012, | ||
| 132 | biter: 1, | ||
| 133 | }; | ||
| 134 | |||
| 135 | // Load TCD0 into hardware registers | ||
| 136 | dma_ch0.load_tcd(&tcds[0]); | ||
| 137 | } | ||
| 138 | |||
| 139 | defmt::info!("Triggering first half transfer..."); | ||
| 140 | |||
| 141 | // Trigger first transfer (first half: SRC[0..4] -> DST[0..4]) | ||
| 142 | unsafe { | ||
| 143 | dma_ch0.trigger_start(); | ||
| 144 | } | ||
| 145 | |||
| 146 | let tcd = dma_ch0.tcd(); | ||
| 147 | // Wait for first half | ||
| 148 | loop { | ||
| 149 | if tcd.tcd_saddr().read().bits() != src.as_ptr() as u32 { | ||
| 150 | break; | ||
| 151 | } | ||
| 152 | } | ||
| 153 | |||
| 154 | defmt::info!("First half transferred."); | ||
| 155 | defmt::info!("Triggering second half transfer..."); | ||
| 156 | |||
| 157 | // Trigger second transfer (second half: SRC[4..8] -> DST[4..8]) | ||
| 158 | unsafe { | ||
| 159 | dma_ch0.trigger_start(); | ||
| 160 | } | ||
| 161 | |||
| 162 | // Wait for second half | ||
| 163 | loop { | ||
| 164 | if tcd.tcd_saddr().read().bits() != unsafe { src.as_ptr().add(half_len) } as u32 { | ||
| 165 | break; | ||
| 166 | } | ||
| 167 | } | ||
| 168 | |||
| 169 | defmt::info!("Second half transferred."); | ||
| 170 | |||
| 171 | defmt::info!("EDMA ping-pong transfer example finish."); | ||
| 172 | defmt::info!("Destination Buffer (after): {=[?]}", dst.as_slice()); | ||
| 173 | |||
| 174 | // Verify: DST should match SRC | ||
| 175 | let mismatch = src != dst; | ||
| 176 | |||
| 177 | if mismatch { | ||
| 178 | defmt::error!("FAIL: Approach 1 mismatch detected!"); | ||
| 179 | } else { | ||
| 180 | defmt::info!("PASS: Approach 1 data verified."); | ||
| 181 | } | ||
| 182 | |||
| 183 | // ========================================================================= | ||
| 184 | // Approach 2: Half-Transfer Interrupt with wait_half() (NEW!) | ||
| 185 | // ========================================================================= | ||
| 186 | // | ||
| 187 | // This approach uses a single continuous DMA transfer with half-transfer | ||
| 188 | // interrupt enabled. The wait_half() method allows you to be notified | ||
| 189 | // when the first half of the buffer is complete, so you can process it | ||
| 190 | // while the second half is still being filled. | ||
| 191 | // | ||
| 192 | // Benefits: | ||
| 193 | // - Simpler setup (no TCD pool needed) | ||
| 194 | // - True async/await support | ||
| 195 | // - Good for streaming data processing | ||
| 196 | |||
| 197 | defmt::info!("--- Approach 2: wait_half() demo ---"); | ||
| 198 | |||
| 199 | // Enable DMA CH1 interrupt | ||
| 200 | unsafe { | ||
| 201 | cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH1); | ||
| 202 | } | ||
| 203 | |||
| 204 | // Initialize approach 2 buffers | ||
| 205 | let src2 = SRC2.take(); | ||
| 206 | let dst2 = DST2.take(); | ||
| 207 | |||
| 208 | defmt::info!("SRC2: {=[?]}", src2.as_slice()); | ||
| 209 | |||
| 210 | let dma_ch1 = DmaChannel::new(p.DMA_CH1); | ||
| 211 | |||
| 212 | // Configure transfer with half-transfer interrupt enabled | ||
| 213 | let mut options = TransferOptions::default(); | ||
| 214 | options.half_transfer_interrupt = true; // Enable half-transfer interrupt | ||
| 215 | options.complete_transfer_interrupt = true; | ||
| 216 | |||
| 217 | defmt::info!("Starting transfer with half_transfer_interrupt..."); | ||
| 218 | |||
| 219 | // Create the transfer | ||
| 220 | let mut transfer = dma_ch1.mem_to_mem(src2, dst2, options).unwrap(); | ||
| 221 | |||
| 222 | // Wait for half-transfer (first 4 elements) | ||
| 223 | defmt::info!("Waiting for first half..."); | ||
| 224 | let _ok = transfer.wait_half().await.unwrap(); | ||
| 225 | |||
| 226 | defmt::info!("Half-transfer complete!"); | ||
| 227 | |||
| 228 | // Wait for complete transfer | ||
| 229 | defmt::info!("Waiting for second half..."); | ||
| 230 | transfer.await.unwrap(); | ||
| 231 | |||
| 232 | defmt::info!("Transfer complete! Full DST2: {=[?]}", dst2.as_slice()); | ||
| 233 | |||
| 234 | // Verify approach 2 | ||
| 235 | let mismatch2 = src2 != dst2; | ||
| 236 | |||
| 237 | if mismatch2 { | ||
| 238 | defmt::error!("FAIL: Approach 2 mismatch!"); | ||
| 239 | } else { | ||
| 240 | defmt::info!("PASS: Approach 2 verified."); | ||
| 241 | } | ||
| 242 | |||
| 243 | defmt::info!("=== All ping-pong demos complete ==="); | ||
| 244 | } | ||
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..eb9960764 --- /dev/null +++ b/examples/mcxa/src/bin/raw_dma_scatter_gather.rs | |||
| @@ -0,0 +1,165 @@ | |||
| 1 | //! DMA scatter-gather transfer example for MCXA276. | ||
| 2 | //! | ||
| 3 | //! NOTE: this is a "raw dma" example! It exists as a proof of concept, as we don't have | ||
| 4 | //! a high-level and safe API for. It should not be taken as typical, recommended, or | ||
| 5 | //! stable usage! | ||
| 6 | //! | ||
| 7 | //! This example demonstrates using DMA with scatter/gather to chain multiple | ||
| 8 | //! transfer descriptors. The first TCD transfers the first half of the buffer, | ||
| 9 | //! then automatically loads the second TCD to transfer the second half. | ||
| 10 | //! | ||
| 11 | //! # Embassy-style features demonstrated: | ||
| 12 | //! - `DmaChannel::new()` for channel creation | ||
| 13 | //! - Scatter/gather with chained TCDs | ||
| 14 | //! - Custom handler that delegates to HAL's `on_interrupt()` (best practice) | ||
| 15 | |||
| 16 | #![no_std] | ||
| 17 | #![no_main] | ||
| 18 | |||
| 19 | use embassy_executor::Spawner; | ||
| 20 | use embassy_mcxa::clocks::config::Div8; | ||
| 21 | use embassy_mcxa::dma::{DmaChannel, Tcd}; | ||
| 22 | use static_cell::ConstStaticCell; | ||
| 23 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 24 | |||
| 25 | // Source and destination buffers | ||
| 26 | static SRC: ConstStaticCell<[u32; 12]> = ConstStaticCell::new([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); | ||
| 27 | static DST: ConstStaticCell<[u32; 12]> = ConstStaticCell::new([0; 12]); | ||
| 28 | |||
| 29 | // TCD pool for scatter/gather - must be 32-byte aligned | ||
| 30 | #[repr(C, align(32))] | ||
| 31 | struct TcdPool([Tcd; 2]); | ||
| 32 | |||
| 33 | static TCD_POOL: ConstStaticCell<TcdPool> = ConstStaticCell::new(TcdPool( | ||
| 34 | [Tcd { | ||
| 35 | saddr: 0, | ||
| 36 | soff: 0, | ||
| 37 | attr: 0, | ||
| 38 | nbytes: 0, | ||
| 39 | slast: 0, | ||
| 40 | daddr: 0, | ||
| 41 | doff: 0, | ||
| 42 | citer: 0, | ||
| 43 | dlast_sga: 0, | ||
| 44 | csr: 0, | ||
| 45 | biter: 0, | ||
| 46 | }; 2], | ||
| 47 | )); | ||
| 48 | |||
| 49 | #[embassy_executor::main] | ||
| 50 | async fn main(_spawner: Spawner) { | ||
| 51 | // Small delay to allow probe-rs to attach after reset | ||
| 52 | for _ in 0..100_000 { | ||
| 53 | cortex_m::asm::nop(); | ||
| 54 | } | ||
| 55 | |||
| 56 | let mut cfg = hal::config::Config::default(); | ||
| 57 | cfg.clock_cfg.sirc.fro_12m_enabled = true; | ||
| 58 | cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); | ||
| 59 | let p = hal::init(cfg); | ||
| 60 | |||
| 61 | defmt::info!("DMA scatter-gather transfer example starting..."); | ||
| 62 | |||
| 63 | defmt::info!("EDMA scatter-gather transfer example begin."); | ||
| 64 | |||
| 65 | // Initialize buffers | ||
| 66 | let src = SRC.take(); | ||
| 67 | let dst = DST.take(); | ||
| 68 | |||
| 69 | defmt::info!("Source Buffer: {=[?]}", src.as_slice()); | ||
| 70 | defmt::info!("Destination Buffer (before): {=[?]}", dst.as_slice()); | ||
| 71 | defmt::info!("Configuring scatter-gather DMA with Embassy-style API..."); | ||
| 72 | |||
| 73 | let dma_ch0 = DmaChannel::new(p.DMA_CH0); | ||
| 74 | let src_ptr = src.as_ptr(); | ||
| 75 | let dst_ptr = dst.as_mut_ptr(); | ||
| 76 | |||
| 77 | // Configure scatter-gather transfer using direct TCD access: | ||
| 78 | // This sets up TCD0 and TCD1 in RAM, and loads TCD0 into the channel. | ||
| 79 | // TCD0 transfers first half (SRC[0..4] -> DST[0..4]), then loads TCD1. | ||
| 80 | // TCD1 transfers second half (SRC[4..12] -> DST[4..12]), last TCD. | ||
| 81 | unsafe { | ||
| 82 | let tcds = &mut TCD_POOL.take().0; | ||
| 83 | |||
| 84 | // In the first transfer, copy | ||
| 85 | tcds[0] = Tcd { | ||
| 86 | saddr: src_ptr as u32, | ||
| 87 | soff: 4, | ||
| 88 | attr: 0x0202, // 32-bit src/dst | ||
| 89 | nbytes: 4 * 4, | ||
| 90 | slast: 0, | ||
| 91 | daddr: dst_ptr as u32, | ||
| 92 | doff: 4, | ||
| 93 | citer: 1, | ||
| 94 | dlast_sga: tcds.as_ptr().add(1) as i32, | ||
| 95 | // ESG (scatter/gather) for non-last, INTMAJOR for all | ||
| 96 | csr: 0x0012, | ||
| 97 | biter: 1, | ||
| 98 | }; | ||
| 99 | |||
| 100 | tcds[1] = Tcd { | ||
| 101 | saddr: src_ptr.add(4) as u32, | ||
| 102 | soff: 4, | ||
| 103 | attr: 0x0202, // 32-bit src/dst | ||
| 104 | nbytes: 8 * 4, | ||
| 105 | slast: 0, | ||
| 106 | daddr: dst_ptr.add(4) as u32, | ||
| 107 | doff: 4, | ||
| 108 | citer: 1, | ||
| 109 | dlast_sga: 0, | ||
| 110 | // ESG (scatter/gather) for non-last, INTMAJOR for all | ||
| 111 | csr: 0x0002, | ||
| 112 | biter: 1, | ||
| 113 | }; | ||
| 114 | |||
| 115 | // Load TCD0 into hardware registers | ||
| 116 | dma_ch0.load_tcd(&tcds[0]); | ||
| 117 | } | ||
| 118 | |||
| 119 | defmt::info!("Triggering first half transfer..."); | ||
| 120 | |||
| 121 | let tcd = dma_ch0.tcd(); | ||
| 122 | |||
| 123 | // Trigger first transfer (first half: SRC[0..4] -> DST[0..4]) | ||
| 124 | // TCD0 is currently loaded. | ||
| 125 | unsafe { | ||
| 126 | dma_ch0.trigger_start(); | ||
| 127 | } | ||
| 128 | |||
| 129 | // Wait for first half | ||
| 130 | loop { | ||
| 131 | if tcd.tcd_saddr().read().bits() != src_ptr as u32 { | ||
| 132 | defmt::info!("saddr: {=u32}", tcd.tcd_saddr().read().bits()); | ||
| 133 | defmt::info!("srptr: {=u32}", src_ptr as u32); | ||
| 134 | break; | ||
| 135 | } | ||
| 136 | } | ||
| 137 | |||
| 138 | defmt::info!("First half transferred."); | ||
| 139 | defmt::info!("Triggering second half transfer..."); | ||
| 140 | |||
| 141 | // Trigger second transfer (second half: SRC[4..8] -> DST[4..8]) | ||
| 142 | // TCD1 should have been loaded by the scatter/gather engine. | ||
| 143 | unsafe { | ||
| 144 | dma_ch0.trigger_start(); | ||
| 145 | } | ||
| 146 | |||
| 147 | // Wait for second half | ||
| 148 | while !dma_ch0.is_done() { | ||
| 149 | cortex_m::asm::nop(); | ||
| 150 | } | ||
| 151 | |||
| 152 | defmt::info!("Second half transferred."); | ||
| 153 | |||
| 154 | defmt::info!("EDMA scatter-gather transfer example finish."); | ||
| 155 | defmt::info!("Destination Buffer (after): {=[?]}", dst.as_slice()); | ||
| 156 | |||
| 157 | // Verify: DST should match SRC | ||
| 158 | let mismatch = src != dst; | ||
| 159 | |||
| 160 | if mismatch { | ||
| 161 | defmt::error!("FAIL: Mismatch detected!"); | ||
| 162 | } else { | ||
| 163 | defmt::info!("PASS: Data verified."); | ||
| 164 | } | ||
| 165 | } | ||
