aboutsummaryrefslogtreecommitdiff
path: root/examples/src/bin/dma_wrap_transfer.rs
diff options
context:
space:
mode:
authorBogdan Petru Chircu Mare <[email protected]>2025-11-25 22:09:01 -0800
committerBogdan Petru Chircu Mare <[email protected]>2025-11-28 12:34:24 -0800
commit03356a261801d7ee234490809eef3eac3c27cc52 (patch)
tree23de784ea642f65ce5c02fdcb111ee314a4ca97f /examples/src/bin/dma_wrap_transfer.rs
parent87c4eaf3380505ca15ef7ed1d5dc435e9af2200e (diff)
feat(dma): add DMA driver with 10 verified examples
Initial DMA driver implementation for MCXA276 with: Core DMA Features: - DmaChannel type with ownership tracking via Channel trait - Transfer, RingBuffer, and ScatterGatherBuilder abstractions - Support for mem-to-mem, mem-to-peripheral, peripheral-to-mem transfers - Interrupt-driven completion with embassy async/await integration - Word size abstraction (u8, u16, u32) via Word trait LPUART DMA Integration: - LpuartTxDma and LpuartRxDma drivers for async UART with DMA - LpuartDma combined TX/RX driver - Automatic chunking for buffers > 0x7FFF bytes - DMA guards with Drop impl for safe cancellation 10 Verified Examples: - dma_mem2mem: Basic memory-to-memory copy - dma_memset: Memory fill with pattern - dma_uart_tx: UART transmit via DMA - dma_uart_rx: UART receive via DMA - dma_uart_loopback: Combined TX/RX loopback test - dma_scatter_gather: Linked descriptor chains - dma_channel_linking: Major/minor loop channel linking - dma_ring_buffer: Circular buffer for continuous streaming - dma_ping_pong: Double-buffering pattern - dma_software_trigger: Manual transfer triggering PR Feedback Addressed: - Use PAC accessor for LPUART DATA register instead of manual offset - Add EnableInterrupt enum to replace boolean parameter for readability - Add DMA guards with Drop impl for safe async cancellation - Automatic chunking for large buffers instead of returning error - Use NonNull<[W]> + PhantomData for RingBuffer (DMA acts like separate thread) - Remove edma parameter from all methods (single eDMA instance steals ptr internally) - Make edma_tcd() non-public (HAL should not expose PAC items)
Diffstat (limited to 'examples/src/bin/dma_wrap_transfer.rs')
-rw-r--r--examples/src/bin/dma_wrap_transfer.rs231
1 files changed, 231 insertions, 0 deletions
diff --git a/examples/src/bin/dma_wrap_transfer.rs b/examples/src/bin/dma_wrap_transfer.rs
new file mode 100644
index 000000000..b115a2c19
--- /dev/null
+++ b/examples/src/bin/dma_wrap_transfer.rs
@@ -0,0 +1,231 @@
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//! - `dma::edma_tcd()` accessor for simplified register access
8//! - `DmaChannel::is_done()` and `clear_done()` helper methods
9//! - No need to pass register block around
10
11#![no_std]
12#![no_main]
13
14use embassy_executor::Spawner;
15use embassy_mcxa::clocks::config::Div8;
16use embassy_mcxa::clocks::Gate;
17use embassy_mcxa::dma::{edma_tcd, DmaChannel, DmaCh0InterruptHandler};
18use embassy_mcxa::{bind_interrupts, dma};
19use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx};
20use embassy_mcxa::pac;
21use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _};
22
23// Bind DMA channel 0 interrupt using Embassy-style macro
24bind_interrupts!(struct Irqs {
25 DMA_CH0 => DmaCh0InterruptHandler;
26});
27
28// Source buffer: 4 words (16 bytes), aligned to 16 bytes for modulo
29#[repr(align(16))]
30struct AlignedSrc([u32; 4]);
31
32static mut SRC: AlignedSrc = AlignedSrc([0; 4]);
33static mut DST: [u32; 8] = [0; 8];
34
35/// Helper to write a u32 as decimal ASCII to UART
36fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) {
37 let mut buf = [0u8; 10];
38 let mut n = val;
39 let mut i = buf.len();
40
41 if n == 0 {
42 tx.blocking_write(b"0").ok();
43 return;
44 }
45
46 while n > 0 {
47 i -= 1;
48 buf[i] = b'0' + (n % 10) as u8;
49 n /= 10;
50 }
51
52 tx.blocking_write(&buf[i..]).ok();
53}
54
55/// Helper to print a buffer to UART
56fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) {
57 tx.blocking_write(b"[").ok();
58 unsafe {
59 for i in 0..len {
60 write_u32(tx, *buf_ptr.add(i));
61 if i < len - 1 {
62 tx.blocking_write(b", ").ok();
63 }
64 }
65 }
66 tx.blocking_write(b"]").ok();
67}
68
69#[embassy_executor::main]
70async fn main(_spawner: Spawner) {
71 // Small delay to allow probe-rs to attach after reset
72 for _ in 0..100_000 {
73 cortex_m::asm::nop();
74 }
75
76 let mut cfg = hal::config::Config::default();
77 cfg.clock_cfg.sirc.fro_12m_enabled = true;
78 cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div());
79 let p = hal::init(cfg);
80
81 defmt::info!("DMA wrap transfer example starting...");
82
83 // Enable DMA0 clock and release reset
84 unsafe {
85 hal::peripherals::DMA0::enable_clock();
86 hal::peripherals::DMA0::release_reset();
87 }
88
89 let pac_periphs = unsafe { pac::Peripherals::steal() };
90
91 unsafe {
92 dma::init(&pac_periphs);
93 }
94
95 // Enable DMA interrupt
96 unsafe {
97 cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0);
98 }
99
100 let config = Config {
101 baudrate_bps: 115_200,
102 enable_tx: true,
103 enable_rx: false,
104 ..Default::default()
105 };
106
107 let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap();
108 let (mut tx, _rx) = lpuart.split();
109
110 tx.blocking_write(b"EDMA wrap transfer example begin.\r\n\r\n")
111 .unwrap();
112
113 // Initialize buffers
114 unsafe {
115 SRC.0 = [1, 2, 3, 4];
116 DST = [0; 8];
117 }
118
119 tx.blocking_write(b"Source Buffer: ").unwrap();
120 print_buffer(&mut tx, unsafe { core::ptr::addr_of!(SRC.0) } as *const u32, 4);
121 tx.blocking_write(b"\r\n").unwrap();
122
123 tx.blocking_write(b"Destination Buffer (before): ").unwrap();
124 print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8);
125 tx.blocking_write(b"\r\n").unwrap();
126
127 tx.blocking_write(b"Configuring DMA with Embassy-style API...\r\n")
128 .unwrap();
129
130 // Create DMA channel using Embassy-style API
131 let dma_ch0 = DmaChannel::new(p.DMA_CH0);
132
133 // Use edma_tcd() accessor instead of passing register block around
134 let edma = edma_tcd();
135
136 // Configure wrap transfer using direct TCD access:
137 // SRC is 16 bytes (4 * u32). We want to transfer 32 bytes (8 * u32).
138 // SRC modulo is 16 bytes (2^4 = 16) - wraps source address.
139 // DST modulo is 0 (disabled).
140 // This causes the source address to wrap around after 16 bytes,
141 // effectively repeating the source data.
142 unsafe {
143 let t = edma.tcd(0);
144
145 // Reset channel state
146 t.ch_csr().write(|w| {
147 w.erq().disable()
148 .earq().disable()
149 .eei().no_error()
150 .ebw().disable()
151 .done().clear_bit_by_one()
152 });
153 t.ch_es().write(|w| w.bits(0));
154 t.ch_int().write(|w| w.int().clear_bit_by_one());
155
156 // Source/destination addresses
157 t.tcd_saddr().write(|w| w.saddr().bits(core::ptr::addr_of!(SRC.0) as u32));
158 t.tcd_daddr().write(|w| w.daddr().bits(core::ptr::addr_of_mut!(DST) as u32));
159
160 // Offsets: both increment by 4 bytes
161 t.tcd_soff().write(|w| w.soff().bits(4));
162 t.tcd_doff().write(|w| w.doff().bits(4));
163
164 // Attributes: 32-bit transfers (size = 2)
165 // SMOD = 4 (2^4 = 16 byte modulo for source), DMOD = 0 (disabled)
166 t.tcd_attr().write(|w| {
167 w.ssize().bits(2)
168 .dsize().bits(2)
169 .smod().bits(4) // Source modulo: 2^4 = 16 bytes
170 .dmod().bits(0) // Dest modulo: disabled
171 });
172
173 // Transfer 32 bytes total in one minor loop
174 let nbytes = 32u32;
175 t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(nbytes));
176
177 // Source wraps via modulo, no adjustment needed
178 t.tcd_slast_sda().write(|w| w.slast_sda().bits(0));
179 // Reset dest address after major loop
180 t.tcd_dlast_sga().write(|w| w.dlast_sga().bits(-(nbytes as i32) as u32));
181
182 // Major loop count = 1
183 t.tcd_biter_elinkno().write(|w| w.biter().bits(1));
184 t.tcd_citer_elinkno().write(|w| w.citer().bits(1));
185
186 // Enable interrupt on major loop completion
187 t.tcd_csr().write(|w| w.intmajor().set_bit());
188
189 cortex_m::asm::dsb();
190
191 tx.blocking_write(b"Triggering transfer...\r\n").unwrap();
192 dma_ch0.trigger_start(edma);
193 }
194
195 // Wait for completion using channel helper method
196 while !dma_ch0.is_done(edma) {
197 cortex_m::asm::nop();
198 }
199 unsafe { dma_ch0.clear_done(edma); }
200
201 tx.blocking_write(b"\r\nEDMA wrap transfer example finish.\r\n\r\n")
202 .unwrap();
203 tx.blocking_write(b"Destination Buffer (after): ").unwrap();
204 print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8);
205 tx.blocking_write(b"\r\n\r\n").unwrap();
206
207 // Verify: DST should be [1, 2, 3, 4, 1, 2, 3, 4]
208 let expected = [1u32, 2, 3, 4, 1, 2, 3, 4];
209 let mut mismatch = false;
210 unsafe {
211 for i in 0..8 {
212 if DST[i] != expected[i] {
213 mismatch = true;
214 break;
215 }
216 }
217 }
218
219 if mismatch {
220 tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap();
221 defmt::error!("FAIL: Mismatch detected!");
222 } else {
223 tx.blocking_write(b"PASS: Data verified.\r\n").unwrap();
224 defmt::info!("PASS: Data verified.");
225 }
226
227 loop {
228 cortex_m::asm::wfe();
229 }
230}
231