aboutsummaryrefslogtreecommitdiff
path: root/examples/mcxa/src/bin/raw_dma_ping_pong_transfer.rs
diff options
context:
space:
mode:
authorJames Munns <[email protected]>2025-12-09 15:52:12 +0100
committerJames Munns <[email protected]>2025-12-09 15:52:12 +0100
commite962f5568a9f6433dcd6ad3e41d3faabb8b7c552 (patch)
tree0bcf67cbd0d53039580042b856e1b9dd58a528aa /examples/mcxa/src/bin/raw_dma_ping_pong_transfer.rs
parent4386b39e2516c453966d894b4fd265fae9d82c1a (diff)
Clean up remaining examples, move some to "raw" examples
Diffstat (limited to 'examples/mcxa/src/bin/raw_dma_ping_pong_transfer.rs')
-rw-r--r--examples/mcxa/src/bin/raw_dma_ping_pong_transfer.rs269
1 files changed, 269 insertions, 0 deletions
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..4a64b2498
--- /dev/null
+++ b/examples/mcxa/src/bin/raw_dma_ping_pong_transfer.rs
@@ -0,0 +1,269 @@
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 core::sync::atomic::{AtomicBool, Ordering};
31
32use embassy_executor::Spawner;
33use embassy_mcxa::clocks::config::Div8;
34use embassy_mcxa::dma::{self, DmaChannel, Tcd, TransferOptions};
35use embassy_mcxa::{bind_interrupts, pac};
36use static_cell::ConstStaticCell;
37use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _};
38
39// Source and destination buffers for Approach 1 (scatter/gather)
40static SRC: ConstStaticCell<[u32; 8]> = ConstStaticCell::new([1, 2, 3, 4, 5, 6, 7, 8]);
41static DST: ConstStaticCell<[u32; 8]> = ConstStaticCell::new([0; 8]);
42
43// Source and destination buffers for Approach 2 (wait_half)
44static SRC2: ConstStaticCell<[u32; 8]> = ConstStaticCell::new([0xA1, 0xA2, 0xA3, 0xA4, 0xB1, 0xB2, 0xB3, 0xB4]);
45static DST2: ConstStaticCell<[u32; 8]> = ConstStaticCell::new([0; 8]);
46
47// TCD pool for scatter/gather - must be 32-byte aligned
48#[repr(C, align(32))]
49struct TcdPool([Tcd; 2]);
50
51static TCD_POOL: ConstStaticCell<TcdPool> = ConstStaticCell::new(TcdPool(
52 [Tcd {
53 saddr: 0,
54 soff: 0,
55 attr: 0,
56 nbytes: 0,
57 slast: 0,
58 daddr: 0,
59 doff: 0,
60 citer: 0,
61 dlast_sga: 0,
62 csr: 0,
63 biter: 0,
64 }; 2],
65));
66
67// AtomicBool to track scatter/gather completion
68// Note: With ESG=1, DONE bit is cleared by hardware when next TCD loads,
69// so we need this flag to detect when each transfer completes
70static TRANSFER_DONE: AtomicBool = AtomicBool::new(false);
71
72// Custom handler for scatter/gather that delegates to HAL's on_interrupt()
73// This follows the "interrupts as threads" pattern - the handler does minimal work
74// (delegates to HAL + sets a flag) and the main task does the actual processing
75pub struct PingPongDmaHandler;
76
77impl embassy_mcxa::interrupt::typelevel::Handler<embassy_mcxa::interrupt::typelevel::DMA_CH0> for PingPongDmaHandler {
78 unsafe fn on_interrupt() {
79 // Delegate to HAL's on_interrupt() which clears INT flag and wakes wakers
80 dma::on_interrupt(0);
81 // Signal completion for polling (needed because ESG clears DONE bit)
82 TRANSFER_DONE.store(true, Ordering::Release);
83 }
84}
85
86// Bind DMA channel interrupts
87// CH0: Custom handler for scatter/gather (delegates to on_interrupt + sets flag)
88// CH1: Standard handler for wait_half() demo
89bind_interrupts!(struct Irqs {
90 DMA_CH0 => PingPongDmaHandler;
91});
92
93#[embassy_executor::main]
94async fn main(_spawner: Spawner) {
95 // Small delay to allow probe-rs to attach after reset
96 for _ in 0..100_000 {
97 cortex_m::asm::nop();
98 }
99
100 let mut cfg = hal::config::Config::default();
101 cfg.clock_cfg.sirc.fro_12m_enabled = true;
102 cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div());
103 let p = hal::init(cfg);
104
105 defmt::info!("DMA ping-pong transfer example starting...");
106
107 defmt::info!("EDMA ping-pong transfer example begin.");
108
109 // Initialize buffers
110 let src = SRC.take();
111 let dst = DST.take();
112
113 defmt::info!("Source Buffer: {=[?]}", src.as_slice());
114 defmt::info!("Destination Buffer (before): {=[?]}", dst.as_slice());
115
116 defmt::info!("Configuring ping-pong DMA with Embassy-style API...");
117
118 let dma_ch0 = DmaChannel::new(p.DMA_CH0);
119
120 // Configure ping-pong transfer using direct TCD access:
121 // This sets up TCD0 and TCD1 in RAM, and loads TCD0 into the channel.
122 // TCD0 transfers first half (SRC[0..4] -> DST[0..4]), links to TCD1.
123 // TCD1 transfers second half (SRC[4..8] -> DST[4..8]), links to TCD0.
124 unsafe {
125 let tcds = &mut TCD_POOL.take().0;
126
127 let half_len = 4usize;
128 let half_bytes = (half_len * 4) as u32;
129
130 let tcd0_addr = &tcds[0] as *const _ as u32;
131 let tcd1_addr = &tcds[1] as *const _ as u32;
132
133 // TCD0: First half -> Links to TCD1
134 tcds[0] = Tcd {
135 saddr: src.as_ptr() as u32,
136 soff: 4,
137 attr: 0x0202, // 32-bit src/dst
138 nbytes: half_bytes,
139 slast: 0,
140 daddr: dst.as_mut_ptr() as u32,
141 doff: 4,
142 citer: 1,
143 dlast_sga: tcd1_addr as i32,
144 csr: 0x0012, // ESG | INTMAJOR
145 biter: 1,
146 };
147
148 // TCD1: Second half -> Links to TCD0
149 tcds[1] = Tcd {
150 saddr: src.as_ptr().add(half_len) as u32,
151 soff: 4,
152 attr: 0x0202,
153 nbytes: half_bytes,
154 slast: 0,
155 daddr: dst.as_mut_ptr().add(half_len) as u32,
156 doff: 4,
157 citer: 1,
158 dlast_sga: tcd0_addr as i32,
159 csr: 0x0012,
160 biter: 1,
161 };
162
163 // Load TCD0 into hardware registers
164 dma_ch0.load_tcd(&tcds[0]);
165 }
166
167 defmt::info!("Triggering first half transfer...");
168
169 // Trigger first transfer (first half: SRC[0..4] -> DST[0..4])
170 unsafe {
171 dma_ch0.trigger_start();
172 }
173
174 // Wait for first half
175 while !TRANSFER_DONE.load(Ordering::Acquire) {
176 cortex_m::asm::nop();
177 }
178 TRANSFER_DONE.store(false, Ordering::Release);
179
180 defmt::info!("First half transferred.");
181 defmt::info!("Triggering second half transfer...");
182
183 // Trigger second transfer (second half: SRC[4..8] -> DST[4..8])
184 unsafe {
185 dma_ch0.trigger_start();
186 }
187
188 // Wait for second half
189 while !TRANSFER_DONE.load(Ordering::Acquire) {
190 cortex_m::asm::nop();
191 }
192 TRANSFER_DONE.store(false, Ordering::Release);
193
194 defmt::info!("Second half transferred.");
195
196 defmt::info!("EDMA ping-pong transfer example finish.");
197 defmt::info!("Destination Buffer (after): {=[?]}", dst.as_slice());
198
199 // Verify: DST should match SRC
200 let mismatch = src != dst;
201
202 if mismatch {
203 defmt::error!("FAIL: Approach 1 mismatch detected!");
204 } else {
205 defmt::info!("PASS: Approach 1 data verified.");
206 }
207
208 // =========================================================================
209 // Approach 2: Half-Transfer Interrupt with wait_half() (NEW!)
210 // =========================================================================
211 //
212 // This approach uses a single continuous DMA transfer with half-transfer
213 // interrupt enabled. The wait_half() method allows you to be notified
214 // when the first half of the buffer is complete, so you can process it
215 // while the second half is still being filled.
216 //
217 // Benefits:
218 // - Simpler setup (no TCD pool needed)
219 // - True async/await support
220 // - Good for streaming data processing
221
222 defmt::info!("--- Approach 2: wait_half() demo ---");
223
224 // Enable DMA CH1 interrupt
225 unsafe {
226 cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH1);
227 }
228
229 // Initialize approach 2 buffers
230 let src2 = SRC2.take();
231 let dst2 = DST2.take();
232
233 defmt::info!("SRC2: {=[?]}", src2.as_slice());
234
235 let dma_ch1 = DmaChannel::new(p.DMA_CH1);
236
237 // Configure transfer with half-transfer interrupt enabled
238 let mut options = TransferOptions::default();
239 options.half_transfer_interrupt = true; // Enable half-transfer interrupt
240 options.complete_transfer_interrupt = true;
241
242 defmt::info!("Starting transfer with half_transfer_interrupt...");
243
244 // Create the transfer
245 let mut transfer = dma_ch1.mem_to_mem(src2, dst2, options);
246
247 // Wait for half-transfer (first 4 elements)
248 defmt::info!("Waiting for first half...");
249 let _ok = transfer.wait_half().await;
250
251 defmt::info!("Half-transfer complete!");
252
253 // Wait for complete transfer
254 defmt::info!("Waiting for second half...");
255 transfer.await;
256
257 defmt::info!("Transfer complete! Full DST2: {=[?]}", dst2.as_slice());
258
259 // Verify approach 2
260 let mismatch2 = src2 != dst2;
261
262 if mismatch2 {
263 defmt::error!("FAIL: Approach 2 mismatch!");
264 } else {
265 defmt::info!("PASS: Approach 2 verified.");
266 }
267
268 defmt::info!("=== All ping-pong demos complete ===");
269}