diff options
Diffstat (limited to 'examples/mcxa')
| -rw-r--r-- | examples/mcxa/src/bin/dma_channel_link.rs | 372 | ||||
| -rw-r--r-- | examples/mcxa/src/bin/dma_interleave_transfer.rs | 215 | ||||
| -rw-r--r-- | examples/mcxa/src/bin/dma_mem_to_mem.rs | 229 | ||||
| -rw-r--r-- | examples/mcxa/src/bin/dma_memset.rs | 218 | ||||
| -rw-r--r-- | examples/mcxa/src/bin/dma_ping_pong_transfer.rs | 376 | ||||
| -rw-r--r-- | examples/mcxa/src/bin/dma_scatter_gather.rs | 262 | ||||
| -rw-r--r-- | examples/mcxa/src/bin/dma_scatter_gather_builder.rs | 231 | ||||
| -rw-r--r-- | examples/mcxa/src/bin/dma_wrap_transfer.rs | 222 | ||||
| -rw-r--r-- | examples/mcxa/src/bin/lpuart_dma.rs | 81 | ||||
| -rw-r--r-- | examples/mcxa/src/bin/lpuart_ring_buffer.rs | 130 |
10 files changed, 2336 insertions, 0 deletions
diff --git a/examples/mcxa/src/bin/dma_channel_link.rs b/examples/mcxa/src/bin/dma_channel_link.rs new file mode 100644 index 000000000..92c7a9681 --- /dev/null +++ b/examples/mcxa/src/bin/dma_channel_link.rs | |||
| @@ -0,0 +1,372 @@ | |||
| 1 | //! DMA channel linking example for MCXA276. | ||
| 2 | //! | ||
| 3 | //! This example demonstrates DMA channel linking (minor and major loop linking): | ||
| 4 | //! - Channel 0: Transfers SRC_BUFFER to DEST_BUFFER0, with: | ||
| 5 | //! - Minor Link to Channel 1 (triggers CH1 after each minor loop) | ||
| 6 | //! - Major Link to Channel 2 (triggers CH2 after major loop completes) | ||
| 7 | //! - Channel 1: Transfers SRC_BUFFER to DEST_BUFFER1 (triggered by CH0 minor link) | ||
| 8 | //! - Channel 2: Transfers SRC_BUFFER to DEST_BUFFER2 (triggered by CH0 major link) | ||
| 9 | //! | ||
| 10 | //! # Embassy-style features demonstrated: | ||
| 11 | //! - `DmaChannel::new()` for channel creation | ||
| 12 | //! - `DmaChannel::is_done()` and `clear_done()` helper methods | ||
| 13 | //! - Channel linking with `set_minor_link()` and `set_major_link()` | ||
| 14 | //! - Standard `DmaCh*InterruptHandler` with `bind_interrupts!` macro | ||
| 15 | |||
| 16 | #![no_std] | ||
| 17 | #![no_main] | ||
| 18 | |||
| 19 | use embassy_executor::Spawner; | ||
| 20 | use embassy_mcxa::clocks::config::Div8; | ||
| 21 | use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaCh1InterruptHandler, DmaCh2InterruptHandler, DmaChannel}; | ||
| 22 | use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; | ||
| 23 | use embassy_mcxa::{bind_interrupts, pac}; | ||
| 24 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 25 | |||
| 26 | // Buffers | ||
| 27 | static mut SRC_BUFFER: [u32; 4] = [1, 2, 3, 4]; | ||
| 28 | static mut DEST_BUFFER0: [u32; 4] = [0; 4]; | ||
| 29 | static mut DEST_BUFFER1: [u32; 4] = [0; 4]; | ||
| 30 | static mut DEST_BUFFER2: [u32; 4] = [0; 4]; | ||
| 31 | |||
| 32 | // Bind DMA channel interrupts using Embassy-style macro | ||
| 33 | // The standard handlers call on_interrupt() which wakes wakers and clears flags | ||
| 34 | bind_interrupts!(struct Irqs { | ||
| 35 | DMA_CH0 => DmaCh0InterruptHandler; | ||
| 36 | DMA_CH1 => DmaCh1InterruptHandler; | ||
| 37 | DMA_CH2 => DmaCh2InterruptHandler; | ||
| 38 | }); | ||
| 39 | |||
| 40 | /// Helper to write a u32 as decimal ASCII to UART | ||
| 41 | fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { | ||
| 42 | let mut buf = [0u8; 10]; | ||
| 43 | let mut n = val; | ||
| 44 | let mut i = buf.len(); | ||
| 45 | |||
| 46 | if n == 0 { | ||
| 47 | tx.blocking_write(b"0").ok(); | ||
| 48 | return; | ||
| 49 | } | ||
| 50 | |||
| 51 | while n > 0 { | ||
| 52 | i -= 1; | ||
| 53 | buf[i] = b'0' + (n % 10) as u8; | ||
| 54 | n /= 10; | ||
| 55 | } | ||
| 56 | |||
| 57 | tx.blocking_write(&buf[i..]).ok(); | ||
| 58 | } | ||
| 59 | |||
| 60 | /// Helper to print a buffer to UART | ||
| 61 | fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { | ||
| 62 | tx.blocking_write(b"[").ok(); | ||
| 63 | unsafe { | ||
| 64 | for i in 0..len { | ||
| 65 | write_u32(tx, *buf_ptr.add(i)); | ||
| 66 | if i < len - 1 { | ||
| 67 | tx.blocking_write(b", ").ok(); | ||
| 68 | } | ||
| 69 | } | ||
| 70 | } | ||
| 71 | tx.blocking_write(b"]").ok(); | ||
| 72 | } | ||
| 73 | |||
| 74 | #[embassy_executor::main] | ||
| 75 | async fn main(_spawner: Spawner) { | ||
| 76 | // Small delay to allow probe-rs to attach after reset | ||
| 77 | for _ in 0..100_000 { | ||
| 78 | cortex_m::asm::nop(); | ||
| 79 | } | ||
| 80 | |||
| 81 | let mut cfg = hal::config::Config::default(); | ||
| 82 | cfg.clock_cfg.sirc.fro_12m_enabled = true; | ||
| 83 | cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); | ||
| 84 | let p = hal::init(cfg); | ||
| 85 | |||
| 86 | defmt::info!("DMA channel link example starting..."); | ||
| 87 | |||
| 88 | // DMA is initialized during hal::init() - no need to call ensure_init() | ||
| 89 | |||
| 90 | let pac_periphs = unsafe { pac::Peripherals::steal() }; | ||
| 91 | let dma0 = &pac_periphs.dma0; | ||
| 92 | let edma = unsafe { &*pac::Edma0Tcd0::ptr() }; | ||
| 93 | |||
| 94 | // Clear any residual state | ||
| 95 | for i in 0..3 { | ||
| 96 | let t = edma.tcd(i); | ||
| 97 | t.ch_csr().write(|w| w.erq().disable().done().clear_bit_by_one()); | ||
| 98 | t.ch_int().write(|w| w.int().clear_bit_by_one()); | ||
| 99 | t.ch_es().write(|w| w.err().clear_bit_by_one()); | ||
| 100 | t.ch_mux().write(|w| unsafe { w.bits(0) }); | ||
| 101 | } | ||
| 102 | |||
| 103 | // Clear Global Halt/Error state | ||
| 104 | dma0.mp_csr().modify(|_, w| { | ||
| 105 | w.halt() | ||
| 106 | .normal_operation() | ||
| 107 | .hae() | ||
| 108 | .normal_operation() | ||
| 109 | .ecx() | ||
| 110 | .normal_operation() | ||
| 111 | .cx() | ||
| 112 | .normal_operation() | ||
| 113 | }); | ||
| 114 | |||
| 115 | unsafe { | ||
| 116 | cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); | ||
| 117 | cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH1); | ||
| 118 | cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH2); | ||
| 119 | } | ||
| 120 | |||
| 121 | let config = Config { | ||
| 122 | baudrate_bps: 115_200, | ||
| 123 | ..Default::default() | ||
| 124 | }; | ||
| 125 | |||
| 126 | let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); | ||
| 127 | let (mut tx, _rx) = lpuart.split(); | ||
| 128 | |||
| 129 | tx.blocking_write(b"EDMA channel link example begin.\r\n\r\n").unwrap(); | ||
| 130 | |||
| 131 | // Initialize buffers | ||
| 132 | unsafe { | ||
| 133 | SRC_BUFFER = [1, 2, 3, 4]; | ||
| 134 | DEST_BUFFER0 = [0; 4]; | ||
| 135 | DEST_BUFFER1 = [0; 4]; | ||
| 136 | DEST_BUFFER2 = [0; 4]; | ||
| 137 | } | ||
| 138 | |||
| 139 | tx.blocking_write(b"Source Buffer: ").unwrap(); | ||
| 140 | print_buffer(&mut tx, core::ptr::addr_of!(SRC_BUFFER) as *const u32, 4); | ||
| 141 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 142 | |||
| 143 | tx.blocking_write(b"DEST0 (before): ").unwrap(); | ||
| 144 | print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER0) as *const u32, 4); | ||
| 145 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 146 | |||
| 147 | tx.blocking_write(b"DEST1 (before): ").unwrap(); | ||
| 148 | print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER1) as *const u32, 4); | ||
| 149 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 150 | |||
| 151 | tx.blocking_write(b"DEST2 (before): ").unwrap(); | ||
| 152 | print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER2) as *const u32, 4); | ||
| 153 | tx.blocking_write(b"\r\n\r\n").unwrap(); | ||
| 154 | |||
| 155 | tx.blocking_write(b"Configuring DMA channels with Embassy-style API...\r\n") | ||
| 156 | .unwrap(); | ||
| 157 | |||
| 158 | let ch0 = DmaChannel::new(p.DMA_CH0); | ||
| 159 | let ch1 = DmaChannel::new(p.DMA_CH1); | ||
| 160 | let ch2 = DmaChannel::new(p.DMA_CH2); | ||
| 161 | |||
| 162 | // Configure channels using direct TCD access (advanced feature demo) | ||
| 163 | // This example demonstrates channel linking which requires direct TCD manipulation | ||
| 164 | |||
| 165 | // Helper to configure TCD for memory-to-memory transfer | ||
| 166 | // Parameters: channel, src, dst, width, nbytes (minor loop), count (major loop), interrupt | ||
| 167 | #[allow(clippy::too_many_arguments)] | ||
| 168 | unsafe fn configure_tcd( | ||
| 169 | edma: &embassy_mcxa::pac::edma_0_tcd0::RegisterBlock, | ||
| 170 | ch: usize, | ||
| 171 | src: u32, | ||
| 172 | dst: u32, | ||
| 173 | width: u8, | ||
| 174 | nbytes: u32, | ||
| 175 | count: u16, | ||
| 176 | enable_int: bool, | ||
| 177 | ) { | ||
| 178 | let t = edma.tcd(ch); | ||
| 179 | |||
| 180 | // Reset channel state | ||
| 181 | t.ch_csr().write(|w| { | ||
| 182 | w.erq() | ||
| 183 | .disable() | ||
| 184 | .earq() | ||
| 185 | .disable() | ||
| 186 | .eei() | ||
| 187 | .no_error() | ||
| 188 | .ebw() | ||
| 189 | .disable() | ||
| 190 | .done() | ||
| 191 | .clear_bit_by_one() | ||
| 192 | }); | ||
| 193 | t.ch_es().write(|w| w.bits(0)); | ||
| 194 | t.ch_int().write(|w| w.int().clear_bit_by_one()); | ||
| 195 | |||
| 196 | // Source/destination addresses | ||
| 197 | t.tcd_saddr().write(|w| w.saddr().bits(src)); | ||
| 198 | t.tcd_daddr().write(|w| w.daddr().bits(dst)); | ||
| 199 | |||
| 200 | // Offsets: increment by width | ||
| 201 | t.tcd_soff().write(|w| w.soff().bits(width as u16)); | ||
| 202 | t.tcd_doff().write(|w| w.doff().bits(width as u16)); | ||
| 203 | |||
| 204 | // Attributes: size = log2(width) | ||
| 205 | let size = match width { | ||
| 206 | 1 => 0, | ||
| 207 | 2 => 1, | ||
| 208 | 4 => 2, | ||
| 209 | _ => 0, | ||
| 210 | }; | ||
| 211 | t.tcd_attr().write(|w| w.ssize().bits(size).dsize().bits(size)); | ||
| 212 | |||
| 213 | // Number of bytes per minor loop | ||
| 214 | t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(nbytes)); | ||
| 215 | |||
| 216 | // Major loop: reset source address after major loop | ||
| 217 | let total_bytes = nbytes * count as u32; | ||
| 218 | t.tcd_slast_sda() | ||
| 219 | .write(|w| w.slast_sda().bits(-(total_bytes as i32) as u32)); | ||
| 220 | t.tcd_dlast_sga() | ||
| 221 | .write(|w| w.dlast_sga().bits(-(total_bytes as i32) as u32)); | ||
| 222 | |||
| 223 | // Major loop count | ||
| 224 | t.tcd_biter_elinkno().write(|w| w.biter().bits(count)); | ||
| 225 | t.tcd_citer_elinkno().write(|w| w.citer().bits(count)); | ||
| 226 | |||
| 227 | // Control/status: enable interrupt if requested | ||
| 228 | if enable_int { | ||
| 229 | t.tcd_csr().write(|w| w.intmajor().set_bit()); | ||
| 230 | } else { | ||
| 231 | t.tcd_csr().write(|w| w.intmajor().clear_bit()); | ||
| 232 | } | ||
| 233 | |||
| 234 | cortex_m::asm::dsb(); | ||
| 235 | } | ||
| 236 | |||
| 237 | unsafe { | ||
| 238 | // Channel 0: Transfer 16 bytes total (8 bytes per minor loop, 2 major iterations) | ||
| 239 | // Minor Link -> Channel 1 | ||
| 240 | // Major Link -> Channel 2 | ||
| 241 | configure_tcd( | ||
| 242 | edma, | ||
| 243 | 0, | ||
| 244 | core::ptr::addr_of!(SRC_BUFFER) as u32, | ||
| 245 | core::ptr::addr_of_mut!(DEST_BUFFER0) as u32, | ||
| 246 | 4, // src width | ||
| 247 | 8, // nbytes (minor loop = 2 words) | ||
| 248 | 2, // count (major loop = 2 iterations) | ||
| 249 | false, // no interrupt | ||
| 250 | ); | ||
| 251 | ch0.set_minor_link(1); // Link to CH1 after each minor loop | ||
| 252 | ch0.set_major_link(2); // Link to CH2 after major loop | ||
| 253 | |||
| 254 | // Channel 1: Transfer 16 bytes (triggered by CH0 minor link) | ||
| 255 | configure_tcd( | ||
| 256 | edma, | ||
| 257 | 1, | ||
| 258 | core::ptr::addr_of!(SRC_BUFFER) as u32, | ||
| 259 | core::ptr::addr_of_mut!(DEST_BUFFER1) as u32, | ||
| 260 | 4, | ||
| 261 | 16, // full buffer in one minor loop | ||
| 262 | 1, // 1 major iteration | ||
| 263 | false, | ||
| 264 | ); | ||
| 265 | |||
| 266 | // Channel 2: Transfer 16 bytes (triggered by CH0 major link) | ||
| 267 | configure_tcd( | ||
| 268 | edma, | ||
| 269 | 2, | ||
| 270 | core::ptr::addr_of!(SRC_BUFFER) as u32, | ||
| 271 | core::ptr::addr_of_mut!(DEST_BUFFER2) as u32, | ||
| 272 | 4, | ||
| 273 | 16, // full buffer in one minor loop | ||
| 274 | 1, // 1 major iteration | ||
| 275 | true, // enable interrupt | ||
| 276 | ); | ||
| 277 | } | ||
| 278 | |||
| 279 | tx.blocking_write(b"Triggering Channel 0 (1st minor loop)...\r\n") | ||
| 280 | .unwrap(); | ||
| 281 | |||
| 282 | // Trigger first minor loop of CH0 | ||
| 283 | unsafe { | ||
| 284 | ch0.trigger_start(); | ||
| 285 | } | ||
| 286 | |||
| 287 | // Wait for CH1 to complete (triggered by CH0 minor link) | ||
| 288 | while !ch1.is_done() { | ||
| 289 | cortex_m::asm::nop(); | ||
| 290 | } | ||
| 291 | unsafe { | ||
| 292 | ch1.clear_done(); | ||
| 293 | } | ||
| 294 | |||
| 295 | tx.blocking_write(b"CH1 done (via minor link).\r\n").unwrap(); | ||
| 296 | tx.blocking_write(b"Triggering Channel 0 (2nd minor loop)...\r\n") | ||
| 297 | .unwrap(); | ||
| 298 | |||
| 299 | // Trigger second minor loop of CH0 | ||
| 300 | unsafe { | ||
| 301 | ch0.trigger_start(); | ||
| 302 | } | ||
| 303 | |||
| 304 | // Wait for CH0 major loop to complete | ||
| 305 | while !ch0.is_done() { | ||
| 306 | cortex_m::asm::nop(); | ||
| 307 | } | ||
| 308 | unsafe { | ||
| 309 | ch0.clear_done(); | ||
| 310 | } | ||
| 311 | |||
| 312 | tx.blocking_write(b"CH0 major loop done.\r\n").unwrap(); | ||
| 313 | |||
| 314 | // Wait for CH2 to complete (triggered by CH0 major link) | ||
| 315 | // Using is_done() instead of AtomicBool - the standard interrupt handler | ||
| 316 | // clears the interrupt flag and wakes wakers, but DONE bit remains set | ||
| 317 | while !ch2.is_done() { | ||
| 318 | cortex_m::asm::nop(); | ||
| 319 | } | ||
| 320 | unsafe { | ||
| 321 | ch2.clear_done(); | ||
| 322 | } | ||
| 323 | |||
| 324 | tx.blocking_write(b"CH2 done (via major link).\r\n\r\n").unwrap(); | ||
| 325 | |||
| 326 | tx.blocking_write(b"EDMA channel link example finish.\r\n\r\n").unwrap(); | ||
| 327 | |||
| 328 | tx.blocking_write(b"DEST0 (after): ").unwrap(); | ||
| 329 | print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER0) as *const u32, 4); | ||
| 330 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 331 | |||
| 332 | tx.blocking_write(b"DEST1 (after): ").unwrap(); | ||
| 333 | print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER1) as *const u32, 4); | ||
| 334 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 335 | |||
| 336 | tx.blocking_write(b"DEST2 (after): ").unwrap(); | ||
| 337 | print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER2) as *const u32, 4); | ||
| 338 | tx.blocking_write(b"\r\n\r\n").unwrap(); | ||
| 339 | |||
| 340 | // Verify all buffers match source | ||
| 341 | let mut success = true; | ||
| 342 | unsafe { | ||
| 343 | let src_ptr = core::ptr::addr_of!(SRC_BUFFER) as *const u32; | ||
| 344 | let dst0_ptr = core::ptr::addr_of!(DEST_BUFFER0) as *const u32; | ||
| 345 | let dst1_ptr = core::ptr::addr_of!(DEST_BUFFER1) as *const u32; | ||
| 346 | let dst2_ptr = core::ptr::addr_of!(DEST_BUFFER2) as *const u32; | ||
| 347 | |||
| 348 | for i in 0..4 { | ||
| 349 | if *dst0_ptr.add(i) != *src_ptr.add(i) { | ||
| 350 | success = false; | ||
| 351 | } | ||
| 352 | if *dst1_ptr.add(i) != *src_ptr.add(i) { | ||
| 353 | success = false; | ||
| 354 | } | ||
| 355 | if *dst2_ptr.add(i) != *src_ptr.add(i) { | ||
| 356 | success = false; | ||
| 357 | } | ||
| 358 | } | ||
| 359 | } | ||
| 360 | |||
| 361 | if success { | ||
| 362 | tx.blocking_write(b"PASS: Data verified.\r\n").unwrap(); | ||
| 363 | defmt::info!("PASS: Data verified."); | ||
| 364 | } else { | ||
| 365 | tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); | ||
| 366 | defmt::error!("FAIL: Mismatch detected!"); | ||
| 367 | } | ||
| 368 | |||
| 369 | loop { | ||
| 370 | cortex_m::asm::wfe(); | ||
| 371 | } | ||
| 372 | } | ||
diff --git a/examples/mcxa/src/bin/dma_interleave_transfer.rs b/examples/mcxa/src/bin/dma_interleave_transfer.rs new file mode 100644 index 000000000..7876e8978 --- /dev/null +++ b/examples/mcxa/src/bin/dma_interleave_transfer.rs | |||
| @@ -0,0 +1,215 @@ | |||
| 1 | //! DMA interleaved transfer example for MCXA276. | ||
| 2 | //! | ||
| 3 | //! This example demonstrates using DMA with custom source/destination offsets | ||
| 4 | //! to interleave data during transfer. | ||
| 5 | //! | ||
| 6 | //! # Embassy-style features demonstrated: | ||
| 7 | //! - `TransferOptions::default()` for configuration (used internally) | ||
| 8 | //! - DMA channel with `DmaChannel::new()` | ||
| 9 | |||
| 10 | #![no_std] | ||
| 11 | #![no_main] | ||
| 12 | |||
| 13 | use embassy_executor::Spawner; | ||
| 14 | use embassy_mcxa::clocks::config::Div8; | ||
| 15 | use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaChannel}; | ||
| 16 | use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; | ||
| 17 | use embassy_mcxa::{bind_interrupts, pac}; | ||
| 18 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 19 | |||
| 20 | // Bind DMA channel 0 interrupt using Embassy-style macro | ||
| 21 | bind_interrupts!(struct Irqs { | ||
| 22 | DMA_CH0 => DmaCh0InterruptHandler; | ||
| 23 | }); | ||
| 24 | |||
| 25 | const BUFFER_LENGTH: usize = 16; | ||
| 26 | const HALF_BUFF_LENGTH: usize = BUFFER_LENGTH / 2; | ||
| 27 | |||
| 28 | // Buffers in RAM | ||
| 29 | static mut SRC_BUFFER: [u32; HALF_BUFF_LENGTH] = [0; HALF_BUFF_LENGTH]; | ||
| 30 | static mut DEST_BUFFER: [u32; BUFFER_LENGTH] = [0; BUFFER_LENGTH]; | ||
| 31 | |||
| 32 | /// Helper to write a u32 as decimal ASCII to UART | ||
| 33 | fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { | ||
| 34 | let mut buf = [0u8; 10]; | ||
| 35 | let mut n = val; | ||
| 36 | let mut i = buf.len(); | ||
| 37 | |||
| 38 | if n == 0 { | ||
| 39 | tx.blocking_write(b"0").ok(); | ||
| 40 | return; | ||
| 41 | } | ||
| 42 | |||
| 43 | while n > 0 { | ||
| 44 | i -= 1; | ||
| 45 | buf[i] = b'0' + (n % 10) as u8; | ||
| 46 | n /= 10; | ||
| 47 | } | ||
| 48 | |||
| 49 | tx.blocking_write(&buf[i..]).ok(); | ||
| 50 | } | ||
| 51 | |||
| 52 | /// Helper to print a buffer to UART | ||
| 53 | fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { | ||
| 54 | tx.blocking_write(b"[").ok(); | ||
| 55 | unsafe { | ||
| 56 | for i in 0..len { | ||
| 57 | write_u32(tx, *buf_ptr.add(i)); | ||
| 58 | if i < len - 1 { | ||
| 59 | tx.blocking_write(b", ").ok(); | ||
| 60 | } | ||
| 61 | } | ||
| 62 | } | ||
| 63 | tx.blocking_write(b"]").ok(); | ||
| 64 | } | ||
| 65 | |||
| 66 | #[embassy_executor::main] | ||
| 67 | async fn main(_spawner: Spawner) { | ||
| 68 | // Small delay to allow probe-rs to attach after reset | ||
| 69 | for _ in 0..100_000 { | ||
| 70 | cortex_m::asm::nop(); | ||
| 71 | } | ||
| 72 | |||
| 73 | let mut cfg = hal::config::Config::default(); | ||
| 74 | cfg.clock_cfg.sirc.fro_12m_enabled = true; | ||
| 75 | cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); | ||
| 76 | let p = hal::init(cfg); | ||
| 77 | |||
| 78 | defmt::info!("DMA interleave transfer example starting..."); | ||
| 79 | |||
| 80 | // Enable DMA interrupt (DMA clock/reset/init is handled automatically by HAL) | ||
| 81 | unsafe { | ||
| 82 | cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); | ||
| 83 | } | ||
| 84 | |||
| 85 | let config = Config { | ||
| 86 | baudrate_bps: 115_200, | ||
| 87 | ..Default::default() | ||
| 88 | }; | ||
| 89 | |||
| 90 | let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); | ||
| 91 | let (mut tx, _rx) = lpuart.split(); | ||
| 92 | |||
| 93 | tx.blocking_write(b"EDMA interleave transfer example begin.\r\n\r\n") | ||
| 94 | .unwrap(); | ||
| 95 | |||
| 96 | // Initialize buffers | ||
| 97 | unsafe { | ||
| 98 | SRC_BUFFER = [1, 2, 3, 4, 5, 6, 7, 8]; | ||
| 99 | DEST_BUFFER = [0; BUFFER_LENGTH]; | ||
| 100 | } | ||
| 101 | |||
| 102 | tx.blocking_write(b"Source Buffer: ").unwrap(); | ||
| 103 | print_buffer(&mut tx, core::ptr::addr_of!(SRC_BUFFER) as *const u32, HALF_BUFF_LENGTH); | ||
| 104 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 105 | |||
| 106 | tx.blocking_write(b"Destination Buffer (before): ").unwrap(); | ||
| 107 | print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER) as *const u32, BUFFER_LENGTH); | ||
| 108 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 109 | |||
| 110 | tx.blocking_write(b"Configuring DMA with Embassy-style API...\r\n") | ||
| 111 | .unwrap(); | ||
| 112 | |||
| 113 | // Create DMA channel using Embassy-style API | ||
| 114 | let dma_ch0 = DmaChannel::new(p.DMA_CH0); | ||
| 115 | |||
| 116 | // Configure interleaved transfer using direct TCD access: | ||
| 117 | // - src_offset = 4: advance source by 4 bytes after each read | ||
| 118 | // - dst_offset = 8: advance dest by 8 bytes after each write | ||
| 119 | // This spreads source data across every other word in destination | ||
| 120 | unsafe { | ||
| 121 | let t = dma_ch0.tcd(); | ||
| 122 | |||
| 123 | // Reset channel state | ||
| 124 | t.ch_csr().write(|w| { | ||
| 125 | w.erq() | ||
| 126 | .disable() | ||
| 127 | .earq() | ||
| 128 | .disable() | ||
| 129 | .eei() | ||
| 130 | .no_error() | ||
| 131 | .ebw() | ||
| 132 | .disable() | ||
| 133 | .done() | ||
| 134 | .clear_bit_by_one() | ||
| 135 | }); | ||
| 136 | t.ch_es().write(|w| w.bits(0)); | ||
| 137 | t.ch_int().write(|w| w.int().clear_bit_by_one()); | ||
| 138 | |||
| 139 | // Source/destination addresses | ||
| 140 | t.tcd_saddr() | ||
| 141 | .write(|w| w.saddr().bits(core::ptr::addr_of_mut!(SRC_BUFFER) as u32)); | ||
| 142 | t.tcd_daddr() | ||
| 143 | .write(|w| w.daddr().bits(core::ptr::addr_of_mut!(DEST_BUFFER) as u32)); | ||
| 144 | |||
| 145 | // Custom offsets for interleaving | ||
| 146 | t.tcd_soff().write(|w| w.soff().bits(4)); // src: +4 bytes per read | ||
| 147 | t.tcd_doff().write(|w| w.doff().bits(8)); // dst: +8 bytes per write | ||
| 148 | |||
| 149 | // Attributes: 32-bit transfers (size = 2) | ||
| 150 | t.tcd_attr().write(|w| w.ssize().bits(2).dsize().bits(2)); | ||
| 151 | |||
| 152 | // Transfer entire source buffer in one minor loop | ||
| 153 | let nbytes = (HALF_BUFF_LENGTH * 4) as u32; | ||
| 154 | t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(nbytes)); | ||
| 155 | |||
| 156 | // Reset source address after major loop | ||
| 157 | t.tcd_slast_sda().write(|w| w.slast_sda().bits(-(nbytes as i32) as u32)); | ||
| 158 | // Destination uses 2x offset, so adjust accordingly | ||
| 159 | let dst_total = (HALF_BUFF_LENGTH * 8) as u32; | ||
| 160 | t.tcd_dlast_sga() | ||
| 161 | .write(|w| w.dlast_sga().bits(-(dst_total as i32) as u32)); | ||
| 162 | |||
| 163 | // Major loop count = 1 | ||
| 164 | t.tcd_biter_elinkno().write(|w| w.biter().bits(1)); | ||
| 165 | t.tcd_citer_elinkno().write(|w| w.citer().bits(1)); | ||
| 166 | |||
| 167 | // Enable interrupt on major loop completion | ||
| 168 | t.tcd_csr().write(|w| w.intmajor().set_bit()); | ||
| 169 | |||
| 170 | cortex_m::asm::dsb(); | ||
| 171 | |||
| 172 | tx.blocking_write(b"Triggering transfer...\r\n").unwrap(); | ||
| 173 | dma_ch0.trigger_start(); | ||
| 174 | } | ||
| 175 | |||
| 176 | // Wait for completion using channel helper method | ||
| 177 | while !dma_ch0.is_done() { | ||
| 178 | cortex_m::asm::nop(); | ||
| 179 | } | ||
| 180 | unsafe { | ||
| 181 | dma_ch0.clear_done(); | ||
| 182 | } | ||
| 183 | |||
| 184 | tx.blocking_write(b"\r\nEDMA interleave transfer example finish.\r\n\r\n") | ||
| 185 | .unwrap(); | ||
| 186 | tx.blocking_write(b"Destination Buffer (after): ").unwrap(); | ||
| 187 | print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER) as *const u32, BUFFER_LENGTH); | ||
| 188 | tx.blocking_write(b"\r\n\r\n").unwrap(); | ||
| 189 | |||
| 190 | // Verify: Even indices should match SRC_BUFFER[i/2], odd indices should be 0 | ||
| 191 | let mut mismatch = false; | ||
| 192 | unsafe { | ||
| 193 | for i in 0..BUFFER_LENGTH { | ||
| 194 | if i % 2 == 0 { | ||
| 195 | if DEST_BUFFER[i] != SRC_BUFFER[i / 2] { | ||
| 196 | mismatch = true; | ||
| 197 | } | ||
| 198 | } else if DEST_BUFFER[i] != 0 { | ||
| 199 | mismatch = true; | ||
| 200 | } | ||
| 201 | } | ||
| 202 | } | ||
| 203 | |||
| 204 | if mismatch { | ||
| 205 | tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); | ||
| 206 | defmt::error!("FAIL: Mismatch detected!"); | ||
| 207 | } else { | ||
| 208 | tx.blocking_write(b"PASS: Data verified.\r\n").unwrap(); | ||
| 209 | defmt::info!("PASS: Data verified."); | ||
| 210 | } | ||
| 211 | |||
| 212 | loop { | ||
| 213 | cortex_m::asm::wfe(); | ||
| 214 | } | ||
| 215 | } | ||
diff --git a/examples/mcxa/src/bin/dma_mem_to_mem.rs b/examples/mcxa/src/bin/dma_mem_to_mem.rs new file mode 100644 index 000000000..68f70e742 --- /dev/null +++ b/examples/mcxa/src/bin/dma_mem_to_mem.rs | |||
| @@ -0,0 +1,229 @@ | |||
| 1 | //! DMA memory-to-memory transfer example for MCXA276. | ||
| 2 | //! | ||
| 3 | //! This example demonstrates using DMA to copy data between memory buffers | ||
| 4 | //! using the Embassy-style async API with type-safe transfers. | ||
| 5 | //! | ||
| 6 | //! # Embassy-style features demonstrated: | ||
| 7 | //! - `TransferOptions` for configuration | ||
| 8 | //! - Type-safe `mem_to_mem<u32>()` method with async `.await` | ||
| 9 | //! - `Transfer` Future that can be `.await`ed | ||
| 10 | //! - `Word` trait for automatic transfer width detection | ||
| 11 | //! - `memset()` method for filling memory with a pattern | ||
| 12 | |||
| 13 | #![no_std] | ||
| 14 | #![no_main] | ||
| 15 | |||
| 16 | use embassy_executor::Spawner; | ||
| 17 | use embassy_mcxa::clocks::config::Div8; | ||
| 18 | use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaChannel, TransferOptions}; | ||
| 19 | use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; | ||
| 20 | use embassy_mcxa::{bind_interrupts, pac}; | ||
| 21 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 22 | |||
| 23 | // Bind DMA channel 0 interrupt using Embassy-style macro | ||
| 24 | bind_interrupts!(struct Irqs { | ||
| 25 | DMA_CH0 => DmaCh0InterruptHandler; | ||
| 26 | }); | ||
| 27 | |||
| 28 | const BUFFER_LENGTH: usize = 4; | ||
| 29 | |||
| 30 | // Buffers in RAM (static mut is automatically placed in .bss/.data) | ||
| 31 | static mut SRC_BUFFER: [u32; BUFFER_LENGTH] = [0; BUFFER_LENGTH]; | ||
| 32 | static mut DEST_BUFFER: [u32; BUFFER_LENGTH] = [0; BUFFER_LENGTH]; | ||
| 33 | static mut MEMSET_BUFFER: [u32; BUFFER_LENGTH] = [0; BUFFER_LENGTH]; | ||
| 34 | |||
| 35 | /// Helper to write a u32 as decimal ASCII to UART | ||
| 36 | fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { | ||
| 37 | let mut buf = [0u8; 10]; // u32 max is 4294967295 (10 digits) | ||
| 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 as [v1, v2, v3, v4] to UART | ||
| 56 | /// Takes a raw pointer to avoid warnings about shared references to mutable statics | ||
| 57 | fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const [u32; BUFFER_LENGTH]) { | ||
| 58 | tx.blocking_write(b"[").ok(); | ||
| 59 | unsafe { | ||
| 60 | let buf = &*buf_ptr; | ||
| 61 | for (i, val) in buf.iter().enumerate() { | ||
| 62 | write_u32(tx, *val); | ||
| 63 | if i < buf.len() - 1 { | ||
| 64 | tx.blocking_write(b", ").ok(); | ||
| 65 | } | ||
| 66 | } | ||
| 67 | } | ||
| 68 | tx.blocking_write(b"]").ok(); | ||
| 69 | } | ||
| 70 | |||
| 71 | #[embassy_executor::main] | ||
| 72 | async fn main(_spawner: Spawner) { | ||
| 73 | // Small delay to allow probe-rs to attach after reset | ||
| 74 | for _ in 0..100_000 { | ||
| 75 | cortex_m::asm::nop(); | ||
| 76 | } | ||
| 77 | |||
| 78 | let mut cfg = hal::config::Config::default(); | ||
| 79 | cfg.clock_cfg.sirc.fro_12m_enabled = true; | ||
| 80 | cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); | ||
| 81 | let p = hal::init(cfg); | ||
| 82 | |||
| 83 | defmt::info!("DMA memory-to-memory example starting..."); | ||
| 84 | |||
| 85 | // Enable DMA interrupt (DMA clock/reset/init is handled automatically by HAL) | ||
| 86 | unsafe { | ||
| 87 | cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); | ||
| 88 | } | ||
| 89 | |||
| 90 | // Create UART for debug output | ||
| 91 | let config = Config { | ||
| 92 | baudrate_bps: 115_200, | ||
| 93 | ..Default::default() | ||
| 94 | }; | ||
| 95 | |||
| 96 | let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); | ||
| 97 | let (mut tx, _rx) = lpuart.split(); | ||
| 98 | |||
| 99 | tx.blocking_write(b"EDMA memory to memory example begin.\r\n\r\n") | ||
| 100 | .unwrap(); | ||
| 101 | |||
| 102 | // Initialize buffers | ||
| 103 | unsafe { | ||
| 104 | SRC_BUFFER = [1, 2, 3, 4]; | ||
| 105 | DEST_BUFFER = [0; BUFFER_LENGTH]; | ||
| 106 | } | ||
| 107 | |||
| 108 | tx.blocking_write(b"Source Buffer: ").unwrap(); | ||
| 109 | print_buffer(&mut tx, &raw const SRC_BUFFER); | ||
| 110 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 111 | |||
| 112 | tx.blocking_write(b"Destination Buffer (before): ").unwrap(); | ||
| 113 | print_buffer(&mut tx, &raw const DEST_BUFFER); | ||
| 114 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 115 | |||
| 116 | tx.blocking_write(b"Configuring DMA with Embassy-style API...\r\n") | ||
| 117 | .unwrap(); | ||
| 118 | |||
| 119 | // Create DMA channel | ||
| 120 | let dma_ch0 = DmaChannel::new(p.DMA_CH0); | ||
| 121 | |||
| 122 | // Configure transfer options (Embassy-style) | ||
| 123 | // TransferOptions defaults to: complete_transfer_interrupt = true | ||
| 124 | let options = TransferOptions::default(); | ||
| 125 | |||
| 126 | // ========================================================================= | ||
| 127 | // Part 1: Embassy-style async API demonstration (mem_to_mem) | ||
| 128 | // ========================================================================= | ||
| 129 | // | ||
| 130 | // Use the new type-safe `mem_to_mem<u32>()` method: | ||
| 131 | // - Automatically determines transfer width from buffer element type (u32) | ||
| 132 | // - Returns a `Transfer` future that can be `.await`ed | ||
| 133 | // - Uses TransferOptions for consistent configuration | ||
| 134 | // | ||
| 135 | // Using async `.await` - the executor can run other tasks while waiting! | ||
| 136 | |||
| 137 | // Perform type-safe memory-to-memory transfer using Embassy-style async API | ||
| 138 | unsafe { | ||
| 139 | let src = &*core::ptr::addr_of!(SRC_BUFFER); | ||
| 140 | let dst = &mut *core::ptr::addr_of_mut!(DEST_BUFFER); | ||
| 141 | |||
| 142 | // Using async `.await` - the executor can run other tasks while waiting! | ||
| 143 | let transfer = dma_ch0.mem_to_mem(src, dst, options); | ||
| 144 | transfer.await; | ||
| 145 | } | ||
| 146 | |||
| 147 | tx.blocking_write(b"DMA mem-to-mem transfer complete!\r\n\r\n").unwrap(); | ||
| 148 | tx.blocking_write(b"Destination Buffer (after): ").unwrap(); | ||
| 149 | print_buffer(&mut tx, &raw const DEST_BUFFER); | ||
| 150 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 151 | |||
| 152 | // Verify data | ||
| 153 | let mut mismatch = false; | ||
| 154 | unsafe { | ||
| 155 | for i in 0..BUFFER_LENGTH { | ||
| 156 | if SRC_BUFFER[i] != DEST_BUFFER[i] { | ||
| 157 | mismatch = true; | ||
| 158 | break; | ||
| 159 | } | ||
| 160 | } | ||
| 161 | } | ||
| 162 | |||
| 163 | if mismatch { | ||
| 164 | tx.blocking_write(b"FAIL: mem_to_mem mismatch!\r\n").unwrap(); | ||
| 165 | defmt::error!("FAIL: mem_to_mem mismatch!"); | ||
| 166 | } else { | ||
| 167 | tx.blocking_write(b"PASS: mem_to_mem verified.\r\n\r\n").unwrap(); | ||
| 168 | defmt::info!("PASS: mem_to_mem verified."); | ||
| 169 | } | ||
| 170 | |||
| 171 | // ========================================================================= | ||
| 172 | // Part 2: memset() demonstration | ||
| 173 | // ========================================================================= | ||
| 174 | // | ||
| 175 | // The `memset()` method fills a buffer with a pattern value: | ||
| 176 | // - Fixed source address (pattern is read repeatedly) | ||
| 177 | // - Incrementing destination address | ||
| 178 | // - Uses the same Transfer future pattern | ||
| 179 | |||
| 180 | tx.blocking_write(b"--- Demonstrating memset() feature ---\r\n\r\n") | ||
| 181 | .unwrap(); | ||
| 182 | |||
| 183 | tx.blocking_write(b"Memset Buffer (before): ").unwrap(); | ||
| 184 | print_buffer(&mut tx, &raw const MEMSET_BUFFER); | ||
| 185 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 186 | |||
| 187 | // Fill buffer with a pattern value using DMA memset | ||
| 188 | let pattern: u32 = 0xDEADBEEF; | ||
| 189 | tx.blocking_write(b"Filling with pattern 0xDEADBEEF...\r\n").unwrap(); | ||
| 190 | |||
| 191 | unsafe { | ||
| 192 | let dst = &mut *core::ptr::addr_of_mut!(MEMSET_BUFFER); | ||
| 193 | |||
| 194 | // Using blocking_wait() for demonstration - also shows non-async usage | ||
| 195 | let transfer = dma_ch0.memset(&pattern, dst, options); | ||
| 196 | transfer.blocking_wait(); | ||
| 197 | } | ||
| 198 | |||
| 199 | tx.blocking_write(b"DMA memset complete!\r\n\r\n").unwrap(); | ||
| 200 | tx.blocking_write(b"Memset Buffer (after): ").unwrap(); | ||
| 201 | print_buffer(&mut tx, &raw const MEMSET_BUFFER); | ||
| 202 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 203 | |||
| 204 | // Verify memset result | ||
| 205 | let mut memset_ok = true; | ||
| 206 | unsafe { | ||
| 207 | #[allow(clippy::needless_range_loop)] | ||
| 208 | for i in 0..BUFFER_LENGTH { | ||
| 209 | if MEMSET_BUFFER[i] != pattern { | ||
| 210 | memset_ok = false; | ||
| 211 | break; | ||
| 212 | } | ||
| 213 | } | ||
| 214 | } | ||
| 215 | |||
| 216 | if !memset_ok { | ||
| 217 | tx.blocking_write(b"FAIL: memset mismatch!\r\n").unwrap(); | ||
| 218 | defmt::error!("FAIL: memset mismatch!"); | ||
| 219 | } else { | ||
| 220 | tx.blocking_write(b"PASS: memset verified.\r\n\r\n").unwrap(); | ||
| 221 | defmt::info!("PASS: memset verified."); | ||
| 222 | } | ||
| 223 | |||
| 224 | tx.blocking_write(b"=== All DMA tests complete ===\r\n").unwrap(); | ||
| 225 | |||
| 226 | loop { | ||
| 227 | cortex_m::asm::wfe(); | ||
| 228 | } | ||
| 229 | } | ||
diff --git a/examples/mcxa/src/bin/dma_memset.rs b/examples/mcxa/src/bin/dma_memset.rs new file mode 100644 index 000000000..95e365e47 --- /dev/null +++ b/examples/mcxa/src/bin/dma_memset.rs | |||
| @@ -0,0 +1,218 @@ | |||
| 1 | //! DMA memset example for MCXA276. | ||
| 2 | //! | ||
| 3 | //! This example demonstrates using DMA to fill a buffer with a repeated pattern. | ||
| 4 | //! The source address stays fixed while the destination increments. | ||
| 5 | //! | ||
| 6 | //! # Embassy-style features demonstrated: | ||
| 7 | //! - `DmaChannel::is_done()` and `clear_done()` helper methods | ||
| 8 | //! - No need to pass register block around | ||
| 9 | |||
| 10 | #![no_std] | ||
| 11 | #![no_main] | ||
| 12 | |||
| 13 | use embassy_executor::Spawner; | ||
| 14 | use embassy_mcxa::clocks::config::Div8; | ||
| 15 | use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaChannel}; | ||
| 16 | use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; | ||
| 17 | use embassy_mcxa::{bind_interrupts, pac}; | ||
| 18 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 19 | |||
| 20 | // Bind DMA channel 0 interrupt using Embassy-style macro | ||
| 21 | bind_interrupts!(struct Irqs { | ||
| 22 | DMA_CH0 => DmaCh0InterruptHandler; | ||
| 23 | }); | ||
| 24 | |||
| 25 | const BUFFER_LENGTH: usize = 4; | ||
| 26 | |||
| 27 | // Buffers in RAM | ||
| 28 | static mut PATTERN: u32 = 0; | ||
| 29 | static mut DEST_BUFFER: [u32; BUFFER_LENGTH] = [0; BUFFER_LENGTH]; | ||
| 30 | |||
| 31 | /// Helper to write a u32 as decimal ASCII to UART | ||
| 32 | fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { | ||
| 33 | let mut buf = [0u8; 10]; | ||
| 34 | let mut n = val; | ||
| 35 | let mut i = buf.len(); | ||
| 36 | |||
| 37 | if n == 0 { | ||
| 38 | tx.blocking_write(b"0").ok(); | ||
| 39 | return; | ||
| 40 | } | ||
| 41 | |||
| 42 | while n > 0 { | ||
| 43 | i -= 1; | ||
| 44 | buf[i] = b'0' + (n % 10) as u8; | ||
| 45 | n /= 10; | ||
| 46 | } | ||
| 47 | |||
| 48 | tx.blocking_write(&buf[i..]).ok(); | ||
| 49 | } | ||
| 50 | |||
| 51 | /// Helper to print a buffer to UART | ||
| 52 | fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { | ||
| 53 | tx.blocking_write(b"[").ok(); | ||
| 54 | unsafe { | ||
| 55 | for i in 0..len { | ||
| 56 | write_u32(tx, *buf_ptr.add(i)); | ||
| 57 | if i < len - 1 { | ||
| 58 | tx.blocking_write(b", ").ok(); | ||
| 59 | } | ||
| 60 | } | ||
| 61 | } | ||
| 62 | tx.blocking_write(b"]").ok(); | ||
| 63 | } | ||
| 64 | |||
| 65 | #[embassy_executor::main] | ||
| 66 | async 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 memset example starting..."); | ||
| 78 | |||
| 79 | // Enable DMA interrupt (DMA clock/reset/init is handled automatically by HAL) | ||
| 80 | unsafe { | ||
| 81 | cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); | ||
| 82 | } | ||
| 83 | |||
| 84 | let config = Config { | ||
| 85 | baudrate_bps: 115_200, | ||
| 86 | ..Default::default() | ||
| 87 | }; | ||
| 88 | |||
| 89 | let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); | ||
| 90 | let (mut tx, _rx) = lpuart.split(); | ||
| 91 | |||
| 92 | tx.blocking_write(b"EDMA memset example begin.\r\n\r\n").unwrap(); | ||
| 93 | |||
| 94 | // Initialize buffers | ||
| 95 | unsafe { | ||
| 96 | PATTERN = 0xDEADBEEF; | ||
| 97 | DEST_BUFFER = [0; BUFFER_LENGTH]; | ||
| 98 | } | ||
| 99 | |||
| 100 | tx.blocking_write(b"Pattern value: 0x").unwrap(); | ||
| 101 | // Print pattern in hex | ||
| 102 | unsafe { | ||
| 103 | let hex_chars = b"0123456789ABCDEF"; | ||
| 104 | let mut hex_buf = [0u8; 8]; | ||
| 105 | let mut val = PATTERN; | ||
| 106 | for i in (0..8).rev() { | ||
| 107 | hex_buf[i] = hex_chars[(val & 0xF) as usize]; | ||
| 108 | val >>= 4; | ||
| 109 | } | ||
| 110 | tx.blocking_write(&hex_buf).ok(); | ||
| 111 | } | ||
| 112 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 113 | |||
| 114 | tx.blocking_write(b"Destination Buffer (before): ").unwrap(); | ||
| 115 | print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER) as *const u32, BUFFER_LENGTH); | ||
| 116 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 117 | |||
| 118 | tx.blocking_write(b"Configuring DMA with Embassy-style API...\r\n") | ||
| 119 | .unwrap(); | ||
| 120 | |||
| 121 | // Create DMA channel using Embassy-style API | ||
| 122 | let dma_ch0 = DmaChannel::new(p.DMA_CH0); | ||
| 123 | |||
| 124 | // Configure memset transfer using direct TCD access: | ||
| 125 | // Source stays fixed (soff = 0, reads same pattern repeatedly) | ||
| 126 | // Destination increments (doff = 4) | ||
| 127 | unsafe { | ||
| 128 | let t = dma_ch0.tcd(); | ||
| 129 | |||
| 130 | // Reset channel state | ||
| 131 | t.ch_csr().write(|w| { | ||
| 132 | w.erq() | ||
| 133 | .disable() | ||
| 134 | .earq() | ||
| 135 | .disable() | ||
| 136 | .eei() | ||
| 137 | .no_error() | ||
| 138 | .ebw() | ||
| 139 | .disable() | ||
| 140 | .done() | ||
| 141 | .clear_bit_by_one() | ||
| 142 | }); | ||
| 143 | t.ch_es().write(|w| w.bits(0)); | ||
| 144 | t.ch_int().write(|w| w.int().clear_bit_by_one()); | ||
| 145 | |||
| 146 | // Source address (pattern) - fixed | ||
| 147 | t.tcd_saddr() | ||
| 148 | .write(|w| w.saddr().bits(core::ptr::addr_of_mut!(PATTERN) as u32)); | ||
| 149 | // Destination address - increments | ||
| 150 | t.tcd_daddr() | ||
| 151 | .write(|w| w.daddr().bits(core::ptr::addr_of_mut!(DEST_BUFFER) as u32)); | ||
| 152 | |||
| 153 | // Source offset = 0 (stays fixed), Dest offset = 4 (increments) | ||
| 154 | t.tcd_soff().write(|w| w.soff().bits(0)); | ||
| 155 | t.tcd_doff().write(|w| w.doff().bits(4)); | ||
| 156 | |||
| 157 | // Attributes: 32-bit transfers (size = 2) | ||
| 158 | t.tcd_attr().write(|w| w.ssize().bits(2).dsize().bits(2)); | ||
| 159 | |||
| 160 | // Transfer entire buffer in one minor loop | ||
| 161 | let nbytes = (BUFFER_LENGTH * 4) as u32; | ||
| 162 | t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(nbytes)); | ||
| 163 | |||
| 164 | // Source doesn't need adjustment (stays fixed) | ||
| 165 | t.tcd_slast_sda().write(|w| w.slast_sda().bits(0)); | ||
| 166 | // Reset dest address after major loop | ||
| 167 | t.tcd_dlast_sga().write(|w| w.dlast_sga().bits(-(nbytes as i32) as u32)); | ||
| 168 | |||
| 169 | // Major loop count = 1 | ||
| 170 | t.tcd_biter_elinkno().write(|w| w.biter().bits(1)); | ||
| 171 | t.tcd_citer_elinkno().write(|w| w.citer().bits(1)); | ||
| 172 | |||
| 173 | // Enable interrupt on major loop completion | ||
| 174 | t.tcd_csr().write(|w| w.intmajor().set_bit()); | ||
| 175 | |||
| 176 | cortex_m::asm::dsb(); | ||
| 177 | |||
| 178 | tx.blocking_write(b"Triggering transfer...\r\n").unwrap(); | ||
| 179 | dma_ch0.trigger_start(); | ||
| 180 | } | ||
| 181 | |||
| 182 | // Wait for completion using channel helper method | ||
| 183 | while !dma_ch0.is_done() { | ||
| 184 | cortex_m::asm::nop(); | ||
| 185 | } | ||
| 186 | unsafe { | ||
| 187 | dma_ch0.clear_done(); | ||
| 188 | } | ||
| 189 | |||
| 190 | tx.blocking_write(b"\r\nEDMA memset example finish.\r\n\r\n").unwrap(); | ||
| 191 | tx.blocking_write(b"Destination Buffer (after): ").unwrap(); | ||
| 192 | print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER) as *const u32, BUFFER_LENGTH); | ||
| 193 | tx.blocking_write(b"\r\n\r\n").unwrap(); | ||
| 194 | |||
| 195 | // Verify: All elements should equal PATTERN | ||
| 196 | let mut mismatch = false; | ||
| 197 | unsafe { | ||
| 198 | #[allow(clippy::needless_range_loop)] | ||
| 199 | for i in 0..BUFFER_LENGTH { | ||
| 200 | if DEST_BUFFER[i] != PATTERN { | ||
| 201 | mismatch = true; | ||
| 202 | break; | ||
| 203 | } | ||
| 204 | } | ||
| 205 | } | ||
| 206 | |||
| 207 | if mismatch { | ||
| 208 | tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); | ||
| 209 | defmt::error!("FAIL: Mismatch detected!"); | ||
| 210 | } else { | ||
| 211 | tx.blocking_write(b"PASS: Data verified.\r\n").unwrap(); | ||
| 212 | defmt::info!("PASS: Data verified."); | ||
| 213 | } | ||
| 214 | |||
| 215 | loop { | ||
| 216 | cortex_m::asm::wfe(); | ||
| 217 | } | ||
| 218 | } | ||
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 | |||
| 26 | use core::sync::atomic::{AtomicBool, Ordering}; | ||
| 27 | |||
| 28 | use embassy_executor::Spawner; | ||
| 29 | use embassy_mcxa::clocks::config::Div8; | ||
| 30 | use embassy_mcxa::dma::{self, DmaCh1InterruptHandler, DmaChannel, Tcd, TransferOptions}; | ||
| 31 | use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; | ||
| 32 | use embassy_mcxa::{bind_interrupts, pac}; | ||
| 33 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 34 | |||
| 35 | // Source and destination buffers for Approach 1 (scatter/gather) | ||
| 36 | static mut SRC: [u32; 8] = [1, 2, 3, 4, 5, 6, 7, 8]; | ||
| 37 | static mut DST: [u32; 8] = [0; 8]; | ||
| 38 | |||
| 39 | // Source and destination buffers for Approach 2 (wait_half) | ||
| 40 | static mut SRC2: [u32; 8] = [0xA1, 0xA2, 0xA3, 0xA4, 0xB1, 0xB2, 0xB3, 0xB4]; | ||
| 41 | static mut DST2: [u32; 8] = [0; 8]; | ||
| 42 | |||
| 43 | // TCD pool for scatter/gather - must be 32-byte aligned | ||
| 44 | #[repr(C, align(32))] | ||
| 45 | struct TcdPool([Tcd; 2]); | ||
| 46 | |||
| 47 | static 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 | ||
| 66 | static 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 | ||
| 71 | pub struct PingPongDmaHandler; | ||
| 72 | |||
| 73 | impl 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 | ||
| 85 | bind_interrupts!(struct Irqs { | ||
| 86 | DMA_CH0 => PingPongDmaHandler; | ||
| 87 | DMA_CH1 => DmaCh1InterruptHandler; | ||
| 88 | }); | ||
| 89 | |||
| 90 | /// Helper to write a u32 as decimal ASCII to UART | ||
| 91 | fn 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 | ||
| 111 | fn 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] | ||
| 125 | async 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 | } | ||
diff --git a/examples/mcxa/src/bin/dma_scatter_gather.rs b/examples/mcxa/src/bin/dma_scatter_gather.rs new file mode 100644 index 000000000..4b26bc2ed --- /dev/null +++ b/examples/mcxa/src/bin/dma_scatter_gather.rs | |||
| @@ -0,0 +1,262 @@ | |||
| 1 | //! DMA scatter-gather transfer example for MCXA276. | ||
| 2 | //! | ||
| 3 | //! This example demonstrates using DMA with scatter/gather to chain multiple | ||
| 4 | //! transfer descriptors. The first TCD transfers the first half of the buffer, | ||
| 5 | //! then automatically loads the second TCD to transfer the second half. | ||
| 6 | //! | ||
| 7 | //! # Embassy-style features demonstrated: | ||
| 8 | //! - `DmaChannel::new()` for channel creation | ||
| 9 | //! - Scatter/gather with chained TCDs | ||
| 10 | //! - Custom handler that delegates to HAL's `on_interrupt()` (best practice) | ||
| 11 | |||
| 12 | #![no_std] | ||
| 13 | #![no_main] | ||
| 14 | |||
| 15 | use core::sync::atomic::{AtomicBool, Ordering}; | ||
| 16 | |||
| 17 | use embassy_executor::Spawner; | ||
| 18 | use embassy_mcxa::clocks::config::Div8; | ||
| 19 | use embassy_mcxa::dma::{self, DmaChannel, Tcd}; | ||
| 20 | use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; | ||
| 21 | use embassy_mcxa::{bind_interrupts, pac}; | ||
| 22 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 23 | |||
| 24 | // Source and destination buffers | ||
| 25 | static mut SRC: [u32; 8] = [1, 2, 3, 4, 5, 6, 7, 8]; | ||
| 26 | static mut DST: [u32; 8] = [0; 8]; | ||
| 27 | |||
| 28 | // TCD pool for scatter/gather - must be 32-byte aligned | ||
| 29 | #[repr(C, align(32))] | ||
| 30 | struct TcdPool([Tcd; 2]); | ||
| 31 | |||
| 32 | static mut TCD_POOL: TcdPool = TcdPool( | ||
| 33 | [Tcd { | ||
| 34 | saddr: 0, | ||
| 35 | soff: 0, | ||
| 36 | attr: 0, | ||
| 37 | nbytes: 0, | ||
| 38 | slast: 0, | ||
| 39 | daddr: 0, | ||
| 40 | doff: 0, | ||
| 41 | citer: 0, | ||
| 42 | dlast_sga: 0, | ||
| 43 | csr: 0, | ||
| 44 | biter: 0, | ||
| 45 | }; 2], | ||
| 46 | ); | ||
| 47 | |||
| 48 | // AtomicBool to track scatter/gather completion | ||
| 49 | // Note: With ESG=1, DONE bit is cleared by hardware when next TCD loads, | ||
| 50 | // so we need this flag to detect when each transfer completes | ||
| 51 | static TRANSFER_DONE: AtomicBool = AtomicBool::new(false); | ||
| 52 | |||
| 53 | // Custom handler for scatter/gather that delegates to HAL's on_interrupt() | ||
| 54 | // This follows the "interrupts as threads" pattern - the handler does minimal work | ||
| 55 | // (delegates to HAL + sets a flag) and the main task does the actual processing | ||
| 56 | pub struct ScatterGatherDmaHandler; | ||
| 57 | |||
| 58 | impl embassy_mcxa::interrupt::typelevel::Handler<embassy_mcxa::interrupt::typelevel::DMA_CH0> | ||
| 59 | for ScatterGatherDmaHandler | ||
| 60 | { | ||
| 61 | unsafe fn on_interrupt() { | ||
| 62 | // Delegate to HAL's on_interrupt() which clears INT flag and wakes wakers | ||
| 63 | dma::on_interrupt(0); | ||
| 64 | // Signal completion for polling (needed because ESG clears DONE bit) | ||
| 65 | TRANSFER_DONE.store(true, Ordering::Release); | ||
| 66 | } | ||
| 67 | } | ||
| 68 | |||
| 69 | // Bind DMA channel interrupt | ||
| 70 | // Custom handler for scatter/gather (delegates to on_interrupt + sets flag) | ||
| 71 | bind_interrupts!(struct Irqs { | ||
| 72 | DMA_CH0 => ScatterGatherDmaHandler; | ||
| 73 | }); | ||
| 74 | |||
| 75 | /// Helper to write a u32 as decimal ASCII to UART | ||
| 76 | fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { | ||
| 77 | let mut buf = [0u8; 10]; | ||
| 78 | let mut n = val; | ||
| 79 | let mut i = buf.len(); | ||
| 80 | |||
| 81 | if n == 0 { | ||
| 82 | tx.blocking_write(b"0").ok(); | ||
| 83 | return; | ||
| 84 | } | ||
| 85 | |||
| 86 | while n > 0 { | ||
| 87 | i -= 1; | ||
| 88 | buf[i] = b'0' + (n % 10) as u8; | ||
| 89 | n /= 10; | ||
| 90 | } | ||
| 91 | |||
| 92 | tx.blocking_write(&buf[i..]).ok(); | ||
| 93 | } | ||
| 94 | |||
| 95 | /// Helper to print a buffer to UART | ||
| 96 | fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { | ||
| 97 | tx.blocking_write(b"[").ok(); | ||
| 98 | unsafe { | ||
| 99 | for i in 0..len { | ||
| 100 | write_u32(tx, *buf_ptr.add(i)); | ||
| 101 | if i < len - 1 { | ||
| 102 | tx.blocking_write(b", ").ok(); | ||
| 103 | } | ||
| 104 | } | ||
| 105 | } | ||
| 106 | tx.blocking_write(b"]").ok(); | ||
| 107 | } | ||
| 108 | |||
| 109 | #[embassy_executor::main] | ||
| 110 | async fn main(_spawner: Spawner) { | ||
| 111 | // Small delay to allow probe-rs to attach after reset | ||
| 112 | for _ in 0..100_000 { | ||
| 113 | cortex_m::asm::nop(); | ||
| 114 | } | ||
| 115 | |||
| 116 | let mut cfg = hal::config::Config::default(); | ||
| 117 | cfg.clock_cfg.sirc.fro_12m_enabled = true; | ||
| 118 | cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); | ||
| 119 | let p = hal::init(cfg); | ||
| 120 | |||
| 121 | defmt::info!("DMA scatter-gather transfer example starting..."); | ||
| 122 | |||
| 123 | // DMA is initialized during hal::init() - no need to call ensure_init() | ||
| 124 | |||
| 125 | // Enable DMA interrupt | ||
| 126 | unsafe { | ||
| 127 | cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); | ||
| 128 | } | ||
| 129 | |||
| 130 | let config = Config { | ||
| 131 | baudrate_bps: 115_200, | ||
| 132 | ..Default::default() | ||
| 133 | }; | ||
| 134 | |||
| 135 | let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); | ||
| 136 | let (mut tx, _rx) = lpuart.split(); | ||
| 137 | |||
| 138 | tx.blocking_write(b"EDMA scatter-gather transfer example begin.\r\n\r\n") | ||
| 139 | .unwrap(); | ||
| 140 | |||
| 141 | // Initialize buffers | ||
| 142 | unsafe { | ||
| 143 | SRC = [1, 2, 3, 4, 5, 6, 7, 8]; | ||
| 144 | DST = [0; 8]; | ||
| 145 | } | ||
| 146 | |||
| 147 | tx.blocking_write(b"Source Buffer: ").unwrap(); | ||
| 148 | print_buffer(&mut tx, core::ptr::addr_of!(SRC) as *const u32, 8); | ||
| 149 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 150 | |||
| 151 | tx.blocking_write(b"Destination Buffer (before): ").unwrap(); | ||
| 152 | print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); | ||
| 153 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 154 | |||
| 155 | tx.blocking_write(b"Configuring scatter-gather DMA with Embassy-style API...\r\n") | ||
| 156 | .unwrap(); | ||
| 157 | |||
| 158 | let dma_ch0 = DmaChannel::new(p.DMA_CH0); | ||
| 159 | |||
| 160 | // Configure scatter-gather transfer using direct TCD access: | ||
| 161 | // This sets up TCD0 and TCD1 in RAM, and loads TCD0 into the channel. | ||
| 162 | // TCD0 transfers first half (SRC[0..4] -> DST[0..4]), then loads TCD1. | ||
| 163 | // TCD1 transfers second half (SRC[4..8] -> DST[4..8]), last TCD. | ||
| 164 | unsafe { | ||
| 165 | let tcds = core::slice::from_raw_parts_mut(core::ptr::addr_of_mut!(TCD_POOL.0) as *mut Tcd, 2); | ||
| 166 | let src_ptr = core::ptr::addr_of!(SRC) as *const u32; | ||
| 167 | let dst_ptr = core::ptr::addr_of_mut!(DST) as *mut u32; | ||
| 168 | |||
| 169 | let num_tcds = 2usize; | ||
| 170 | let chunk_len = 4usize; // 8 / 2 | ||
| 171 | let chunk_bytes = (chunk_len * 4) as u32; | ||
| 172 | |||
| 173 | for i in 0..num_tcds { | ||
| 174 | let is_last = i == num_tcds - 1; | ||
| 175 | let next_tcd_addr = if is_last { | ||
| 176 | 0 // No next TCD | ||
| 177 | } else { | ||
| 178 | &tcds[i + 1] as *const _ as u32 | ||
| 179 | }; | ||
| 180 | |||
| 181 | tcds[i] = Tcd { | ||
| 182 | saddr: src_ptr.add(i * chunk_len) as u32, | ||
| 183 | soff: 4, | ||
| 184 | attr: 0x0202, // 32-bit src/dst | ||
| 185 | nbytes: chunk_bytes, | ||
| 186 | slast: 0, | ||
| 187 | daddr: dst_ptr.add(i * chunk_len) as u32, | ||
| 188 | doff: 4, | ||
| 189 | citer: 1, | ||
| 190 | dlast_sga: next_tcd_addr as i32, | ||
| 191 | // ESG (scatter/gather) for non-last, INTMAJOR for all | ||
| 192 | csr: if is_last { 0x0002 } else { 0x0012 }, | ||
| 193 | biter: 1, | ||
| 194 | }; | ||
| 195 | } | ||
| 196 | |||
| 197 | // Load TCD0 into hardware registers | ||
| 198 | dma_ch0.load_tcd(&tcds[0]); | ||
| 199 | } | ||
| 200 | |||
| 201 | tx.blocking_write(b"Triggering first half transfer...\r\n").unwrap(); | ||
| 202 | |||
| 203 | // Trigger first transfer (first half: SRC[0..4] -> DST[0..4]) | ||
| 204 | // TCD0 is currently loaded. | ||
| 205 | unsafe { | ||
| 206 | dma_ch0.trigger_start(); | ||
| 207 | } | ||
| 208 | |||
| 209 | // Wait for first half | ||
| 210 | while !TRANSFER_DONE.load(Ordering::Acquire) { | ||
| 211 | cortex_m::asm::nop(); | ||
| 212 | } | ||
| 213 | TRANSFER_DONE.store(false, Ordering::Release); | ||
| 214 | |||
| 215 | tx.blocking_write(b"First half transferred.\r\n").unwrap(); | ||
| 216 | tx.blocking_write(b"Triggering second half transfer...\r\n").unwrap(); | ||
| 217 | |||
| 218 | // Trigger second transfer (second half: SRC[4..8] -> DST[4..8]) | ||
| 219 | // TCD1 should have been loaded by the scatter/gather engine. | ||
| 220 | unsafe { | ||
| 221 | dma_ch0.trigger_start(); | ||
| 222 | } | ||
| 223 | |||
| 224 | // Wait for second half | ||
| 225 | while !TRANSFER_DONE.load(Ordering::Acquire) { | ||
| 226 | cortex_m::asm::nop(); | ||
| 227 | } | ||
| 228 | TRANSFER_DONE.store(false, Ordering::Release); | ||
| 229 | |||
| 230 | tx.blocking_write(b"Second half transferred.\r\n\r\n").unwrap(); | ||
| 231 | |||
| 232 | tx.blocking_write(b"EDMA scatter-gather transfer example finish.\r\n\r\n") | ||
| 233 | .unwrap(); | ||
| 234 | tx.blocking_write(b"Destination Buffer (after): ").unwrap(); | ||
| 235 | print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); | ||
| 236 | tx.blocking_write(b"\r\n\r\n").unwrap(); | ||
| 237 | |||
| 238 | // Verify: DST should match SRC | ||
| 239 | let mut mismatch = false; | ||
| 240 | unsafe { | ||
| 241 | let src_ptr = core::ptr::addr_of!(SRC) as *const u32; | ||
| 242 | let dst_ptr = core::ptr::addr_of!(DST) as *const u32; | ||
| 243 | for i in 0..8 { | ||
| 244 | if *src_ptr.add(i) != *dst_ptr.add(i) { | ||
| 245 | mismatch = true; | ||
| 246 | break; | ||
| 247 | } | ||
| 248 | } | ||
| 249 | } | ||
| 250 | |||
| 251 | if mismatch { | ||
| 252 | tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); | ||
| 253 | defmt::error!("FAIL: Mismatch detected!"); | ||
| 254 | } else { | ||
| 255 | tx.blocking_write(b"PASS: Data verified.\r\n").unwrap(); | ||
| 256 | defmt::info!("PASS: Data verified."); | ||
| 257 | } | ||
| 258 | |||
| 259 | loop { | ||
| 260 | cortex_m::asm::wfe(); | ||
| 261 | } | ||
| 262 | } | ||
diff --git a/examples/mcxa/src/bin/dma_scatter_gather_builder.rs b/examples/mcxa/src/bin/dma_scatter_gather_builder.rs new file mode 100644 index 000000000..e483bb81f --- /dev/null +++ b/examples/mcxa/src/bin/dma_scatter_gather_builder.rs | |||
| @@ -0,0 +1,231 @@ | |||
| 1 | //! DMA Scatter-Gather Builder example for MCXA276. | ||
| 2 | //! | ||
| 3 | //! This example demonstrates using the new `ScatterGatherBuilder` API for | ||
| 4 | //! chaining multiple DMA transfers with a type-safe builder pattern. | ||
| 5 | //! | ||
| 6 | //! # Features demonstrated: | ||
| 7 | //! - `ScatterGatherBuilder::new()` for creating a builder | ||
| 8 | //! - `add_transfer()` for adding memory-to-memory segments | ||
| 9 | //! - `build()` to start the chained transfer | ||
| 10 | //! - Automatic TCD linking and ESG bit management | ||
| 11 | //! | ||
| 12 | //! # Comparison with manual scatter-gather: | ||
| 13 | //! The manual approach (see `dma_scatter_gather.rs`) requires: | ||
| 14 | //! - Manual TCD pool allocation and alignment | ||
| 15 | //! - Manual CSR/ESG/INTMAJOR bit manipulation | ||
| 16 | //! - Manual dlast_sga address calculations | ||
| 17 | //! | ||
| 18 | //! The builder approach handles all of this automatically! | ||
| 19 | |||
| 20 | #![no_std] | ||
| 21 | #![no_main] | ||
| 22 | |||
| 23 | use embassy_executor::Spawner; | ||
| 24 | use embassy_mcxa::clocks::config::Div8; | ||
| 25 | use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaChannel, ScatterGatherBuilder}; | ||
| 26 | use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; | ||
| 27 | use embassy_mcxa::{bind_interrupts, pac}; | ||
| 28 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 29 | |||
| 30 | // Bind DMA channel 0 interrupt | ||
| 31 | bind_interrupts!(struct Irqs { | ||
| 32 | DMA_CH0 => DmaCh0InterruptHandler; | ||
| 33 | }); | ||
| 34 | |||
| 35 | // Source buffers (multiple segments) | ||
| 36 | static mut SRC1: [u32; 4] = [0x11111111, 0x22222222, 0x33333333, 0x44444444]; | ||
| 37 | static mut SRC2: [u32; 4] = [0xAAAAAAAA, 0xBBBBBBBB, 0xCCCCCCCC, 0xDDDDDDDD]; | ||
| 38 | static mut SRC3: [u32; 4] = [0x12345678, 0x9ABCDEF0, 0xFEDCBA98, 0x76543210]; | ||
| 39 | |||
| 40 | // Destination buffers (one per segment) | ||
| 41 | static mut DST1: [u32; 4] = [0; 4]; | ||
| 42 | static mut DST2: [u32; 4] = [0; 4]; | ||
| 43 | static mut DST3: [u32; 4] = [0; 4]; | ||
| 44 | |||
| 45 | /// Helper to write a u32 as hex to UART | ||
| 46 | fn write_hex(tx: &mut LpuartTx<'_, Blocking>, val: u32) { | ||
| 47 | const HEX: &[u8; 16] = b"0123456789ABCDEF"; | ||
| 48 | for i in (0..8).rev() { | ||
| 49 | let nibble = ((val >> (i * 4)) & 0xF) as usize; | ||
| 50 | tx.blocking_write(&[HEX[nibble]]).ok(); | ||
| 51 | } | ||
| 52 | } | ||
| 53 | |||
| 54 | /// Helper to print a buffer to UART | ||
| 55 | fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { | ||
| 56 | tx.blocking_write(b"[").ok(); | ||
| 57 | unsafe { | ||
| 58 | for i in 0..len { | ||
| 59 | write_hex(tx, *buf_ptr.add(i)); | ||
| 60 | if i < len - 1 { | ||
| 61 | tx.blocking_write(b", ").ok(); | ||
| 62 | } | ||
| 63 | } | ||
| 64 | } | ||
| 65 | tx.blocking_write(b"]").ok(); | ||
| 66 | } | ||
| 67 | |||
| 68 | #[embassy_executor::main] | ||
| 69 | async fn main(_spawner: Spawner) { | ||
| 70 | // Small delay to allow probe-rs to attach after reset | ||
| 71 | for _ in 0..100_000 { | ||
| 72 | cortex_m::asm::nop(); | ||
| 73 | } | ||
| 74 | |||
| 75 | let mut cfg = hal::config::Config::default(); | ||
| 76 | cfg.clock_cfg.sirc.fro_12m_enabled = true; | ||
| 77 | cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); | ||
| 78 | let p = hal::init(cfg); | ||
| 79 | |||
| 80 | defmt::info!("DMA Scatter-Gather Builder example starting..."); | ||
| 81 | |||
| 82 | // Enable DMA interrupt (DMA clock/reset/init is handled automatically by HAL) | ||
| 83 | unsafe { | ||
| 84 | cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); | ||
| 85 | } | ||
| 86 | |||
| 87 | // Create UART for debug output | ||
| 88 | let config = Config { | ||
| 89 | baudrate_bps: 115_200, | ||
| 90 | ..Default::default() | ||
| 91 | }; | ||
| 92 | |||
| 93 | let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); | ||
| 94 | let (mut tx, _rx) = lpuart.split(); | ||
| 95 | |||
| 96 | tx.blocking_write(b"DMA Scatter-Gather Builder Example\r\n").unwrap(); | ||
| 97 | tx.blocking_write(b"===================================\r\n\r\n") | ||
| 98 | .unwrap(); | ||
| 99 | |||
| 100 | // Show source buffers | ||
| 101 | tx.blocking_write(b"Source buffers:\r\n").unwrap(); | ||
| 102 | tx.blocking_write(b" SRC1: ").unwrap(); | ||
| 103 | print_buffer(&mut tx, core::ptr::addr_of!(SRC1) as *const u32, 4); | ||
| 104 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 105 | tx.blocking_write(b" SRC2: ").unwrap(); | ||
| 106 | print_buffer(&mut tx, core::ptr::addr_of!(SRC2) as *const u32, 4); | ||
| 107 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 108 | tx.blocking_write(b" SRC3: ").unwrap(); | ||
| 109 | print_buffer(&mut tx, core::ptr::addr_of!(SRC3) as *const u32, 4); | ||
| 110 | tx.blocking_write(b"\r\n\r\n").unwrap(); | ||
| 111 | |||
| 112 | tx.blocking_write(b"Destination buffers (before):\r\n").unwrap(); | ||
| 113 | tx.blocking_write(b" DST1: ").unwrap(); | ||
| 114 | print_buffer(&mut tx, core::ptr::addr_of!(DST1) as *const u32, 4); | ||
| 115 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 116 | tx.blocking_write(b" DST2: ").unwrap(); | ||
| 117 | print_buffer(&mut tx, core::ptr::addr_of!(DST2) as *const u32, 4); | ||
| 118 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 119 | tx.blocking_write(b" DST3: ").unwrap(); | ||
| 120 | print_buffer(&mut tx, core::ptr::addr_of!(DST3) as *const u32, 4); | ||
| 121 | tx.blocking_write(b"\r\n\r\n").unwrap(); | ||
| 122 | |||
| 123 | // Create DMA channel | ||
| 124 | let dma_ch0 = DmaChannel::new(p.DMA_CH0); | ||
| 125 | |||
| 126 | tx.blocking_write(b"Building scatter-gather chain with builder API...\r\n") | ||
| 127 | .unwrap(); | ||
| 128 | |||
| 129 | // ========================================================================= | ||
| 130 | // ScatterGatherBuilder API demonstration | ||
| 131 | // ========================================================================= | ||
| 132 | // | ||
| 133 | // The builder pattern makes scatter-gather transfers much easier: | ||
| 134 | // 1. Create a builder | ||
| 135 | // 2. Add transfer segments with add_transfer() | ||
| 136 | // 3. Call build() to start the entire chain | ||
| 137 | // No manual TCD manipulation required! | ||
| 138 | |||
| 139 | let mut builder = ScatterGatherBuilder::<u32>::new(); | ||
| 140 | |||
| 141 | // Add three transfer segments - the builder handles TCD linking automatically | ||
| 142 | unsafe { | ||
| 143 | let src1 = &*core::ptr::addr_of!(SRC1); | ||
| 144 | let dst1 = &mut *core::ptr::addr_of_mut!(DST1); | ||
| 145 | builder.add_transfer(src1, dst1); | ||
| 146 | } | ||
| 147 | |||
| 148 | unsafe { | ||
| 149 | let src2 = &*core::ptr::addr_of!(SRC2); | ||
| 150 | let dst2 = &mut *core::ptr::addr_of_mut!(DST2); | ||
| 151 | builder.add_transfer(src2, dst2); | ||
| 152 | } | ||
| 153 | |||
| 154 | unsafe { | ||
| 155 | let src3 = &*core::ptr::addr_of!(SRC3); | ||
| 156 | let dst3 = &mut *core::ptr::addr_of_mut!(DST3); | ||
| 157 | builder.add_transfer(src3, dst3); | ||
| 158 | } | ||
| 159 | |||
| 160 | tx.blocking_write(b"Added 3 transfer segments to chain.\r\n").unwrap(); | ||
| 161 | tx.blocking_write(b"Starting scatter-gather transfer with .await...\r\n\r\n") | ||
| 162 | .unwrap(); | ||
| 163 | |||
| 164 | // Build and execute the scatter-gather chain | ||
| 165 | // The build() method: | ||
| 166 | // - Links all TCDs together with ESG bit | ||
| 167 | // - Sets INTMAJOR on all TCDs | ||
| 168 | // - Loads the first TCD into hardware | ||
| 169 | // - Returns a Transfer future | ||
| 170 | unsafe { | ||
| 171 | let transfer = builder.build(&dma_ch0).expect("Failed to build scatter-gather"); | ||
| 172 | transfer.blocking_wait(); | ||
| 173 | } | ||
| 174 | |||
| 175 | tx.blocking_write(b"Scatter-gather transfer complete!\r\n\r\n").unwrap(); | ||
| 176 | |||
| 177 | // Show results | ||
| 178 | tx.blocking_write(b"Destination buffers (after):\r\n").unwrap(); | ||
| 179 | tx.blocking_write(b" DST1: ").unwrap(); | ||
| 180 | print_buffer(&mut tx, core::ptr::addr_of!(DST1) as *const u32, 4); | ||
| 181 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 182 | tx.blocking_write(b" DST2: ").unwrap(); | ||
| 183 | print_buffer(&mut tx, core::ptr::addr_of!(DST2) as *const u32, 4); | ||
| 184 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 185 | tx.blocking_write(b" DST3: ").unwrap(); | ||
| 186 | print_buffer(&mut tx, core::ptr::addr_of!(DST3) as *const u32, 4); | ||
| 187 | tx.blocking_write(b"\r\n\r\n").unwrap(); | ||
| 188 | |||
| 189 | // Verify all three segments | ||
| 190 | let mut all_ok = true; | ||
| 191 | unsafe { | ||
| 192 | let src1 = core::ptr::addr_of!(SRC1) as *const u32; | ||
| 193 | let dst1 = core::ptr::addr_of!(DST1) as *const u32; | ||
| 194 | for i in 0..4 { | ||
| 195 | if *src1.add(i) != *dst1.add(i) { | ||
| 196 | all_ok = false; | ||
| 197 | } | ||
| 198 | } | ||
| 199 | |||
| 200 | let src2 = core::ptr::addr_of!(SRC2) as *const u32; | ||
| 201 | let dst2 = core::ptr::addr_of!(DST2) as *const u32; | ||
| 202 | for i in 0..4 { | ||
| 203 | if *src2.add(i) != *dst2.add(i) { | ||
| 204 | all_ok = false; | ||
| 205 | } | ||
| 206 | } | ||
| 207 | |||
| 208 | let src3 = core::ptr::addr_of!(SRC3) as *const u32; | ||
| 209 | let dst3 = core::ptr::addr_of!(DST3) as *const u32; | ||
| 210 | for i in 0..4 { | ||
| 211 | if *src3.add(i) != *dst3.add(i) { | ||
| 212 | all_ok = false; | ||
| 213 | } | ||
| 214 | } | ||
| 215 | } | ||
| 216 | |||
| 217 | if all_ok { | ||
| 218 | tx.blocking_write(b"PASS: All segments verified!\r\n").unwrap(); | ||
| 219 | defmt::info!("PASS: All segments verified!"); | ||
| 220 | } else { | ||
| 221 | tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); | ||
| 222 | defmt::error!("FAIL: Mismatch detected!"); | ||
| 223 | } | ||
| 224 | |||
| 225 | tx.blocking_write(b"\r\n=== Scatter-Gather Builder example complete ===\r\n") | ||
| 226 | .unwrap(); | ||
| 227 | |||
| 228 | loop { | ||
| 229 | cortex_m::asm::wfe(); | ||
| 230 | } | ||
| 231 | } | ||
diff --git a/examples/mcxa/src/bin/dma_wrap_transfer.rs b/examples/mcxa/src/bin/dma_wrap_transfer.rs new file mode 100644 index 000000000..82936d9d0 --- /dev/null +++ b/examples/mcxa/src/bin/dma_wrap_transfer.rs | |||
| @@ -0,0 +1,222 @@ | |||
| 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 | //! - `DmaChannel::is_done()` and `clear_done()` helper methods | ||
| 8 | //! - No need to pass register block around | ||
| 9 | |||
| 10 | #![no_std] | ||
| 11 | #![no_main] | ||
| 12 | |||
| 13 | use embassy_executor::Spawner; | ||
| 14 | use embassy_mcxa::clocks::config::Div8; | ||
| 15 | use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaChannel}; | ||
| 16 | use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; | ||
| 17 | use embassy_mcxa::{bind_interrupts, pac}; | ||
| 18 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 19 | |||
| 20 | // Bind DMA channel 0 interrupt using Embassy-style macro | ||
| 21 | bind_interrupts!(struct Irqs { | ||
| 22 | DMA_CH0 => DmaCh0InterruptHandler; | ||
| 23 | }); | ||
| 24 | |||
| 25 | // Source buffer: 4 words (16 bytes), aligned to 16 bytes for modulo | ||
| 26 | #[repr(align(16))] | ||
| 27 | struct AlignedSrc([u32; 4]); | ||
| 28 | |||
| 29 | static mut SRC: AlignedSrc = AlignedSrc([0; 4]); | ||
| 30 | static mut DST: [u32; 8] = [0; 8]; | ||
| 31 | |||
| 32 | /// Helper to write a u32 as decimal ASCII to UART | ||
| 33 | fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { | ||
| 34 | let mut buf = [0u8; 10]; | ||
| 35 | let mut n = val; | ||
| 36 | let mut i = buf.len(); | ||
| 37 | |||
| 38 | if n == 0 { | ||
| 39 | tx.blocking_write(b"0").ok(); | ||
| 40 | return; | ||
| 41 | } | ||
| 42 | |||
| 43 | while n > 0 { | ||
| 44 | i -= 1; | ||
| 45 | buf[i] = b'0' + (n % 10) as u8; | ||
| 46 | n /= 10; | ||
| 47 | } | ||
| 48 | |||
| 49 | tx.blocking_write(&buf[i..]).ok(); | ||
| 50 | } | ||
| 51 | |||
| 52 | /// Helper to print a buffer to UART | ||
| 53 | fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { | ||
| 54 | tx.blocking_write(b"[").ok(); | ||
| 55 | unsafe { | ||
| 56 | for i in 0..len { | ||
| 57 | write_u32(tx, *buf_ptr.add(i)); | ||
| 58 | if i < len - 1 { | ||
| 59 | tx.blocking_write(b", ").ok(); | ||
| 60 | } | ||
| 61 | } | ||
| 62 | } | ||
| 63 | tx.blocking_write(b"]").ok(); | ||
| 64 | } | ||
| 65 | |||
| 66 | #[embassy_executor::main] | ||
| 67 | async fn main(_spawner: Spawner) { | ||
| 68 | // Small delay to allow probe-rs to attach after reset | ||
| 69 | for _ in 0..100_000 { | ||
| 70 | cortex_m::asm::nop(); | ||
| 71 | } | ||
| 72 | |||
| 73 | let mut cfg = hal::config::Config::default(); | ||
| 74 | cfg.clock_cfg.sirc.fro_12m_enabled = true; | ||
| 75 | cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); | ||
| 76 | let p = hal::init(cfg); | ||
| 77 | |||
| 78 | defmt::info!("DMA wrap transfer example starting..."); | ||
| 79 | |||
| 80 | // Enable DMA interrupt (DMA clock/reset/init is handled automatically by HAL) | ||
| 81 | unsafe { | ||
| 82 | cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); | ||
| 83 | } | ||
| 84 | |||
| 85 | let config = Config { | ||
| 86 | baudrate_bps: 115_200, | ||
| 87 | ..Default::default() | ||
| 88 | }; | ||
| 89 | |||
| 90 | let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); | ||
| 91 | let (mut tx, _rx) = lpuart.split(); | ||
| 92 | |||
| 93 | tx.blocking_write(b"EDMA wrap transfer example begin.\r\n\r\n").unwrap(); | ||
| 94 | |||
| 95 | // Initialize buffers | ||
| 96 | unsafe { | ||
| 97 | SRC.0 = [1, 2, 3, 4]; | ||
| 98 | DST = [0; 8]; | ||
| 99 | } | ||
| 100 | |||
| 101 | tx.blocking_write(b"Source Buffer: ").unwrap(); | ||
| 102 | print_buffer(&mut tx, unsafe { core::ptr::addr_of!(SRC.0) } as *const u32, 4); | ||
| 103 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 104 | |||
| 105 | tx.blocking_write(b"Destination Buffer (before): ").unwrap(); | ||
| 106 | print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); | ||
| 107 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 108 | |||
| 109 | tx.blocking_write(b"Configuring DMA with Embassy-style API...\r\n") | ||
| 110 | .unwrap(); | ||
| 111 | |||
| 112 | // Create DMA channel using Embassy-style API | ||
| 113 | let dma_ch0 = DmaChannel::new(p.DMA_CH0); | ||
| 114 | |||
| 115 | // Configure wrap transfer using direct TCD access: | ||
| 116 | // SRC is 16 bytes (4 * u32). We want to transfer 32 bytes (8 * u32). | ||
| 117 | // SRC modulo is 16 bytes (2^4 = 16) - wraps source address. | ||
| 118 | // DST modulo is 0 (disabled). | ||
| 119 | // This causes the source address to wrap around after 16 bytes, | ||
| 120 | // effectively repeating the source data. | ||
| 121 | unsafe { | ||
| 122 | let t = dma_ch0.tcd(); | ||
| 123 | |||
| 124 | // Reset channel state | ||
| 125 | t.ch_csr().write(|w| { | ||
| 126 | w.erq() | ||
| 127 | .disable() | ||
| 128 | .earq() | ||
| 129 | .disable() | ||
| 130 | .eei() | ||
| 131 | .no_error() | ||
| 132 | .ebw() | ||
| 133 | .disable() | ||
| 134 | .done() | ||
| 135 | .clear_bit_by_one() | ||
| 136 | }); | ||
| 137 | t.ch_es().write(|w| w.bits(0)); | ||
| 138 | t.ch_int().write(|w| w.int().clear_bit_by_one()); | ||
| 139 | |||
| 140 | // Source/destination addresses | ||
| 141 | t.tcd_saddr() | ||
| 142 | .write(|w| w.saddr().bits(core::ptr::addr_of!(SRC.0) as u32)); | ||
| 143 | t.tcd_daddr() | ||
| 144 | .write(|w| w.daddr().bits(core::ptr::addr_of_mut!(DST) as u32)); | ||
| 145 | |||
| 146 | // Offsets: both increment by 4 bytes | ||
| 147 | t.tcd_soff().write(|w| w.soff().bits(4)); | ||
| 148 | t.tcd_doff().write(|w| w.doff().bits(4)); | ||
| 149 | |||
| 150 | // Attributes: 32-bit transfers (size = 2) | ||
| 151 | // SMOD = 4 (2^4 = 16 byte modulo for source), DMOD = 0 (disabled) | ||
| 152 | t.tcd_attr().write(|w| { | ||
| 153 | w.ssize() | ||
| 154 | .bits(2) | ||
| 155 | .dsize() | ||
| 156 | .bits(2) | ||
| 157 | .smod() | ||
| 158 | .bits(4) // Source modulo: 2^4 = 16 bytes | ||
| 159 | .dmod() | ||
| 160 | .bits(0) // Dest modulo: disabled | ||
| 161 | }); | ||
| 162 | |||
| 163 | // Transfer 32 bytes total in one minor loop | ||
| 164 | let nbytes = 32u32; | ||
| 165 | t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(nbytes)); | ||
| 166 | |||
| 167 | // Source wraps via modulo, no adjustment needed | ||
| 168 | t.tcd_slast_sda().write(|w| w.slast_sda().bits(0)); | ||
| 169 | // Reset dest address after major loop | ||
| 170 | t.tcd_dlast_sga().write(|w| w.dlast_sga().bits(-(nbytes as i32) as u32)); | ||
| 171 | |||
| 172 | // Major loop count = 1 | ||
| 173 | t.tcd_biter_elinkno().write(|w| w.biter().bits(1)); | ||
| 174 | t.tcd_citer_elinkno().write(|w| w.citer().bits(1)); | ||
| 175 | |||
| 176 | // Enable interrupt on major loop completion | ||
| 177 | t.tcd_csr().write(|w| w.intmajor().set_bit()); | ||
| 178 | |||
| 179 | cortex_m::asm::dsb(); | ||
| 180 | |||
| 181 | tx.blocking_write(b"Triggering transfer...\r\n").unwrap(); | ||
| 182 | dma_ch0.trigger_start(); | ||
| 183 | } | ||
| 184 | |||
| 185 | // Wait for completion using channel helper method | ||
| 186 | while !dma_ch0.is_done() { | ||
| 187 | cortex_m::asm::nop(); | ||
| 188 | } | ||
| 189 | unsafe { | ||
| 190 | dma_ch0.clear_done(); | ||
| 191 | } | ||
| 192 | |||
| 193 | tx.blocking_write(b"\r\nEDMA wrap transfer example finish.\r\n\r\n") | ||
| 194 | .unwrap(); | ||
| 195 | tx.blocking_write(b"Destination Buffer (after): ").unwrap(); | ||
| 196 | print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); | ||
| 197 | tx.blocking_write(b"\r\n\r\n").unwrap(); | ||
| 198 | |||
| 199 | // Verify: DST should be [1, 2, 3, 4, 1, 2, 3, 4] | ||
| 200 | let expected = [1u32, 2, 3, 4, 1, 2, 3, 4]; | ||
| 201 | let mut mismatch = false; | ||
| 202 | unsafe { | ||
| 203 | for i in 0..8 { | ||
| 204 | if DST[i] != expected[i] { | ||
| 205 | mismatch = true; | ||
| 206 | break; | ||
| 207 | } | ||
| 208 | } | ||
| 209 | } | ||
| 210 | |||
| 211 | if mismatch { | ||
| 212 | tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); | ||
| 213 | defmt::error!("FAIL: Mismatch detected!"); | ||
| 214 | } else { | ||
| 215 | tx.blocking_write(b"PASS: Data verified.\r\n").unwrap(); | ||
| 216 | defmt::info!("PASS: Data verified."); | ||
| 217 | } | ||
| 218 | |||
| 219 | loop { | ||
| 220 | cortex_m::asm::wfe(); | ||
| 221 | } | ||
| 222 | } | ||
diff --git a/examples/mcxa/src/bin/lpuart_dma.rs b/examples/mcxa/src/bin/lpuart_dma.rs new file mode 100644 index 000000000..5497f8646 --- /dev/null +++ b/examples/mcxa/src/bin/lpuart_dma.rs | |||
| @@ -0,0 +1,81 @@ | |||
| 1 | //! LPUART DMA example for MCXA276. | ||
| 2 | //! | ||
| 3 | //! This example demonstrates using DMA for UART TX and RX operations. | ||
| 4 | //! It sends a message using DMA, then waits for 16 characters to be received | ||
| 5 | //! via DMA and echoes them back. | ||
| 6 | //! | ||
| 7 | //! The DMA request sources are automatically derived from the LPUART instance type. | ||
| 8 | //! DMA clock/reset/init is handled automatically by the HAL. | ||
| 9 | |||
| 10 | #![no_std] | ||
| 11 | #![no_main] | ||
| 12 | |||
| 13 | use embassy_executor::Spawner; | ||
| 14 | use embassy_mcxa::clocks::config::Div8; | ||
| 15 | use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaCh1InterruptHandler}; | ||
| 16 | use embassy_mcxa::lpuart::{Config, LpuartDma}; | ||
| 17 | use embassy_mcxa::{bind_interrupts, pac}; | ||
| 18 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 19 | |||
| 20 | // Bind DMA channel interrupts using Embassy-style macro | ||
| 21 | bind_interrupts!(struct Irqs { | ||
| 22 | DMA_CH0 => DmaCh0InterruptHandler; | ||
| 23 | DMA_CH1 => DmaCh1InterruptHandler; | ||
| 24 | }); | ||
| 25 | |||
| 26 | #[embassy_executor::main] | ||
| 27 | async fn main(_spawner: Spawner) { | ||
| 28 | let mut cfg = hal::config::Config::default(); | ||
| 29 | cfg.clock_cfg.sirc.fro_12m_enabled = true; | ||
| 30 | cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); | ||
| 31 | let p = hal::init(cfg); | ||
| 32 | |||
| 33 | defmt::info!("LPUART DMA example starting..."); | ||
| 34 | |||
| 35 | // Enable DMA interrupts (per-channel, as needed) | ||
| 36 | unsafe { | ||
| 37 | cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); | ||
| 38 | cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH1); | ||
| 39 | } | ||
| 40 | |||
| 41 | // Create UART configuration | ||
| 42 | let config = Config { | ||
| 43 | baudrate_bps: 115_200, | ||
| 44 | ..Default::default() | ||
| 45 | }; | ||
| 46 | |||
| 47 | // Create UART instance with DMA channels | ||
| 48 | let mut lpuart = LpuartDma::new( | ||
| 49 | p.LPUART2, p.P2_2, // TX pin | ||
| 50 | p.P2_3, // RX pin | ||
| 51 | p.DMA_CH0, // TX DMA channel | ||
| 52 | p.DMA_CH1, // RX DMA channel | ||
| 53 | config, | ||
| 54 | ) | ||
| 55 | .unwrap(); | ||
| 56 | |||
| 57 | // Send a message using DMA (DMA request source is automatically derived from LPUART2) | ||
| 58 | let tx_msg = b"Hello from LPUART2 DMA TX!\r\n"; | ||
| 59 | lpuart.write_dma(tx_msg).await.unwrap(); | ||
| 60 | |||
| 61 | defmt::info!("TX DMA complete"); | ||
| 62 | |||
| 63 | // Send prompt | ||
| 64 | let prompt = b"Type 16 characters to echo via DMA:\r\n"; | ||
| 65 | lpuart.write_dma(prompt).await.unwrap(); | ||
| 66 | |||
| 67 | // Receive 16 characters using DMA | ||
| 68 | let mut rx_buf = [0u8; 16]; | ||
| 69 | lpuart.read_dma(&mut rx_buf).await.unwrap(); | ||
| 70 | |||
| 71 | defmt::info!("RX DMA complete"); | ||
| 72 | |||
| 73 | // Echo back the received data | ||
| 74 | let echo_prefix = b"\r\nReceived: "; | ||
| 75 | lpuart.write_dma(echo_prefix).await.unwrap(); | ||
| 76 | lpuart.write_dma(&rx_buf).await.unwrap(); | ||
| 77 | let done_msg = b"\r\nDone!\r\n"; | ||
| 78 | lpuart.write_dma(done_msg).await.unwrap(); | ||
| 79 | |||
| 80 | defmt::info!("Example complete"); | ||
| 81 | } | ||
diff --git a/examples/mcxa/src/bin/lpuart_ring_buffer.rs b/examples/mcxa/src/bin/lpuart_ring_buffer.rs new file mode 100644 index 000000000..1d1a51970 --- /dev/null +++ b/examples/mcxa/src/bin/lpuart_ring_buffer.rs | |||
| @@ -0,0 +1,130 @@ | |||
| 1 | //! LPUART Ring Buffer DMA example for MCXA276. | ||
| 2 | //! | ||
| 3 | //! This example demonstrates using the high-level `LpuartRxDma::setup_ring_buffer()` | ||
| 4 | //! API for continuous circular DMA reception from a UART peripheral. | ||
| 5 | //! | ||
| 6 | //! # Features demonstrated: | ||
| 7 | //! - `LpuartRxDma::setup_ring_buffer()` for continuous peripheral-to-memory DMA | ||
| 8 | //! - `RingBuffer` for async reading of received data | ||
| 9 | //! - Handling of potential overrun conditions | ||
| 10 | //! - Half-transfer and complete-transfer interrupts for timely wakeups | ||
| 11 | //! | ||
| 12 | //! # How it works: | ||
| 13 | //! 1. Create an `LpuartRxDma` driver with a DMA channel | ||
| 14 | //! 2. Call `setup_ring_buffer()` which handles all low-level DMA configuration | ||
| 15 | //! 3. Application asynchronously reads data as it arrives via `ring_buf.read()` | ||
| 16 | //! 4. Both half-transfer and complete-transfer interrupts wake the reader | ||
| 17 | |||
| 18 | #![no_std] | ||
| 19 | #![no_main] | ||
| 20 | |||
| 21 | use embassy_executor::Spawner; | ||
| 22 | use embassy_mcxa::bind_interrupts; | ||
| 23 | use embassy_mcxa::clocks::config::Div8; | ||
| 24 | use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaCh1InterruptHandler}; | ||
| 25 | use embassy_mcxa::lpuart::{Config, LpuartDma, LpuartTxDma}; | ||
| 26 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 27 | |||
| 28 | // Bind DMA channel interrupts | ||
| 29 | bind_interrupts!(struct Irqs { | ||
| 30 | DMA_CH0 => DmaCh0InterruptHandler; | ||
| 31 | DMA_CH1 => DmaCh1InterruptHandler; | ||
| 32 | }); | ||
| 33 | |||
| 34 | // Ring buffer for RX - power of 2 is ideal for modulo efficiency | ||
| 35 | static mut RX_RING_BUFFER: [u8; 64] = [0; 64]; | ||
| 36 | |||
| 37 | /// Helper to write a byte as hex to UART | ||
| 38 | fn write_hex<T: embassy_mcxa::lpuart::Instance, C: embassy_mcxa::dma::Channel>( | ||
| 39 | tx: &mut LpuartTxDma<'_, T, C>, | ||
| 40 | byte: u8, | ||
| 41 | ) { | ||
| 42 | const HEX: &[u8; 16] = b"0123456789ABCDEF"; | ||
| 43 | let buf = [HEX[(byte >> 4) as usize], HEX[(byte & 0x0F) as usize]]; | ||
| 44 | tx.blocking_write(&buf).ok(); | ||
| 45 | } | ||
| 46 | |||
| 47 | #[embassy_executor::main] | ||
| 48 | async fn main(_spawner: Spawner) { | ||
| 49 | // Small delay to allow probe-rs to attach after reset | ||
| 50 | for _ in 0..100_000 { | ||
| 51 | cortex_m::asm::nop(); | ||
| 52 | } | ||
| 53 | |||
| 54 | let mut cfg = hal::config::Config::default(); | ||
| 55 | cfg.clock_cfg.sirc.fro_12m_enabled = true; | ||
| 56 | cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); | ||
| 57 | let p = hal::init(cfg); | ||
| 58 | |||
| 59 | defmt::info!("LPUART Ring Buffer DMA example starting..."); | ||
| 60 | |||
| 61 | // Create UART configuration | ||
| 62 | let config = Config { | ||
| 63 | baudrate_bps: 115_200, | ||
| 64 | ..Default::default() | ||
| 65 | }; | ||
| 66 | |||
| 67 | // Create LPUART with DMA support for both TX and RX, then split | ||
| 68 | // This is the proper Embassy pattern - create once, split into TX and RX | ||
| 69 | let lpuart = LpuartDma::new(p.LPUART2, p.P2_2, p.P2_3, p.DMA_CH1, p.DMA_CH0, config).unwrap(); | ||
| 70 | let (mut tx, rx) = lpuart.split(); | ||
| 71 | |||
| 72 | tx.blocking_write(b"LPUART Ring Buffer DMA Example\r\n").unwrap(); | ||
| 73 | tx.blocking_write(b"==============================\r\n\r\n").unwrap(); | ||
| 74 | |||
| 75 | tx.blocking_write(b"Setting up circular DMA for UART RX...\r\n") | ||
| 76 | .unwrap(); | ||
| 77 | |||
| 78 | // Set up the ring buffer with circular DMA | ||
| 79 | // The HAL handles: DMA request source, RDMAE enable, circular transfer config, NVIC enable | ||
| 80 | let ring_buf = unsafe { | ||
| 81 | let buf = &mut *core::ptr::addr_of_mut!(RX_RING_BUFFER); | ||
| 82 | rx.setup_ring_buffer(buf) | ||
| 83 | }; | ||
| 84 | |||
| 85 | // Enable DMA requests to start continuous reception | ||
| 86 | unsafe { | ||
| 87 | rx.enable_dma_request(); | ||
| 88 | } | ||
| 89 | |||
| 90 | tx.blocking_write(b"Ring buffer ready! Type characters to see them echoed.\r\n") | ||
| 91 | .unwrap(); | ||
| 92 | tx.blocking_write(b"The DMA continuously receives in the background.\r\n\r\n") | ||
| 93 | .unwrap(); | ||
| 94 | |||
| 95 | // Main loop: read from ring buffer and echo back | ||
| 96 | let mut read_buf = [0u8; 16]; | ||
| 97 | let mut total_received: usize = 0; | ||
| 98 | |||
| 99 | loop { | ||
| 100 | // Async read - waits until data is available | ||
| 101 | match ring_buf.read(&mut read_buf).await { | ||
| 102 | Ok(n) if n > 0 => { | ||
| 103 | total_received += n; | ||
| 104 | |||
| 105 | // Echo back what we received | ||
| 106 | tx.blocking_write(b"RX[").unwrap(); | ||
| 107 | for (i, &byte) in read_buf.iter().enumerate().take(n) { | ||
| 108 | write_hex(&mut tx, byte); | ||
| 109 | if i < n - 1 { | ||
| 110 | tx.blocking_write(b" ").unwrap(); | ||
| 111 | } | ||
| 112 | } | ||
| 113 | tx.blocking_write(b"]: ").unwrap(); | ||
| 114 | tx.blocking_write(&read_buf[..n]).unwrap(); | ||
| 115 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 116 | |||
| 117 | defmt::info!("Received {} bytes, total: {}", n, total_received); | ||
| 118 | } | ||
| 119 | Ok(_) => { | ||
| 120 | // No data, shouldn't happen with async read | ||
| 121 | } | ||
| 122 | Err(_) => { | ||
| 123 | // Overrun detected | ||
| 124 | tx.blocking_write(b"ERROR: Ring buffer overrun!\r\n").unwrap(); | ||
| 125 | defmt::error!("Ring buffer overrun!"); | ||
| 126 | ring_buf.clear(); | ||
| 127 | } | ||
| 128 | } | ||
| 129 | } | ||
| 130 | } | ||
