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