aboutsummaryrefslogtreecommitdiff
path: root/examples/mcxa/src/bin/dma_ping_pong_transfer.rs
diff options
context:
space:
mode:
authorJames Munns <[email protected]>2025-12-05 14:28:47 +0100
committerJames Munns <[email protected]>2025-12-05 14:28:47 +0100
commitb252db845e19603faf528cf93fe0c44757a27430 (patch)
tree99e646d17bed747df244dd607a15f5a67baa530a /examples/mcxa/src/bin/dma_ping_pong_transfer.rs
parent6a1eed83b9df8ffa81b93860f530f5bb3252d996 (diff)
Move
Diffstat (limited to 'examples/mcxa/src/bin/dma_ping_pong_transfer.rs')
-rw-r--r--examples/mcxa/src/bin/dma_ping_pong_transfer.rs376
1 files changed, 376 insertions, 0 deletions
diff --git a/examples/mcxa/src/bin/dma_ping_pong_transfer.rs b/examples/mcxa/src/bin/dma_ping_pong_transfer.rs
new file mode 100644
index 000000000..f8f543382
--- /dev/null
+++ b/examples/mcxa/src/bin/dma_ping_pong_transfer.rs
@@ -0,0 +1,376 @@
1//! DMA ping-pong/double-buffer transfer example for MCXA276.
2//!
3//! This example demonstrates two approaches for ping-pong/double-buffering:
4//!
5//! ## Approach 1: Scatter/Gather with linked TCDs (manual)
6//! - Two TCDs link to each other for alternating transfers
7//! - Uses custom handler that delegates to on_interrupt() then signals completion
8//! - Note: With ESG=1, DONE bit is cleared by hardware when next TCD loads,
9//! so we need an AtomicBool to track completion
10//!
11//! ## Approach 2: Half-transfer interrupt with wait_half() (NEW!)
12//! - Single continuous transfer over entire buffer
13//! - Uses half-transfer interrupt to know when first half is ready
14//! - Application can process first half while second half is being filled
15//!
16//! # Embassy-style features demonstrated:
17//! - `DmaChannel::new()` for channel creation
18//! - Scatter/gather with linked TCDs
19//! - Custom handler that delegates to HAL's `on_interrupt()` (best practice)
20//! - Standard `DmaCh1InterruptHandler` with `bind_interrupts!` macro
21//! - NEW: `wait_half()` for half-transfer interrupt handling
22
23#![no_std]
24#![no_main]
25
26use core::sync::atomic::{AtomicBool, Ordering};
27
28use embassy_executor::Spawner;
29use embassy_mcxa::clocks::config::Div8;
30use embassy_mcxa::dma::{self, DmaCh1InterruptHandler, DmaChannel, Tcd, TransferOptions};
31use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx};
32use embassy_mcxa::{bind_interrupts, pac};
33use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _};
34
35// Source and destination buffers for Approach 1 (scatter/gather)
36static mut SRC: [u32; 8] = [1, 2, 3, 4, 5, 6, 7, 8];
37static mut DST: [u32; 8] = [0; 8];
38
39// Source and destination buffers for Approach 2 (wait_half)
40static mut SRC2: [u32; 8] = [0xA1, 0xA2, 0xA3, 0xA4, 0xB1, 0xB2, 0xB3, 0xB4];
41static mut DST2: [u32; 8] = [0; 8];
42
43// TCD pool for scatter/gather - must be 32-byte aligned
44#[repr(C, align(32))]
45struct TcdPool([Tcd; 2]);
46
47static mut TCD_POOL: TcdPool = TcdPool(
48 [Tcd {
49 saddr: 0,
50 soff: 0,
51 attr: 0,
52 nbytes: 0,
53 slast: 0,
54 daddr: 0,
55 doff: 0,
56 citer: 0,
57 dlast_sga: 0,
58 csr: 0,
59 biter: 0,
60 }; 2],
61);
62
63// AtomicBool to track scatter/gather completion
64// Note: With ESG=1, DONE bit is cleared by hardware when next TCD loads,
65// so we need this flag to detect when each transfer completes
66static TRANSFER_DONE: AtomicBool = AtomicBool::new(false);
67
68// Custom handler for scatter/gather that delegates to HAL's on_interrupt()
69// This follows the "interrupts as threads" pattern - the handler does minimal work
70// (delegates to HAL + sets a flag) and the main task does the actual processing
71pub struct PingPongDmaHandler;
72
73impl embassy_mcxa::interrupt::typelevel::Handler<embassy_mcxa::interrupt::typelevel::DMA_CH0> for PingPongDmaHandler {
74 unsafe fn on_interrupt() {
75 // Delegate to HAL's on_interrupt() which clears INT flag and wakes wakers
76 dma::on_interrupt(0);
77 // Signal completion for polling (needed because ESG clears DONE bit)
78 TRANSFER_DONE.store(true, Ordering::Release);
79 }
80}
81
82// Bind DMA channel interrupts
83// CH0: Custom handler for scatter/gather (delegates to on_interrupt + sets flag)
84// CH1: Standard handler for wait_half() demo
85bind_interrupts!(struct Irqs {
86 DMA_CH0 => PingPongDmaHandler;
87 DMA_CH1 => DmaCh1InterruptHandler;
88});
89
90/// Helper to write a u32 as decimal ASCII to UART
91fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) {
92 let mut buf = [0u8; 10];
93 let mut n = val;
94 let mut i = buf.len();
95
96 if n == 0 {
97 tx.blocking_write(b"0").ok();
98 return;
99 }
100
101 while n > 0 {
102 i -= 1;
103 buf[i] = b'0' + (n % 10) as u8;
104 n /= 10;
105 }
106
107 tx.blocking_write(&buf[i..]).ok();
108}
109
110/// Helper to print a buffer to UART
111fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) {
112 tx.blocking_write(b"[").ok();
113 unsafe {
114 for i in 0..len {
115 write_u32(tx, *buf_ptr.add(i));
116 if i < len - 1 {
117 tx.blocking_write(b", ").ok();
118 }
119 }
120 }
121 tx.blocking_write(b"]").ok();
122}
123
124#[embassy_executor::main]
125async fn main(_spawner: Spawner) {
126 // Small delay to allow probe-rs to attach after reset
127 for _ in 0..100_000 {
128 cortex_m::asm::nop();
129 }
130
131 let mut cfg = hal::config::Config::default();
132 cfg.clock_cfg.sirc.fro_12m_enabled = true;
133 cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div());
134 let p = hal::init(cfg);
135
136 defmt::info!("DMA ping-pong transfer example starting...");
137
138 // Enable DMA interrupt (DMA clock/reset/init is handled automatically by HAL)
139 unsafe {
140 cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0);
141 }
142
143 let config = Config {
144 baudrate_bps: 115_200,
145 ..Default::default()
146 };
147
148 let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap();
149 let (mut tx, _rx) = lpuart.split();
150
151 tx.blocking_write(b"EDMA ping-pong transfer example begin.\r\n\r\n")
152 .unwrap();
153
154 // Initialize buffers
155 unsafe {
156 SRC = [1, 2, 3, 4, 5, 6, 7, 8];
157 DST = [0; 8];
158 }
159
160 tx.blocking_write(b"Source Buffer: ").unwrap();
161 print_buffer(&mut tx, core::ptr::addr_of!(SRC) as *const u32, 8);
162 tx.blocking_write(b"\r\n").unwrap();
163
164 tx.blocking_write(b"Destination Buffer (before): ").unwrap();
165 print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8);
166 tx.blocking_write(b"\r\n").unwrap();
167
168 tx.blocking_write(b"Configuring ping-pong DMA with Embassy-style API...\r\n")
169 .unwrap();
170
171 let dma_ch0 = DmaChannel::new(p.DMA_CH0);
172
173 // Configure ping-pong transfer using direct TCD access:
174 // This sets up TCD0 and TCD1 in RAM, and loads TCD0 into the channel.
175 // TCD0 transfers first half (SRC[0..4] -> DST[0..4]), links to TCD1.
176 // TCD1 transfers second half (SRC[4..8] -> DST[4..8]), links to TCD0.
177 unsafe {
178 let tcds = &mut *core::ptr::addr_of_mut!(TCD_POOL.0);
179 let src_ptr = core::ptr::addr_of!(SRC) as *const u32;
180 let dst_ptr = core::ptr::addr_of_mut!(DST) as *mut u32;
181
182 let half_len = 4usize;
183 let half_bytes = (half_len * 4) as u32;
184
185 let tcd0_addr = &tcds[0] as *const _ as u32;
186 let tcd1_addr = &tcds[1] as *const _ as u32;
187
188 // TCD0: First half -> Links to TCD1
189 tcds[0] = Tcd {
190 saddr: src_ptr as u32,
191 soff: 4,
192 attr: 0x0202, // 32-bit src/dst
193 nbytes: half_bytes,
194 slast: 0,
195 daddr: dst_ptr as u32,
196 doff: 4,
197 citer: 1,
198 dlast_sga: tcd1_addr as i32,
199 csr: 0x0012, // ESG | INTMAJOR
200 biter: 1,
201 };
202
203 // TCD1: Second half -> Links to TCD0
204 tcds[1] = Tcd {
205 saddr: src_ptr.add(half_len) as u32,
206 soff: 4,
207 attr: 0x0202,
208 nbytes: half_bytes,
209 slast: 0,
210 daddr: dst_ptr.add(half_len) as u32,
211 doff: 4,
212 citer: 1,
213 dlast_sga: tcd0_addr as i32,
214 csr: 0x0012,
215 biter: 1,
216 };
217
218 // Load TCD0 into hardware registers
219 dma_ch0.load_tcd(&tcds[0]);
220 }
221
222 tx.blocking_write(b"Triggering first half transfer...\r\n").unwrap();
223
224 // Trigger first transfer (first half: SRC[0..4] -> DST[0..4])
225 unsafe {
226 dma_ch0.trigger_start();
227 }
228
229 // Wait for first half
230 while !TRANSFER_DONE.load(Ordering::Acquire) {
231 cortex_m::asm::nop();
232 }
233 TRANSFER_DONE.store(false, Ordering::Release);
234
235 tx.blocking_write(b"First half transferred.\r\n").unwrap();
236 tx.blocking_write(b"Triggering second half transfer...\r\n").unwrap();
237
238 // Trigger second transfer (second half: SRC[4..8] -> DST[4..8])
239 unsafe {
240 dma_ch0.trigger_start();
241 }
242
243 // Wait for second half
244 while !TRANSFER_DONE.load(Ordering::Acquire) {
245 cortex_m::asm::nop();
246 }
247 TRANSFER_DONE.store(false, Ordering::Release);
248
249 tx.blocking_write(b"Second half transferred.\r\n\r\n").unwrap();
250
251 tx.blocking_write(b"EDMA ping-pong transfer example finish.\r\n\r\n")
252 .unwrap();
253 tx.blocking_write(b"Destination Buffer (after): ").unwrap();
254 print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8);
255 tx.blocking_write(b"\r\n\r\n").unwrap();
256
257 // Verify: DST should match SRC
258 let mut mismatch = false;
259 unsafe {
260 let src_ptr = core::ptr::addr_of!(SRC) as *const u32;
261 let dst_ptr = core::ptr::addr_of!(DST) as *const u32;
262 for i in 0..8 {
263 if *src_ptr.add(i) != *dst_ptr.add(i) {
264 mismatch = true;
265 break;
266 }
267 }
268 }
269
270 if mismatch {
271 tx.blocking_write(b"FAIL: Approach 1 mismatch detected!\r\n").unwrap();
272 defmt::error!("FAIL: Approach 1 mismatch detected!");
273 } else {
274 tx.blocking_write(b"PASS: Approach 1 data verified.\r\n\r\n").unwrap();
275 defmt::info!("PASS: Approach 1 data verified.");
276 }
277
278 // =========================================================================
279 // Approach 2: Half-Transfer Interrupt with wait_half() (NEW!)
280 // =========================================================================
281 //
282 // This approach uses a single continuous DMA transfer with half-transfer
283 // interrupt enabled. The wait_half() method allows you to be notified
284 // when the first half of the buffer is complete, so you can process it
285 // while the second half is still being filled.
286 //
287 // Benefits:
288 // - Simpler setup (no TCD pool needed)
289 // - True async/await support
290 // - Good for streaming data processing
291
292 tx.blocking_write(b"--- Approach 2: wait_half() demo ---\r\n\r\n")
293 .unwrap();
294
295 // Enable DMA CH1 interrupt
296 unsafe {
297 cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH1);
298 }
299
300 // Initialize approach 2 buffers
301 unsafe {
302 SRC2 = [0xA1, 0xA2, 0xA3, 0xA4, 0xB1, 0xB2, 0xB3, 0xB4];
303 DST2 = [0; 8];
304 }
305
306 tx.blocking_write(b"SRC2: ").unwrap();
307 print_buffer(&mut tx, core::ptr::addr_of!(SRC2) as *const u32, 8);
308 tx.blocking_write(b"\r\n").unwrap();
309
310 let dma_ch1 = DmaChannel::new(p.DMA_CH1);
311
312 // Configure transfer with half-transfer interrupt enabled
313 let mut options = TransferOptions::default();
314 options.half_transfer_interrupt = true; // Enable half-transfer interrupt
315 options.complete_transfer_interrupt = true;
316
317 tx.blocking_write(b"Starting transfer with half_transfer_interrupt...\r\n")
318 .unwrap();
319
320 unsafe {
321 let src = &*core::ptr::addr_of!(SRC2);
322 let dst = &mut *core::ptr::addr_of_mut!(DST2);
323
324 // Create the transfer
325 let mut transfer = dma_ch1.mem_to_mem(src, dst, options);
326
327 // Wait for half-transfer (first 4 elements)
328 tx.blocking_write(b"Waiting for first half...\r\n").unwrap();
329 let half_ok = transfer.wait_half().await;
330
331 if half_ok {
332 tx.blocking_write(b"Half-transfer complete! First half of DST2: ")
333 .unwrap();
334 print_buffer(&mut tx, core::ptr::addr_of!(DST2) as *const u32, 4);
335 tx.blocking_write(b"\r\n").unwrap();
336 tx.blocking_write(b"(Processing first half while second half transfers...)\r\n")
337 .unwrap();
338 }
339
340 // Wait for complete transfer
341 tx.blocking_write(b"Waiting for second half...\r\n").unwrap();
342 transfer.await;
343 }
344
345 tx.blocking_write(b"Transfer complete! Full DST2: ").unwrap();
346 print_buffer(&mut tx, core::ptr::addr_of!(DST2) as *const u32, 8);
347 tx.blocking_write(b"\r\n\r\n").unwrap();
348
349 // Verify approach 2
350 let mut mismatch2 = false;
351 unsafe {
352 let src_ptr = core::ptr::addr_of!(SRC2) as *const u32;
353 let dst_ptr = core::ptr::addr_of!(DST2) as *const u32;
354 for i in 0..8 {
355 if *src_ptr.add(i) != *dst_ptr.add(i) {
356 mismatch2 = true;
357 break;
358 }
359 }
360 }
361
362 if mismatch2 {
363 tx.blocking_write(b"FAIL: Approach 2 mismatch!\r\n").unwrap();
364 defmt::error!("FAIL: Approach 2 mismatch!");
365 } else {
366 tx.blocking_write(b"PASS: Approach 2 verified.\r\n").unwrap();
367 defmt::info!("PASS: Approach 2 verified.");
368 }
369
370 tx.blocking_write(b"\r\n=== All ping-pong demos complete ===\r\n")
371 .unwrap();
372
373 loop {
374 cortex_m::asm::wfe();
375 }
376}