aboutsummaryrefslogtreecommitdiff
path: root/examples/mcxa/src/bin/raw_dma_ping_pong_transfer.rs
diff options
context:
space:
mode:
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.rs244
1 files changed, 244 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..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}