aboutsummaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
authorJames Munns <[email protected]>2025-12-09 17:40:22 +0000
committerGitHub <[email protected]>2025-12-09 17:40:22 +0000
commit23623d634b88da7bc398f092ac4ab9e571c6e6e1 (patch)
treed631103d213340780c6bd4e7b8925df491ad4a1d /examples
parent4f322f4a03d336e90d530045255f46cce93e6252 (diff)
parent2d7328d5839e196f7b6c275283a50fd4ac019440 (diff)
Merge pull request #5015 from jamesmunns/james/dma-suggestions
[MCXA]: Extend DMA interface
Diffstat (limited to 'examples')
-rw-r--r--examples/mcxa/Cargo.toml1
-rw-r--r--examples/mcxa/src/bin/dma_mem_to_mem.rs118
-rw-r--r--examples/mcxa/src/bin/dma_scatter_gather_builder.rs130
-rw-r--r--examples/mcxa/src/bin/dma_wrap_transfer.rs184
-rw-r--r--examples/mcxa/src/bin/lpuart_dma.rs68
-rw-r--r--examples/mcxa/src/bin/lpuart_ring_buffer.rs115
-rw-r--r--examples/mcxa/src/bin/raw_dma_channel_link.rs278
-rw-r--r--examples/mcxa/src/bin/raw_dma_interleave_transfer.rs141
-rw-r--r--examples/mcxa/src/bin/raw_dma_memset.rs129
-rw-r--r--examples/mcxa/src/bin/raw_dma_ping_pong_transfer.rs244
-rw-r--r--examples/mcxa/src/bin/raw_dma_scatter_gather.rs165
11 files changed, 1573 insertions, 0 deletions
diff --git a/examples/mcxa/Cargo.toml b/examples/mcxa/Cargo.toml
index 19d8d8657..d07cc4272 100644
--- a/examples/mcxa/Cargo.toml
+++ b/examples/mcxa/Cargo.toml
@@ -21,6 +21,7 @@ embassy-time-driver = "0.2.1"
21embedded-io-async = "0.6.1" 21embedded-io-async = "0.6.1"
22heapless = "0.9.2" 22heapless = "0.9.2"
23panic-probe = { version = "1.0", features = ["print-defmt"] } 23panic-probe = { version = "1.0", features = ["print-defmt"] }
24static_cell = "2.1.1"
24tmp108 = "0.4.0" 25tmp108 = "0.4.0"
25 26
26[profile.release] 27[profile.release]
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
16use embassy_executor::Spawner;
17use embassy_mcxa::clocks::config::Div8;
18use embassy_mcxa::dma::{DmaChannel, TransferOptions};
19use static_cell::ConstStaticCell;
20use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _};
21
22const BUFFER_LENGTH: usize = 4;
23
24// Buffers in RAM (static mut is automatically placed in .bss/.data)
25static SRC_BUFFER: ConstStaticCell<[u32; BUFFER_LENGTH]> = ConstStaticCell::new([1, 2, 3, 4]);
26static DEST_BUFFER: ConstStaticCell<[u32; BUFFER_LENGTH]> = ConstStaticCell::new([0; BUFFER_LENGTH]);
27static MEMSET_BUFFER: ConstStaticCell<[u32; BUFFER_LENGTH]> = ConstStaticCell::new([0; BUFFER_LENGTH]);
28
29#[embassy_executor::main]
30async 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
23use embassy_executor::Spawner;
24use embassy_mcxa::clocks::config::Div8;
25use embassy_mcxa::dma::{DmaChannel, ScatterGatherBuilder};
26use static_cell::ConstStaticCell;
27use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _};
28
29// Source buffers (multiple segments)
30static SRC1: ConstStaticCell<[u32; 4]> = ConstStaticCell::new([0x11111111, 0x22222222, 0x33333333, 0x44444444]);
31static SRC2: ConstStaticCell<[u32; 4]> = ConstStaticCell::new([0xAAAAAAAA, 0xBBBBBBBB, 0xCCCCCCCC, 0xDDDDDDDD]);
32static SRC3: ConstStaticCell<[u32; 4]> = ConstStaticCell::new([0x12345678, 0x9ABCDEF0, 0xFEDCBA98, 0x76543210]);
33
34// Destination buffers (one per segment)
35static DST1: ConstStaticCell<[u32; 4]> = ConstStaticCell::new([0; 4]);
36static DST2: ConstStaticCell<[u32; 4]> = ConstStaticCell::new([0; 4]);
37static DST3: ConstStaticCell<[u32; 4]> = ConstStaticCell::new([0; 4]);
38
39#[embassy_executor::main]
40async 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
13use core::fmt::Write as _;
14
15use embassy_executor::Spawner;
16use embassy_mcxa::clocks::config::Div8;
17use embassy_mcxa::dma::DmaChannel;
18use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx};
19use {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))]
23struct AlignedSrc([u32; 4]);
24
25static mut SRC: AlignedSrc = AlignedSrc([0; 4]);
26static mut DST: [u32; 8] = [0; 8];
27
28/// Helper to print a buffer to UART
29fn 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]
34async 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
13use embassy_executor::Spawner;
14use embassy_mcxa::clocks::config::Div8;
15use embassy_mcxa::lpuart::{Config, LpuartDma};
16use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _};
17
18#[embassy_executor::main]
19async 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
21use embassy_executor::Spawner;
22use embassy_mcxa::clocks::config::Div8;
23use embassy_mcxa::lpuart::{Config, LpuartDma, LpuartTxDma};
24use static_cell::ConstStaticCell;
25use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _};
26
27// Ring buffer for RX - power of 2 is ideal for modulo efficiency
28static RX_RING_BUFFER: ConstStaticCell<[u8; 64]> = ConstStaticCell::new([0; 64]);
29
30/// Helper to write a byte as hex to UART
31fn 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]
41async 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
22use embassy_executor::Spawner;
23use embassy_mcxa::clocks::config::Div8;
24use embassy_mcxa::dma::DmaChannel;
25use embassy_mcxa::pac;
26use static_cell::ConstStaticCell;
27use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _};
28
29// Buffers
30static SRC_BUFFER: ConstStaticCell<[u32; 4]> = ConstStaticCell::new([1, 2, 3, 4]);
31static DEST_BUFFER0: ConstStaticCell<[u32; 4]> = ConstStaticCell::new([0; 4]);
32static DEST_BUFFER1: ConstStaticCell<[u32; 4]> = ConstStaticCell::new([0; 4]);
33static DEST_BUFFER2: ConstStaticCell<[u32; 4]> = ConstStaticCell::new([0; 4]);
34
35#[embassy_executor::main]
36async 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
17use embassy_executor::Spawner;
18use embassy_mcxa::clocks::config::Div8;
19use embassy_mcxa::dma::DmaChannel;
20use static_cell::ConstStaticCell;
21use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _};
22
23const BUFFER_LENGTH: usize = 16;
24const HALF_BUFF_LENGTH: usize = BUFFER_LENGTH / 2;
25
26// Buffers in RAM
27static SRC_BUFFER: ConstStaticCell<[u32; HALF_BUFF_LENGTH]> = ConstStaticCell::new([0; HALF_BUFF_LENGTH]);
28static DEST_BUFFER: ConstStaticCell<[u32; BUFFER_LENGTH]> = ConstStaticCell::new([0; BUFFER_LENGTH]);
29
30#[embassy_executor::main]
31async 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
17use embassy_executor::Spawner;
18use embassy_mcxa::clocks::config::Div8;
19use embassy_mcxa::dma::DmaChannel;
20use static_cell::ConstStaticCell;
21use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _};
22
23const BUFFER_LENGTH: usize = 4;
24
25// Buffers in RAM
26static PATTERN: u32 = 0xDEADBEEF;
27static DEST_BUFFER: ConstStaticCell<[u32; BUFFER_LENGTH]> = ConstStaticCell::new([0; BUFFER_LENGTH]);
28
29#[embassy_executor::main]
30async 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
30use embassy_executor::Spawner;
31use embassy_mcxa::clocks::config::Div8;
32use embassy_mcxa::dma::{DmaChannel, Tcd, TransferOptions};
33use embassy_mcxa::pac;
34use static_cell::ConstStaticCell;
35use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _};
36
37// Source and destination buffers for Approach 1 (scatter/gather)
38static SRC: ConstStaticCell<[u32; 8]> = ConstStaticCell::new([1, 2, 3, 4, 5, 6, 7, 8]);
39static DST: ConstStaticCell<[u32; 8]> = ConstStaticCell::new([0; 8]);
40
41// Source and destination buffers for Approach 2 (wait_half)
42static SRC2: ConstStaticCell<[u32; 8]> = ConstStaticCell::new([0xA1, 0xA2, 0xA3, 0xA4, 0xB1, 0xB2, 0xB3, 0xB4]);
43static 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))]
47struct TcdPool([Tcd; 2]);
48
49static 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]
66async 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
19use embassy_executor::Spawner;
20use embassy_mcxa::clocks::config::Div8;
21use embassy_mcxa::dma::{DmaChannel, Tcd};
22use static_cell::ConstStaticCell;
23use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _};
24
25// Source and destination buffers
26static SRC: ConstStaticCell<[u32; 12]> = ConstStaticCell::new([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
27static 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))]
31struct TcdPool([Tcd; 2]);
32
33static 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]
50async 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}