diff options
| author | Bogdan Petru Chircu Mare <[email protected]> | 2025-11-25 22:09:01 -0800 |
|---|---|---|
| committer | Bogdan Petru Chircu Mare <[email protected]> | 2025-11-28 12:34:24 -0800 |
| commit | 03356a261801d7ee234490809eef3eac3c27cc52 (patch) | |
| tree | 23de784ea642f65ce5c02fdcb111ee314a4ca97f /examples/src/bin | |
| parent | 87c4eaf3380505ca15ef7ed1d5dc435e9af2200e (diff) | |
feat(dma): add DMA driver with 10 verified examples
Initial DMA driver implementation for MCXA276 with:
Core DMA Features:
- DmaChannel type with ownership tracking via Channel trait
- Transfer, RingBuffer, and ScatterGatherBuilder abstractions
- Support for mem-to-mem, mem-to-peripheral, peripheral-to-mem transfers
- Interrupt-driven completion with embassy async/await integration
- Word size abstraction (u8, u16, u32) via Word trait
LPUART DMA Integration:
- LpuartTxDma and LpuartRxDma drivers for async UART with DMA
- LpuartDma combined TX/RX driver
- Automatic chunking for buffers > 0x7FFF bytes
- DMA guards with Drop impl for safe cancellation
10 Verified Examples:
- dma_mem2mem: Basic memory-to-memory copy
- dma_memset: Memory fill with pattern
- dma_uart_tx: UART transmit via DMA
- dma_uart_rx: UART receive via DMA
- dma_uart_loopback: Combined TX/RX loopback test
- dma_scatter_gather: Linked descriptor chains
- dma_channel_linking: Major/minor loop channel linking
- dma_ring_buffer: Circular buffer for continuous streaming
- dma_ping_pong: Double-buffering pattern
- dma_software_trigger: Manual transfer triggering
PR Feedback Addressed:
- Use PAC accessor for LPUART DATA register instead of manual offset
- Add EnableInterrupt enum to replace boolean parameter for readability
- Add DMA guards with Drop impl for safe async cancellation
- Automatic chunking for large buffers instead of returning error
- Use NonNull<[W]> + PhantomData for RingBuffer (DMA acts like separate thread)
- Remove edma parameter from all methods (single eDMA instance steals ptr internally)
- Make edma_tcd() non-public (HAL should not expose PAC items)
Diffstat (limited to 'examples/src/bin')
| -rw-r--r-- | examples/src/bin/dma_channel_link.rs | 396 | ||||
| -rw-r--r-- | examples/src/bin/dma_interleave_transfer.rs | 226 | ||||
| -rw-r--r-- | examples/src/bin/dma_mem_to_mem.rs | 248 | ||||
| -rw-r--r-- | examples/src/bin/dma_memset.rs | 232 | ||||
| -rw-r--r-- | examples/src/bin/dma_ping_pong_transfer.rs | 384 | ||||
| -rw-r--r-- | examples/src/bin/dma_scatter_gather.rs | 281 | ||||
| -rw-r--r-- | examples/src/bin/dma_scatter_gather_builder.rs | 244 | ||||
| -rw-r--r-- | examples/src/bin/dma_wrap_transfer.rs | 231 | ||||
| -rw-r--r-- | examples/src/bin/lpuart_dma.rs | 127 | ||||
| -rw-r--r-- | examples/src/bin/lpuart_ring_buffer.rs | 162 |
10 files changed, 2531 insertions, 0 deletions
diff --git a/examples/src/bin/dma_channel_link.rs b/examples/src/bin/dma_channel_link.rs new file mode 100644 index 000000000..d585f8e3a --- /dev/null +++ b/examples/src/bin/dma_channel_link.rs | |||
| @@ -0,0 +1,396 @@ | |||
| 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 | //! - `dma::edma_tcd()` accessor for simplified register access | ||
| 12 | //! - `DmaChannel::new()` for channel creation | ||
| 13 | //! - `DmaChannel::is_done()` and `clear_done()` helper methods | ||
| 14 | //! - Channel linking with `set_minor_link()` and `set_major_link()` | ||
| 15 | |||
| 16 | #![no_std] | ||
| 17 | #![no_main] | ||
| 18 | |||
| 19 | use core::sync::atomic::{AtomicBool, Ordering}; | ||
| 20 | use embassy_executor::Spawner; | ||
| 21 | use embassy_mcxa::clocks::config::Div8; | ||
| 22 | use embassy_mcxa::clocks::Gate; | ||
| 23 | use embassy_mcxa::dma::{edma_tcd, DmaChannel}; | ||
| 24 | use embassy_mcxa::{bind_interrupts, dma}; | ||
| 25 | use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; | ||
| 26 | use embassy_mcxa::pac; | ||
| 27 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 28 | |||
| 29 | // Buffers | ||
| 30 | static mut SRC_BUFFER: [u32; 4] = [1, 2, 3, 4]; | ||
| 31 | static mut DEST_BUFFER0: [u32; 4] = [0; 4]; | ||
| 32 | static mut DEST_BUFFER1: [u32; 4] = [0; 4]; | ||
| 33 | static mut DEST_BUFFER2: [u32; 4] = [0; 4]; | ||
| 34 | |||
| 35 | static DMA_CH2_DONE: AtomicBool = AtomicBool::new(false); | ||
| 36 | |||
| 37 | // Custom DMA interrupt handlers for channel linking | ||
| 38 | // CH0 and CH1 just clear flags, CH2 signals completion | ||
| 39 | |||
| 40 | pub struct Ch0Handler; | ||
| 41 | impl embassy_mcxa::interrupt::typelevel::Handler<embassy_mcxa::interrupt::typelevel::DMA_CH0> for Ch0Handler { | ||
| 42 | unsafe fn on_interrupt() { | ||
| 43 | let edma = edma_tcd(); | ||
| 44 | edma.tcd(0).ch_int().write(|w| w.int().clear_bit_by_one()); | ||
| 45 | if edma.tcd(0).ch_csr().read().done().bit_is_set() { | ||
| 46 | edma.tcd(0).ch_csr().write(|w| w.done().clear_bit_by_one()); | ||
| 47 | } | ||
| 48 | } | ||
| 49 | } | ||
| 50 | |||
| 51 | pub struct Ch1Handler; | ||
| 52 | impl embassy_mcxa::interrupt::typelevel::Handler<embassy_mcxa::interrupt::typelevel::DMA_CH1> for Ch1Handler { | ||
| 53 | unsafe fn on_interrupt() { | ||
| 54 | let edma = edma_tcd(); | ||
| 55 | edma.tcd(1).ch_int().write(|w| w.int().clear_bit_by_one()); | ||
| 56 | if edma.tcd(1).ch_csr().read().done().bit_is_set() { | ||
| 57 | edma.tcd(1).ch_csr().write(|w| w.done().clear_bit_by_one()); | ||
| 58 | } | ||
| 59 | } | ||
| 60 | } | ||
| 61 | |||
| 62 | pub struct Ch2Handler; | ||
| 63 | impl embassy_mcxa::interrupt::typelevel::Handler<embassy_mcxa::interrupt::typelevel::DMA_CH2> for Ch2Handler { | ||
| 64 | unsafe fn on_interrupt() { | ||
| 65 | let edma = edma_tcd(); | ||
| 66 | edma.tcd(2).ch_int().write(|w| w.int().clear_bit_by_one()); | ||
| 67 | if edma.tcd(2).ch_csr().read().done().bit_is_set() { | ||
| 68 | edma.tcd(2).ch_csr().write(|w| w.done().clear_bit_by_one()); | ||
| 69 | } | ||
| 70 | DMA_CH2_DONE.store(true, Ordering::Release); | ||
| 71 | } | ||
| 72 | } | ||
| 73 | |||
| 74 | bind_interrupts!(struct Irqs { | ||
| 75 | DMA_CH0 => Ch0Handler; | ||
| 76 | DMA_CH1 => Ch1Handler; | ||
| 77 | DMA_CH2 => Ch2Handler; | ||
| 78 | }); | ||
| 79 | |||
| 80 | /// Helper to write a u32 as decimal ASCII to UART | ||
| 81 | fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { | ||
| 82 | let mut buf = [0u8; 10]; | ||
| 83 | let mut n = val; | ||
| 84 | let mut i = buf.len(); | ||
| 85 | |||
| 86 | if n == 0 { | ||
| 87 | tx.blocking_write(b"0").ok(); | ||
| 88 | return; | ||
| 89 | } | ||
| 90 | |||
| 91 | while n > 0 { | ||
| 92 | i -= 1; | ||
| 93 | buf[i] = b'0' + (n % 10) as u8; | ||
| 94 | n /= 10; | ||
| 95 | } | ||
| 96 | |||
| 97 | tx.blocking_write(&buf[i..]).ok(); | ||
| 98 | } | ||
| 99 | |||
| 100 | /// Helper to print a buffer to UART | ||
| 101 | fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { | ||
| 102 | tx.blocking_write(b"[").ok(); | ||
| 103 | unsafe { | ||
| 104 | for i in 0..len { | ||
| 105 | write_u32(tx, *buf_ptr.add(i)); | ||
| 106 | if i < len - 1 { | ||
| 107 | tx.blocking_write(b", ").ok(); | ||
| 108 | } | ||
| 109 | } | ||
| 110 | } | ||
| 111 | tx.blocking_write(b"]").ok(); | ||
| 112 | } | ||
| 113 | |||
| 114 | #[embassy_executor::main] | ||
| 115 | async fn main(_spawner: Spawner) { | ||
| 116 | // Small delay to allow probe-rs to attach after reset | ||
| 117 | for _ in 0..100_000 { | ||
| 118 | cortex_m::asm::nop(); | ||
| 119 | } | ||
| 120 | |||
| 121 | let mut cfg = hal::config::Config::default(); | ||
| 122 | cfg.clock_cfg.sirc.fro_12m_enabled = true; | ||
| 123 | cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); | ||
| 124 | let p = hal::init(cfg); | ||
| 125 | |||
| 126 | defmt::info!("DMA channel link example starting..."); | ||
| 127 | |||
| 128 | // Enable DMA0 clock and release reset | ||
| 129 | unsafe { | ||
| 130 | hal::peripherals::DMA0::enable_clock(); | ||
| 131 | hal::peripherals::DMA0::release_reset(); | ||
| 132 | } | ||
| 133 | |||
| 134 | let pac_periphs = unsafe { pac::Peripherals::steal() }; | ||
| 135 | |||
| 136 | unsafe { | ||
| 137 | dma::init(&pac_periphs); | ||
| 138 | } | ||
| 139 | |||
| 140 | // Use edma_tcd() accessor instead of passing register block around | ||
| 141 | let edma = edma_tcd(); | ||
| 142 | let dma0 = &pac_periphs.dma0; | ||
| 143 | |||
| 144 | // Clear any residual state | ||
| 145 | for i in 0..3 { | ||
| 146 | let t = edma.tcd(i); | ||
| 147 | t.ch_csr().write(|w| w.erq().disable().done().clear_bit_by_one()); | ||
| 148 | t.ch_int().write(|w| w.int().clear_bit_by_one()); | ||
| 149 | t.ch_es().write(|w| w.err().clear_bit_by_one()); | ||
| 150 | t.ch_mux().write(|w| unsafe { w.bits(0) }); | ||
| 151 | } | ||
| 152 | |||
| 153 | // Clear Global Halt/Error state | ||
| 154 | dma0.mp_csr().modify(|_, w| { | ||
| 155 | w.halt().normal_operation() | ||
| 156 | .hae().normal_operation() | ||
| 157 | .ecx().normal_operation() | ||
| 158 | .cx().normal_operation() | ||
| 159 | }); | ||
| 160 | |||
| 161 | unsafe { | ||
| 162 | cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); | ||
| 163 | cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH1); | ||
| 164 | cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH2); | ||
| 165 | } | ||
| 166 | |||
| 167 | let config = Config { | ||
| 168 | baudrate_bps: 115_200, | ||
| 169 | enable_tx: true, | ||
| 170 | enable_rx: false, | ||
| 171 | ..Default::default() | ||
| 172 | }; | ||
| 173 | |||
| 174 | let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); | ||
| 175 | let (mut tx, _rx) = lpuart.split(); | ||
| 176 | |||
| 177 | tx.blocking_write(b"EDMA channel link example begin.\r\n\r\n") | ||
| 178 | .unwrap(); | ||
| 179 | |||
| 180 | // Initialize buffers | ||
| 181 | unsafe { | ||
| 182 | SRC_BUFFER = [1, 2, 3, 4]; | ||
| 183 | DEST_BUFFER0 = [0; 4]; | ||
| 184 | DEST_BUFFER1 = [0; 4]; | ||
| 185 | DEST_BUFFER2 = [0; 4]; | ||
| 186 | } | ||
| 187 | |||
| 188 | tx.blocking_write(b"Source Buffer: ").unwrap(); | ||
| 189 | print_buffer(&mut tx, core::ptr::addr_of!(SRC_BUFFER) as *const u32, 4); | ||
| 190 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 191 | |||
| 192 | tx.blocking_write(b"DEST0 (before): ").unwrap(); | ||
| 193 | print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER0) as *const u32, 4); | ||
| 194 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 195 | |||
| 196 | tx.blocking_write(b"DEST1 (before): ").unwrap(); | ||
| 197 | print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER1) as *const u32, 4); | ||
| 198 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 199 | |||
| 200 | tx.blocking_write(b"DEST2 (before): ").unwrap(); | ||
| 201 | print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER2) as *const u32, 4); | ||
| 202 | tx.blocking_write(b"\r\n\r\n").unwrap(); | ||
| 203 | |||
| 204 | tx.blocking_write(b"Configuring DMA channels with Embassy-style API...\r\n") | ||
| 205 | .unwrap(); | ||
| 206 | |||
| 207 | let ch0 = DmaChannel::new(p.DMA_CH0); | ||
| 208 | let ch1 = DmaChannel::new(p.DMA_CH1); | ||
| 209 | let _ch2 = DmaChannel::new(p.DMA_CH2); | ||
| 210 | |||
| 211 | // Configure channels using direct TCD access (advanced feature demo) | ||
| 212 | // This example demonstrates channel linking which requires direct TCD manipulation | ||
| 213 | |||
| 214 | // Helper to configure TCD for memory-to-memory transfer | ||
| 215 | // Parameters: channel, src, dst, width, nbytes (minor loop), count (major loop), interrupt | ||
| 216 | #[allow(clippy::too_many_arguments)] | ||
| 217 | unsafe fn configure_tcd( | ||
| 218 | edma: &embassy_mcxa::pac::edma_0_tcd0::RegisterBlock, | ||
| 219 | ch: usize, | ||
| 220 | src: u32, | ||
| 221 | dst: u32, | ||
| 222 | width: u8, | ||
| 223 | nbytes: u32, | ||
| 224 | count: u16, | ||
| 225 | enable_int: bool, | ||
| 226 | ) { | ||
| 227 | let t = edma.tcd(ch); | ||
| 228 | |||
| 229 | // Reset channel state | ||
| 230 | t.ch_csr().write(|w| { | ||
| 231 | w.erq().disable() | ||
| 232 | .earq().disable() | ||
| 233 | .eei().no_error() | ||
| 234 | .ebw().disable() | ||
| 235 | .done().clear_bit_by_one() | ||
| 236 | }); | ||
| 237 | t.ch_es().write(|w| w.bits(0)); | ||
| 238 | t.ch_int().write(|w| w.int().clear_bit_by_one()); | ||
| 239 | |||
| 240 | // Source/destination addresses | ||
| 241 | t.tcd_saddr().write(|w| w.saddr().bits(src)); | ||
| 242 | t.tcd_daddr().write(|w| w.daddr().bits(dst)); | ||
| 243 | |||
| 244 | // Offsets: increment by width | ||
| 245 | t.tcd_soff().write(|w| w.soff().bits(width as u16)); | ||
| 246 | t.tcd_doff().write(|w| w.doff().bits(width as u16)); | ||
| 247 | |||
| 248 | // Attributes: size = log2(width) | ||
| 249 | let size = match width { | ||
| 250 | 1 => 0, | ||
| 251 | 2 => 1, | ||
| 252 | 4 => 2, | ||
| 253 | _ => 0, | ||
| 254 | }; | ||
| 255 | t.tcd_attr().write(|w| w.ssize().bits(size).dsize().bits(size)); | ||
| 256 | |||
| 257 | // Number of bytes per minor loop | ||
| 258 | t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(nbytes)); | ||
| 259 | |||
| 260 | // Major loop: reset source address after major loop | ||
| 261 | let total_bytes = nbytes * count as u32; | ||
| 262 | t.tcd_slast_sda().write(|w| w.slast_sda().bits(-(total_bytes as i32) as u32)); | ||
| 263 | t.tcd_dlast_sga().write(|w| w.dlast_sga().bits(-(total_bytes as i32) as u32)); | ||
| 264 | |||
| 265 | // Major loop count | ||
| 266 | t.tcd_biter_elinkno().write(|w| w.biter().bits(count)); | ||
| 267 | t.tcd_citer_elinkno().write(|w| w.citer().bits(count)); | ||
| 268 | |||
| 269 | // Control/status: enable interrupt if requested | ||
| 270 | if enable_int { | ||
| 271 | t.tcd_csr().write(|w| w.intmajor().set_bit()); | ||
| 272 | } else { | ||
| 273 | t.tcd_csr().write(|w| w.intmajor().clear_bit()); | ||
| 274 | } | ||
| 275 | |||
| 276 | cortex_m::asm::dsb(); | ||
| 277 | } | ||
| 278 | |||
| 279 | unsafe { | ||
| 280 | |||
| 281 | // Channel 0: Transfer 16 bytes total (8 bytes per minor loop, 2 major iterations) | ||
| 282 | // Minor Link -> Channel 1 | ||
| 283 | // Major Link -> Channel 2 | ||
| 284 | configure_tcd( | ||
| 285 | edma, | ||
| 286 | 0, | ||
| 287 | core::ptr::addr_of!(SRC_BUFFER) as u32, | ||
| 288 | core::ptr::addr_of_mut!(DEST_BUFFER0) as u32, | ||
| 289 | 4, // src width | ||
| 290 | 8, // nbytes (minor loop = 2 words) | ||
| 291 | 2, // count (major loop = 2 iterations) | ||
| 292 | false, // no interrupt | ||
| 293 | ); | ||
| 294 | ch0.set_minor_link(edma, 1); // Link to CH1 after each minor loop | ||
| 295 | ch0.set_major_link(edma, 2); // Link to CH2 after major loop | ||
| 296 | |||
| 297 | // Channel 1: Transfer 16 bytes (triggered by CH0 minor link) | ||
| 298 | configure_tcd( | ||
| 299 | edma, | ||
| 300 | 1, | ||
| 301 | core::ptr::addr_of!(SRC_BUFFER) as u32, | ||
| 302 | core::ptr::addr_of_mut!(DEST_BUFFER1) as u32, | ||
| 303 | 4, | ||
| 304 | 16, // full buffer in one minor loop | ||
| 305 | 1, // 1 major iteration | ||
| 306 | false, | ||
| 307 | ); | ||
| 308 | |||
| 309 | // Channel 2: Transfer 16 bytes (triggered by CH0 major link) | ||
| 310 | configure_tcd( | ||
| 311 | edma, | ||
| 312 | 2, | ||
| 313 | core::ptr::addr_of!(SRC_BUFFER) as u32, | ||
| 314 | core::ptr::addr_of_mut!(DEST_BUFFER2) as u32, | ||
| 315 | 4, | ||
| 316 | 16, // full buffer in one minor loop | ||
| 317 | 1, // 1 major iteration | ||
| 318 | true, // enable interrupt | ||
| 319 | ); | ||
| 320 | } | ||
| 321 | |||
| 322 | tx.blocking_write(b"Triggering Channel 0 (1st minor loop)...\r\n").unwrap(); | ||
| 323 | |||
| 324 | // Trigger first minor loop of CH0 | ||
| 325 | unsafe { ch0.trigger_start(edma); } | ||
| 326 | |||
| 327 | // Wait for CH1 to complete (triggered by CH0 minor link) | ||
| 328 | while !ch1.is_done(edma) { | ||
| 329 | cortex_m::asm::nop(); | ||
| 330 | } | ||
| 331 | unsafe { ch1.clear_done(edma); } | ||
| 332 | |||
| 333 | tx.blocking_write(b"CH1 done (via minor link).\r\n").unwrap(); | ||
| 334 | tx.blocking_write(b"Triggering Channel 0 (2nd minor loop)...\r\n").unwrap(); | ||
| 335 | |||
| 336 | // Trigger second minor loop of CH0 | ||
| 337 | unsafe { ch0.trigger_start(edma); } | ||
| 338 | |||
| 339 | // Wait for CH0 major loop to complete | ||
| 340 | while !ch0.is_done(edma) { | ||
| 341 | cortex_m::asm::nop(); | ||
| 342 | } | ||
| 343 | unsafe { ch0.clear_done(edma); } | ||
| 344 | |||
| 345 | tx.blocking_write(b"CH0 major loop done.\r\n").unwrap(); | ||
| 346 | |||
| 347 | // Wait for CH2 to complete (triggered by CH0 major link) | ||
| 348 | while !DMA_CH2_DONE.load(Ordering::Acquire) { | ||
| 349 | cortex_m::asm::nop(); | ||
| 350 | } | ||
| 351 | |||
| 352 | tx.blocking_write(b"CH2 done (via major link).\r\n\r\n").unwrap(); | ||
| 353 | |||
| 354 | tx.blocking_write(b"EDMA channel link example finish.\r\n\r\n") | ||
| 355 | .unwrap(); | ||
| 356 | |||
| 357 | tx.blocking_write(b"DEST0 (after): ").unwrap(); | ||
| 358 | print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER0) as *const u32, 4); | ||
| 359 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 360 | |||
| 361 | tx.blocking_write(b"DEST1 (after): ").unwrap(); | ||
| 362 | print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER1) as *const u32, 4); | ||
| 363 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 364 | |||
| 365 | tx.blocking_write(b"DEST2 (after): ").unwrap(); | ||
| 366 | print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER2) as *const u32, 4); | ||
| 367 | tx.blocking_write(b"\r\n\r\n").unwrap(); | ||
| 368 | |||
| 369 | // Verify all buffers match source | ||
| 370 | let mut success = true; | ||
| 371 | unsafe { | ||
| 372 | let src_ptr = core::ptr::addr_of!(SRC_BUFFER) as *const u32; | ||
| 373 | let dst0_ptr = core::ptr::addr_of!(DEST_BUFFER0) as *const u32; | ||
| 374 | let dst1_ptr = core::ptr::addr_of!(DEST_BUFFER1) as *const u32; | ||
| 375 | let dst2_ptr = core::ptr::addr_of!(DEST_BUFFER2) as *const u32; | ||
| 376 | |||
| 377 | for i in 0..4 { | ||
| 378 | if *dst0_ptr.add(i) != *src_ptr.add(i) { success = false; } | ||
| 379 | if *dst1_ptr.add(i) != *src_ptr.add(i) { success = false; } | ||
| 380 | if *dst2_ptr.add(i) != *src_ptr.add(i) { success = false; } | ||
| 381 | } | ||
| 382 | } | ||
| 383 | |||
| 384 | if success { | ||
| 385 | tx.blocking_write(b"PASS: Data verified.\r\n").unwrap(); | ||
| 386 | defmt::info!("PASS: Data verified."); | ||
| 387 | } else { | ||
| 388 | tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); | ||
| 389 | defmt::error!("FAIL: Mismatch detected!"); | ||
| 390 | } | ||
| 391 | |||
| 392 | loop { | ||
| 393 | cortex_m::asm::wfe(); | ||
| 394 | } | ||
| 395 | } | ||
| 396 | |||
diff --git a/examples/src/bin/dma_interleave_transfer.rs b/examples/src/bin/dma_interleave_transfer.rs new file mode 100644 index 000000000..710f18de3 --- /dev/null +++ b/examples/src/bin/dma_interleave_transfer.rs | |||
| @@ -0,0 +1,226 @@ | |||
| 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 | //! - `dma::edma_tcd()` accessor for simplified register access | ||
| 8 | //! - `TransferOptions::default()` for configuration (used internally) | ||
| 9 | //! - DMA channel with `DmaChannel::new()` | ||
| 10 | |||
| 11 | #![no_std] | ||
| 12 | #![no_main] | ||
| 13 | |||
| 14 | use embassy_executor::Spawner; | ||
| 15 | use embassy_mcxa::clocks::config::Div8; | ||
| 16 | use embassy_mcxa::clocks::Gate; | ||
| 17 | use embassy_mcxa::dma::{edma_tcd, DmaChannel, DmaCh0InterruptHandler}; | ||
| 18 | use embassy_mcxa::{bind_interrupts, dma}; | ||
| 19 | use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; | ||
| 20 | use embassy_mcxa::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 = 16; | ||
| 29 | const HALF_BUFF_LENGTH: usize = BUFFER_LENGTH / 2; | ||
| 30 | |||
| 31 | // Buffers in RAM | ||
| 32 | static mut SRC_BUFFER: [u32; HALF_BUFF_LENGTH] = [0; HALF_BUFF_LENGTH]; | ||
| 33 | static mut DEST_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]; | ||
| 38 | let mut n = val; | ||
| 39 | let mut i = buf.len(); | ||
| 40 | |||
| 41 | if n == 0 { | ||
| 42 | tx.blocking_write(b"0").ok(); | ||
| 43 | return; | ||
| 44 | } | ||
| 45 | |||
| 46 | while n > 0 { | ||
| 47 | i -= 1; | ||
| 48 | buf[i] = b'0' + (n % 10) as u8; | ||
| 49 | n /= 10; | ||
| 50 | } | ||
| 51 | |||
| 52 | tx.blocking_write(&buf[i..]).ok(); | ||
| 53 | } | ||
| 54 | |||
| 55 | /// Helper to print a buffer to UART | ||
| 56 | fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { | ||
| 57 | tx.blocking_write(b"[").ok(); | ||
| 58 | unsafe { | ||
| 59 | for i in 0..len { | ||
| 60 | write_u32(tx, *buf_ptr.add(i)); | ||
| 61 | if i < len - 1 { | ||
| 62 | tx.blocking_write(b", ").ok(); | ||
| 63 | } | ||
| 64 | } | ||
| 65 | } | ||
| 66 | tx.blocking_write(b"]").ok(); | ||
| 67 | } | ||
| 68 | |||
| 69 | #[embassy_executor::main] | ||
| 70 | async fn main(_spawner: Spawner) { | ||
| 71 | // Small delay to allow probe-rs to attach after reset | ||
| 72 | for _ in 0..100_000 { | ||
| 73 | cortex_m::asm::nop(); | ||
| 74 | } | ||
| 75 | |||
| 76 | let mut cfg = hal::config::Config::default(); | ||
| 77 | cfg.clock_cfg.sirc.fro_12m_enabled = true; | ||
| 78 | cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); | ||
| 79 | let p = hal::init(cfg); | ||
| 80 | |||
| 81 | defmt::info!("DMA interleave transfer example starting..."); | ||
| 82 | |||
| 83 | // Enable DMA0 clock and release reset | ||
| 84 | unsafe { | ||
| 85 | hal::peripherals::DMA0::enable_clock(); | ||
| 86 | hal::peripherals::DMA0::release_reset(); | ||
| 87 | } | ||
| 88 | |||
| 89 | let pac_periphs = unsafe { pac::Peripherals::steal() }; | ||
| 90 | |||
| 91 | unsafe { | ||
| 92 | dma::init(&pac_periphs); | ||
| 93 | } | ||
| 94 | |||
| 95 | // Enable DMA interrupt | ||
| 96 | unsafe { | ||
| 97 | cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); | ||
| 98 | } | ||
| 99 | |||
| 100 | let config = Config { | ||
| 101 | baudrate_bps: 115_200, | ||
| 102 | enable_tx: true, | ||
| 103 | enable_rx: false, | ||
| 104 | ..Default::default() | ||
| 105 | }; | ||
| 106 | |||
| 107 | let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); | ||
| 108 | let (mut tx, _rx) = lpuart.split(); | ||
| 109 | |||
| 110 | tx.blocking_write(b"EDMA interleave transfer example begin.\r\n\r\n") | ||
| 111 | .unwrap(); | ||
| 112 | |||
| 113 | // Initialize buffers | ||
| 114 | unsafe { | ||
| 115 | SRC_BUFFER = [1, 2, 3, 4, 5, 6, 7, 8]; | ||
| 116 | DEST_BUFFER = [0; BUFFER_LENGTH]; | ||
| 117 | } | ||
| 118 | |||
| 119 | tx.blocking_write(b"Source Buffer: ").unwrap(); | ||
| 120 | print_buffer(&mut tx, core::ptr::addr_of!(SRC_BUFFER) as *const u32, HALF_BUFF_LENGTH); | ||
| 121 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 122 | |||
| 123 | tx.blocking_write(b"Destination Buffer (before): ").unwrap(); | ||
| 124 | print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER) as *const u32, BUFFER_LENGTH); | ||
| 125 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 126 | |||
| 127 | tx.blocking_write(b"Configuring DMA with Embassy-style API...\r\n") | ||
| 128 | .unwrap(); | ||
| 129 | |||
| 130 | // Create DMA channel using Embassy-style API | ||
| 131 | let dma_ch0 = DmaChannel::new(p.DMA_CH0); | ||
| 132 | |||
| 133 | // Use edma_tcd() accessor instead of passing register block around | ||
| 134 | let edma = edma_tcd(); | ||
| 135 | |||
| 136 | // Configure interleaved transfer using direct TCD access: | ||
| 137 | // - src_offset = 4: advance source by 4 bytes after each read | ||
| 138 | // - dst_offset = 8: advance dest by 8 bytes after each write | ||
| 139 | // This spreads source data across every other word in destination | ||
| 140 | unsafe { | ||
| 141 | let t = edma.tcd(0); | ||
| 142 | |||
| 143 | // Reset channel state | ||
| 144 | t.ch_csr().write(|w| { | ||
| 145 | w.erq().disable() | ||
| 146 | .earq().disable() | ||
| 147 | .eei().no_error() | ||
| 148 | .ebw().disable() | ||
| 149 | .done().clear_bit_by_one() | ||
| 150 | }); | ||
| 151 | t.ch_es().write(|w| w.bits(0)); | ||
| 152 | t.ch_int().write(|w| w.int().clear_bit_by_one()); | ||
| 153 | |||
| 154 | // Source/destination addresses | ||
| 155 | t.tcd_saddr().write(|w| w.saddr().bits(core::ptr::addr_of_mut!(SRC_BUFFER) as u32)); | ||
| 156 | t.tcd_daddr().write(|w| w.daddr().bits(core::ptr::addr_of_mut!(DEST_BUFFER) as u32)); | ||
| 157 | |||
| 158 | // Custom offsets for interleaving | ||
| 159 | t.tcd_soff().write(|w| w.soff().bits(4)); // src: +4 bytes per read | ||
| 160 | t.tcd_doff().write(|w| w.doff().bits(8)); // dst: +8 bytes per write | ||
| 161 | |||
| 162 | // Attributes: 32-bit transfers (size = 2) | ||
| 163 | t.tcd_attr().write(|w| w.ssize().bits(2).dsize().bits(2)); | ||
| 164 | |||
| 165 | // Transfer entire source buffer in one minor loop | ||
| 166 | let nbytes = (HALF_BUFF_LENGTH * 4) as u32; | ||
| 167 | t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(nbytes)); | ||
| 168 | |||
| 169 | // Reset source address after major loop | ||
| 170 | t.tcd_slast_sda().write(|w| w.slast_sda().bits(-(nbytes as i32) as u32)); | ||
| 171 | // Destination uses 2x offset, so adjust accordingly | ||
| 172 | let dst_total = (HALF_BUFF_LENGTH * 8) as u32; | ||
| 173 | t.tcd_dlast_sga().write(|w| w.dlast_sga().bits(-(dst_total as i32) as u32)); | ||
| 174 | |||
| 175 | // Major loop count = 1 | ||
| 176 | t.tcd_biter_elinkno().write(|w| w.biter().bits(1)); | ||
| 177 | t.tcd_citer_elinkno().write(|w| w.citer().bits(1)); | ||
| 178 | |||
| 179 | // Enable interrupt on major loop completion | ||
| 180 | t.tcd_csr().write(|w| w.intmajor().set_bit()); | ||
| 181 | |||
| 182 | cortex_m::asm::dsb(); | ||
| 183 | |||
| 184 | tx.blocking_write(b"Triggering transfer...\r\n").unwrap(); | ||
| 185 | dma_ch0.trigger_start(edma); | ||
| 186 | } | ||
| 187 | |||
| 188 | // Wait for completion using channel helper method | ||
| 189 | while !dma_ch0.is_done(edma) { | ||
| 190 | cortex_m::asm::nop(); | ||
| 191 | } | ||
| 192 | unsafe { dma_ch0.clear_done(edma); } | ||
| 193 | |||
| 194 | tx.blocking_write(b"\r\nEDMA interleave transfer example finish.\r\n\r\n") | ||
| 195 | .unwrap(); | ||
| 196 | tx.blocking_write(b"Destination Buffer (after): ").unwrap(); | ||
| 197 | print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER) as *const u32, BUFFER_LENGTH); | ||
| 198 | tx.blocking_write(b"\r\n\r\n").unwrap(); | ||
| 199 | |||
| 200 | // Verify: Even indices should match SRC_BUFFER[i/2], odd indices should be 0 | ||
| 201 | let mut mismatch = false; | ||
| 202 | unsafe { | ||
| 203 | for i in 0..BUFFER_LENGTH { | ||
| 204 | if i % 2 == 0 { | ||
| 205 | if DEST_BUFFER[i] != SRC_BUFFER[i / 2] { | ||
| 206 | mismatch = true; | ||
| 207 | } | ||
| 208 | } else if DEST_BUFFER[i] != 0 { | ||
| 209 | mismatch = true; | ||
| 210 | } | ||
| 211 | } | ||
| 212 | } | ||
| 213 | |||
| 214 | if mismatch { | ||
| 215 | tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); | ||
| 216 | defmt::error!("FAIL: Mismatch detected!"); | ||
| 217 | } else { | ||
| 218 | tx.blocking_write(b"PASS: Data verified.\r\n").unwrap(); | ||
| 219 | defmt::info!("PASS: Data verified."); | ||
| 220 | } | ||
| 221 | |||
| 222 | loop { | ||
| 223 | cortex_m::asm::wfe(); | ||
| 224 | } | ||
| 225 | } | ||
| 226 | |||
diff --git a/examples/src/bin/dma_mem_to_mem.rs b/examples/src/bin/dma_mem_to_mem.rs new file mode 100644 index 000000000..e193e8c6a --- /dev/null +++ b/examples/src/bin/dma_mem_to_mem.rs | |||
| @@ -0,0 +1,248 @@ | |||
| 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::clocks::Gate; | ||
| 19 | use embassy_mcxa::dma::{DmaChannel, DmaCh0InterruptHandler, TransferOptions}; | ||
| 20 | use embassy_mcxa::{bind_interrupts, dma}; | ||
| 21 | use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; | ||
| 22 | use embassy_mcxa::pac; | ||
| 23 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 24 | |||
| 25 | // Bind DMA channel 0 interrupt using Embassy-style macro | ||
| 26 | bind_interrupts!(struct Irqs { | ||
| 27 | DMA_CH0 => DmaCh0InterruptHandler; | ||
| 28 | }); | ||
| 29 | |||
| 30 | const BUFFER_LENGTH: usize = 4; | ||
| 31 | |||
| 32 | // Buffers in RAM (static mut is automatically placed in .bss/.data) | ||
| 33 | static mut SRC_BUFFER: [u32; BUFFER_LENGTH] = [0; BUFFER_LENGTH]; | ||
| 34 | static mut DEST_BUFFER: [u32; BUFFER_LENGTH] = [0; BUFFER_LENGTH]; | ||
| 35 | static mut MEMSET_BUFFER: [u32; BUFFER_LENGTH] = [0; BUFFER_LENGTH]; | ||
| 36 | |||
| 37 | /// Helper to write a u32 as decimal ASCII to UART | ||
| 38 | fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { | ||
| 39 | let mut buf = [0u8; 10]; // u32 max is 4294967295 (10 digits) | ||
| 40 | let mut n = val; | ||
| 41 | let mut i = buf.len(); | ||
| 42 | |||
| 43 | if n == 0 { | ||
| 44 | tx.blocking_write(b"0").ok(); | ||
| 45 | return; | ||
| 46 | } | ||
| 47 | |||
| 48 | while n > 0 { | ||
| 49 | i -= 1; | ||
| 50 | buf[i] = b'0' + (n % 10) as u8; | ||
| 51 | n /= 10; | ||
| 52 | } | ||
| 53 | |||
| 54 | tx.blocking_write(&buf[i..]).ok(); | ||
| 55 | } | ||
| 56 | |||
| 57 | /// Helper to print a buffer as [v1, v2, v3, v4] to UART | ||
| 58 | /// Takes a raw pointer to avoid warnings about shared references to mutable statics | ||
| 59 | fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const [u32; BUFFER_LENGTH]) { | ||
| 60 | tx.blocking_write(b"[").ok(); | ||
| 61 | unsafe { | ||
| 62 | let buf = &*buf_ptr; | ||
| 63 | for (i, val) in buf.iter().enumerate() { | ||
| 64 | write_u32(tx, *val); | ||
| 65 | if i < buf.len() - 1 { | ||
| 66 | tx.blocking_write(b", ").ok(); | ||
| 67 | } | ||
| 68 | } | ||
| 69 | } | ||
| 70 | tx.blocking_write(b"]").ok(); | ||
| 71 | } | ||
| 72 | |||
| 73 | #[embassy_executor::main] | ||
| 74 | async fn main(_spawner: Spawner) { | ||
| 75 | // Small delay to allow probe-rs to attach after reset | ||
| 76 | for _ in 0..100_000 { | ||
| 77 | cortex_m::asm::nop(); | ||
| 78 | } | ||
| 79 | |||
| 80 | let mut cfg = hal::config::Config::default(); | ||
| 81 | cfg.clock_cfg.sirc.fro_12m_enabled = true; | ||
| 82 | cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); | ||
| 83 | let p = hal::init(cfg); | ||
| 84 | |||
| 85 | defmt::info!("DMA memory-to-memory example starting..."); | ||
| 86 | |||
| 87 | // Enable DMA0 clock and release reset | ||
| 88 | unsafe { | ||
| 89 | hal::peripherals::DMA0::enable_clock(); | ||
| 90 | hal::peripherals::DMA0::release_reset(); | ||
| 91 | } | ||
| 92 | |||
| 93 | // Get PAC peripherals for DMA init | ||
| 94 | let pac_periphs = unsafe { pac::Peripherals::steal() }; | ||
| 95 | |||
| 96 | // Initialize DMA | ||
| 97 | unsafe { | ||
| 98 | dma::init(&pac_periphs); | ||
| 99 | } | ||
| 100 | |||
| 101 | // Enable DMA interrupt | ||
| 102 | unsafe { | ||
| 103 | cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); | ||
| 104 | } | ||
| 105 | |||
| 106 | // Create UART for debug output | ||
| 107 | let config = Config { | ||
| 108 | baudrate_bps: 115_200, | ||
| 109 | enable_tx: true, | ||
| 110 | enable_rx: false, | ||
| 111 | ..Default::default() | ||
| 112 | }; | ||
| 113 | |||
| 114 | let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); | ||
| 115 | let (mut tx, _rx) = lpuart.split(); | ||
| 116 | |||
| 117 | tx.blocking_write(b"EDMA memory to memory example begin.\r\n\r\n") | ||
| 118 | .unwrap(); | ||
| 119 | |||
| 120 | // Initialize buffers | ||
| 121 | unsafe { | ||
| 122 | SRC_BUFFER = [1, 2, 3, 4]; | ||
| 123 | DEST_BUFFER = [0; BUFFER_LENGTH]; | ||
| 124 | } | ||
| 125 | |||
| 126 | tx.blocking_write(b"Source Buffer: ").unwrap(); | ||
| 127 | print_buffer(&mut tx, &raw const SRC_BUFFER); | ||
| 128 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 129 | |||
| 130 | tx.blocking_write(b"Destination Buffer (before): ").unwrap(); | ||
| 131 | print_buffer(&mut tx, &raw const DEST_BUFFER); | ||
| 132 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 133 | |||
| 134 | tx.blocking_write(b"Configuring DMA with Embassy-style API...\r\n") | ||
| 135 | .unwrap(); | ||
| 136 | |||
| 137 | // Create DMA channel | ||
| 138 | let dma_ch0 = DmaChannel::new(p.DMA_CH0); | ||
| 139 | |||
| 140 | // Configure transfer options (Embassy-style) | ||
| 141 | // TransferOptions defaults to: complete_transfer_interrupt = true | ||
| 142 | let options = TransferOptions::default(); | ||
| 143 | |||
| 144 | // ========================================================================= | ||
| 145 | // Part 1: Embassy-style async API demonstration (mem_to_mem) | ||
| 146 | // ========================================================================= | ||
| 147 | // | ||
| 148 | // Use the new type-safe `mem_to_mem<u32>()` method: | ||
| 149 | // - Automatically determines transfer width from buffer element type (u32) | ||
| 150 | // - Returns a `Transfer` future that can be `.await`ed | ||
| 151 | // - Uses TransferOptions for consistent configuration | ||
| 152 | // | ||
| 153 | // Using async `.await` - the executor can run other tasks while waiting! | ||
| 154 | |||
| 155 | // Perform type-safe memory-to-memory transfer using Embassy-style async API | ||
| 156 | unsafe { | ||
| 157 | let src = &*core::ptr::addr_of!(SRC_BUFFER); | ||
| 158 | let dst = &mut *core::ptr::addr_of_mut!(DEST_BUFFER); | ||
| 159 | |||
| 160 | // Using async `.await` - the executor can run other tasks while waiting! | ||
| 161 | let transfer = dma_ch0.mem_to_mem(src, dst, options); | ||
| 162 | transfer.await; | ||
| 163 | } | ||
| 164 | |||
| 165 | tx.blocking_write(b"DMA mem-to-mem transfer complete!\r\n\r\n") | ||
| 166 | .unwrap(); | ||
| 167 | tx.blocking_write(b"Destination Buffer (after): ").unwrap(); | ||
| 168 | print_buffer(&mut tx, &raw const DEST_BUFFER); | ||
| 169 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 170 | |||
| 171 | // Verify data | ||
| 172 | let mut mismatch = false; | ||
| 173 | unsafe { | ||
| 174 | for i in 0..BUFFER_LENGTH { | ||
| 175 | if SRC_BUFFER[i] != DEST_BUFFER[i] { | ||
| 176 | mismatch = true; | ||
| 177 | break; | ||
| 178 | } | ||
| 179 | } | ||
| 180 | } | ||
| 181 | |||
| 182 | if mismatch { | ||
| 183 | tx.blocking_write(b"FAIL: mem_to_mem mismatch!\r\n").unwrap(); | ||
| 184 | defmt::error!("FAIL: mem_to_mem mismatch!"); | ||
| 185 | } else { | ||
| 186 | tx.blocking_write(b"PASS: mem_to_mem verified.\r\n\r\n").unwrap(); | ||
| 187 | defmt::info!("PASS: mem_to_mem verified."); | ||
| 188 | } | ||
| 189 | |||
| 190 | // ========================================================================= | ||
| 191 | // Part 2: memset() demonstration | ||
| 192 | // ========================================================================= | ||
| 193 | // | ||
| 194 | // The `memset()` method fills a buffer with a pattern value: | ||
| 195 | // - Fixed source address (pattern is read repeatedly) | ||
| 196 | // - Incrementing destination address | ||
| 197 | // - Uses the same Transfer future pattern | ||
| 198 | |||
| 199 | tx.blocking_write(b"--- Demonstrating memset() feature ---\r\n\r\n").unwrap(); | ||
| 200 | |||
| 201 | tx.blocking_write(b"Memset Buffer (before): ").unwrap(); | ||
| 202 | print_buffer(&mut tx, &raw const MEMSET_BUFFER); | ||
| 203 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 204 | |||
| 205 | // Fill buffer with a pattern value using DMA memset | ||
| 206 | let pattern: u32 = 0xDEADBEEF; | ||
| 207 | tx.blocking_write(b"Filling with pattern 0xDEADBEEF...\r\n").unwrap(); | ||
| 208 | |||
| 209 | unsafe { | ||
| 210 | let dst = &mut *core::ptr::addr_of_mut!(MEMSET_BUFFER); | ||
| 211 | |||
| 212 | // Using blocking_wait() for demonstration - also shows non-async usage | ||
| 213 | let transfer = dma_ch0.memset(&pattern, dst, options); | ||
| 214 | transfer.blocking_wait(); | ||
| 215 | } | ||
| 216 | |||
| 217 | tx.blocking_write(b"DMA memset complete!\r\n\r\n").unwrap(); | ||
| 218 | tx.blocking_write(b"Memset Buffer (after): ").unwrap(); | ||
| 219 | print_buffer(&mut tx, &raw const MEMSET_BUFFER); | ||
| 220 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 221 | |||
| 222 | // Verify memset result | ||
| 223 | let mut memset_ok = true; | ||
| 224 | unsafe { | ||
| 225 | #[allow(clippy::needless_range_loop)] | ||
| 226 | for i in 0..BUFFER_LENGTH { | ||
| 227 | if MEMSET_BUFFER[i] != pattern { | ||
| 228 | memset_ok = false; | ||
| 229 | break; | ||
| 230 | } | ||
| 231 | } | ||
| 232 | } | ||
| 233 | |||
| 234 | if !memset_ok { | ||
| 235 | tx.blocking_write(b"FAIL: memset mismatch!\r\n").unwrap(); | ||
| 236 | defmt::error!("FAIL: memset mismatch!"); | ||
| 237 | } else { | ||
| 238 | tx.blocking_write(b"PASS: memset verified.\r\n\r\n").unwrap(); | ||
| 239 | defmt::info!("PASS: memset verified."); | ||
| 240 | } | ||
| 241 | |||
| 242 | tx.blocking_write(b"=== All DMA tests complete ===\r\n").unwrap(); | ||
| 243 | |||
| 244 | loop { | ||
| 245 | cortex_m::asm::wfe(); | ||
| 246 | } | ||
| 247 | } | ||
| 248 | |||
diff --git a/examples/src/bin/dma_memset.rs b/examples/src/bin/dma_memset.rs new file mode 100644 index 000000000..b76ba988d --- /dev/null +++ b/examples/src/bin/dma_memset.rs | |||
| @@ -0,0 +1,232 @@ | |||
| 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 | //! - `dma::edma_tcd()` accessor for simplified register access | ||
| 8 | //! - `DmaChannel::is_done()` and `clear_done()` helper methods | ||
| 9 | //! - No need to pass register block around | ||
| 10 | |||
| 11 | #![no_std] | ||
| 12 | #![no_main] | ||
| 13 | |||
| 14 | use embassy_executor::Spawner; | ||
| 15 | use embassy_mcxa::clocks::config::Div8; | ||
| 16 | use embassy_mcxa::clocks::Gate; | ||
| 17 | use embassy_mcxa::dma::{edma_tcd, DmaChannel, DmaCh0InterruptHandler}; | ||
| 18 | use embassy_mcxa::{bind_interrupts, dma}; | ||
| 19 | use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; | ||
| 20 | use embassy_mcxa::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 | ||
| 31 | static mut PATTERN: u32 = 0; | ||
| 32 | static mut DEST_BUFFER: [u32; BUFFER_LENGTH] = [0; BUFFER_LENGTH]; | ||
| 33 | |||
| 34 | /// Helper to write a u32 as decimal ASCII to UART | ||
| 35 | fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { | ||
| 36 | let mut buf = [0u8; 10]; | ||
| 37 | let mut n = val; | ||
| 38 | let mut i = buf.len(); | ||
| 39 | |||
| 40 | if n == 0 { | ||
| 41 | tx.blocking_write(b"0").ok(); | ||
| 42 | return; | ||
| 43 | } | ||
| 44 | |||
| 45 | while n > 0 { | ||
| 46 | i -= 1; | ||
| 47 | buf[i] = b'0' + (n % 10) as u8; | ||
| 48 | n /= 10; | ||
| 49 | } | ||
| 50 | |||
| 51 | tx.blocking_write(&buf[i..]).ok(); | ||
| 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_u32(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 memset example starting..."); | ||
| 81 | |||
| 82 | // Enable DMA0 clock and release reset | ||
| 83 | unsafe { | ||
| 84 | hal::peripherals::DMA0::enable_clock(); | ||
| 85 | hal::peripherals::DMA0::release_reset(); | ||
| 86 | } | ||
| 87 | |||
| 88 | let pac_periphs = unsafe { pac::Peripherals::steal() }; | ||
| 89 | |||
| 90 | unsafe { | ||
| 91 | dma::init(&pac_periphs); | ||
| 92 | } | ||
| 93 | |||
| 94 | // Enable DMA interrupt | ||
| 95 | unsafe { | ||
| 96 | cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); | ||
| 97 | } | ||
| 98 | |||
| 99 | let config = Config { | ||
| 100 | baudrate_bps: 115_200, | ||
| 101 | enable_tx: true, | ||
| 102 | enable_rx: false, | ||
| 103 | ..Default::default() | ||
| 104 | }; | ||
| 105 | |||
| 106 | let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); | ||
| 107 | let (mut tx, _rx) = lpuart.split(); | ||
| 108 | |||
| 109 | tx.blocking_write(b"EDMA memset example begin.\r\n\r\n") | ||
| 110 | .unwrap(); | ||
| 111 | |||
| 112 | // Initialize buffers | ||
| 113 | unsafe { | ||
| 114 | PATTERN = 0xDEADBEEF; | ||
| 115 | DEST_BUFFER = [0; BUFFER_LENGTH]; | ||
| 116 | } | ||
| 117 | |||
| 118 | tx.blocking_write(b"Pattern value: 0x").unwrap(); | ||
| 119 | // Print pattern in hex | ||
| 120 | unsafe { | ||
| 121 | let hex_chars = b"0123456789ABCDEF"; | ||
| 122 | let mut hex_buf = [0u8; 8]; | ||
| 123 | let mut val = PATTERN; | ||
| 124 | for i in (0..8).rev() { | ||
| 125 | hex_buf[i] = hex_chars[(val & 0xF) as usize]; | ||
| 126 | val >>= 4; | ||
| 127 | } | ||
| 128 | tx.blocking_write(&hex_buf).ok(); | ||
| 129 | } | ||
| 130 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 131 | |||
| 132 | tx.blocking_write(b"Destination Buffer (before): ").unwrap(); | ||
| 133 | print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER) as *const u32, BUFFER_LENGTH); | ||
| 134 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 135 | |||
| 136 | tx.blocking_write(b"Configuring DMA with Embassy-style API...\r\n") | ||
| 137 | .unwrap(); | ||
| 138 | |||
| 139 | // Create DMA channel using Embassy-style API | ||
| 140 | let dma_ch0 = DmaChannel::new(p.DMA_CH0); | ||
| 141 | |||
| 142 | // Use edma_tcd() accessor instead of passing register block around | ||
| 143 | let edma = edma_tcd(); | ||
| 144 | |||
| 145 | // Configure memset transfer using direct TCD access: | ||
| 146 | // Source stays fixed (soff = 0, reads same pattern repeatedly) | ||
| 147 | // Destination increments (doff = 4) | ||
| 148 | unsafe { | ||
| 149 | let t = edma.tcd(0); | ||
| 150 | |||
| 151 | // Reset channel state | ||
| 152 | t.ch_csr().write(|w| { | ||
| 153 | w.erq().disable() | ||
| 154 | .earq().disable() | ||
| 155 | .eei().no_error() | ||
| 156 | .ebw().disable() | ||
| 157 | .done().clear_bit_by_one() | ||
| 158 | }); | ||
| 159 | t.ch_es().write(|w| w.bits(0)); | ||
| 160 | t.ch_int().write(|w| w.int().clear_bit_by_one()); | ||
| 161 | |||
| 162 | // Source address (pattern) - fixed | ||
| 163 | t.tcd_saddr().write(|w| w.saddr().bits(core::ptr::addr_of_mut!(PATTERN) as u32)); | ||
| 164 | // Destination address - increments | ||
| 165 | t.tcd_daddr().write(|w| w.daddr().bits(core::ptr::addr_of_mut!(DEST_BUFFER) as u32)); | ||
| 166 | |||
| 167 | // Source offset = 0 (stays fixed), Dest offset = 4 (increments) | ||
| 168 | t.tcd_soff().write(|w| w.soff().bits(0)); | ||
| 169 | t.tcd_doff().write(|w| w.doff().bits(4)); | ||
| 170 | |||
| 171 | // Attributes: 32-bit transfers (size = 2) | ||
| 172 | t.tcd_attr().write(|w| w.ssize().bits(2).dsize().bits(2)); | ||
| 173 | |||
| 174 | // Transfer entire buffer in one minor loop | ||
| 175 | let nbytes = (BUFFER_LENGTH * 4) as u32; | ||
| 176 | t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(nbytes)); | ||
| 177 | |||
| 178 | // Source doesn't need adjustment (stays fixed) | ||
| 179 | t.tcd_slast_sda().write(|w| w.slast_sda().bits(0)); | ||
| 180 | // Reset dest address after major loop | ||
| 181 | t.tcd_dlast_sga().write(|w| w.dlast_sga().bits(-(nbytes as i32) as u32)); | ||
| 182 | |||
| 183 | // Major loop count = 1 | ||
| 184 | t.tcd_biter_elinkno().write(|w| w.biter().bits(1)); | ||
| 185 | t.tcd_citer_elinkno().write(|w| w.citer().bits(1)); | ||
| 186 | |||
| 187 | // Enable interrupt on major loop completion | ||
| 188 | t.tcd_csr().write(|w| w.intmajor().set_bit()); | ||
| 189 | |||
| 190 | cortex_m::asm::dsb(); | ||
| 191 | |||
| 192 | tx.blocking_write(b"Triggering transfer...\r\n").unwrap(); | ||
| 193 | dma_ch0.trigger_start(edma); | ||
| 194 | } | ||
| 195 | |||
| 196 | // Wait for completion using channel helper method | ||
| 197 | while !dma_ch0.is_done(edma) { | ||
| 198 | cortex_m::asm::nop(); | ||
| 199 | } | ||
| 200 | unsafe { dma_ch0.clear_done(edma); } | ||
| 201 | |||
| 202 | tx.blocking_write(b"\r\nEDMA memset example finish.\r\n\r\n") | ||
| 203 | .unwrap(); | ||
| 204 | tx.blocking_write(b"Destination Buffer (after): ").unwrap(); | ||
| 205 | print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER) as *const u32, BUFFER_LENGTH); | ||
| 206 | tx.blocking_write(b"\r\n\r\n").unwrap(); | ||
| 207 | |||
| 208 | // Verify: All elements should equal PATTERN | ||
| 209 | let mut mismatch = false; | ||
| 210 | unsafe { | ||
| 211 | #[allow(clippy::needless_range_loop)] | ||
| 212 | for i in 0..BUFFER_LENGTH { | ||
| 213 | if DEST_BUFFER[i] != PATTERN { | ||
| 214 | mismatch = true; | ||
| 215 | break; | ||
| 216 | } | ||
| 217 | } | ||
| 218 | } | ||
| 219 | |||
| 220 | if mismatch { | ||
| 221 | tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); | ||
| 222 | defmt::error!("FAIL: Mismatch detected!"); | ||
| 223 | } else { | ||
| 224 | tx.blocking_write(b"PASS: Data verified.\r\n").unwrap(); | ||
| 225 | defmt::info!("PASS: Data verified."); | ||
| 226 | } | ||
| 227 | |||
| 228 | loop { | ||
| 229 | cortex_m::asm::wfe(); | ||
| 230 | } | ||
| 231 | } | ||
| 232 | |||
diff --git a/examples/src/bin/dma_ping_pong_transfer.rs b/examples/src/bin/dma_ping_pong_transfer.rs new file mode 100644 index 000000000..13ad9782d --- /dev/null +++ b/examples/src/bin/dma_ping_pong_transfer.rs | |||
| @@ -0,0 +1,384 @@ | |||
| 1 | //! DMA ping-pong/double-buffer transfer example for MCXA276. | ||
| 2 | //! | ||
| 3 | //! This example demonstrates two approaches for ping-pong/double-buffering: | ||
| 4 | //! | ||
| 5 | //! ## Approach 1: Scatter/Gather with linked TCDs (manual) | ||
| 6 | //! - Two TCDs link to each other for alternating transfers | ||
| 7 | //! - Uses custom interrupt handler with AtomicBool flag | ||
| 8 | //! | ||
| 9 | //! ## Approach 2: Half-transfer interrupt with wait_half() (NEW!) | ||
| 10 | //! - Single continuous transfer over entire buffer | ||
| 11 | //! - Uses half-transfer interrupt to know when first half is ready | ||
| 12 | //! - Application can process first half while second half is being filled | ||
| 13 | //! | ||
| 14 | //! # Embassy-style features demonstrated: | ||
| 15 | //! - `dma::edma_tcd()` accessor for simplified register access | ||
| 16 | //! - `DmaChannel::new()` for channel creation | ||
| 17 | //! - Scatter/gather with linked TCDs | ||
| 18 | //! - NEW: `wait_half()` for half-transfer interrupt handling | ||
| 19 | |||
| 20 | #![no_std] | ||
| 21 | #![no_main] | ||
| 22 | |||
| 23 | use core::sync::atomic::{AtomicBool, Ordering}; | ||
| 24 | use embassy_executor::Spawner; | ||
| 25 | use embassy_mcxa::clocks::config::Div8; | ||
| 26 | use embassy_mcxa::clocks::Gate; | ||
| 27 | use embassy_mcxa::dma::{edma_tcd, DmaChannel, DmaCh1InterruptHandler, Tcd, TransferOptions}; | ||
| 28 | use embassy_mcxa::{bind_interrupts, dma}; | ||
| 29 | use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; | ||
| 30 | use embassy_mcxa::pac; | ||
| 31 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 32 | |||
| 33 | // Source and destination buffers for Approach 1 (scatter/gather) | ||
| 34 | static mut SRC: [u32; 8] = [1, 2, 3, 4, 5, 6, 7, 8]; | ||
| 35 | static mut DST: [u32; 8] = [0; 8]; | ||
| 36 | |||
| 37 | // Source and destination buffers for Approach 2 (wait_half) | ||
| 38 | static mut SRC2: [u32; 8] = [0xA1, 0xA2, 0xA3, 0xA4, 0xB1, 0xB2, 0xB3, 0xB4]; | ||
| 39 | static mut DST2: [u32; 8] = [0; 8]; | ||
| 40 | |||
| 41 | // TCD pool for scatter/gather - must be 32-byte aligned | ||
| 42 | #[repr(C, align(32))] | ||
| 43 | struct TcdPool([Tcd; 2]); | ||
| 44 | |||
| 45 | static mut TCD_POOL: TcdPool = TcdPool([Tcd { | ||
| 46 | saddr: 0, | ||
| 47 | soff: 0, | ||
| 48 | attr: 0, | ||
| 49 | nbytes: 0, | ||
| 50 | slast: 0, | ||
| 51 | daddr: 0, | ||
| 52 | doff: 0, | ||
| 53 | citer: 0, | ||
| 54 | dlast_sga: 0, | ||
| 55 | csr: 0, | ||
| 56 | biter: 0, | ||
| 57 | }; 2]); | ||
| 58 | |||
| 59 | static TRANSFER_DONE: AtomicBool = AtomicBool::new(false); | ||
| 60 | |||
| 61 | // Custom DMA interrupt handler for ping-pong transfer | ||
| 62 | // We need a custom handler because we signal completion via TRANSFER_DONE flag | ||
| 63 | // and don't clear DONE bit when using Scatter/Gather (ESG=1) | ||
| 64 | pub struct PingPongDmaHandler; | ||
| 65 | |||
| 66 | impl embassy_mcxa::interrupt::typelevel::Handler<embassy_mcxa::interrupt::typelevel::DMA_CH0> for PingPongDmaHandler { | ||
| 67 | unsafe fn on_interrupt() { | ||
| 68 | let edma = edma_tcd(); | ||
| 69 | |||
| 70 | // Clear interrupt flag | ||
| 71 | edma.tcd(0).ch_int().write(|w| w.int().clear_bit_by_one()); | ||
| 72 | |||
| 73 | // Do NOT clear DONE bit when using Scatter/Gather (ESG=1), | ||
| 74 | // as the hardware loads the next TCD which resets the status. | ||
| 75 | |||
| 76 | TRANSFER_DONE.store(true, Ordering::Release); | ||
| 77 | } | ||
| 78 | } | ||
| 79 | |||
| 80 | bind_interrupts!(struct Irqs { | ||
| 81 | DMA_CH0 => PingPongDmaHandler; | ||
| 82 | DMA_CH1 => DmaCh1InterruptHandler; // For wait_half() demo | ||
| 83 | }); | ||
| 84 | |||
| 85 | /// Helper to write a u32 as decimal ASCII to UART | ||
| 86 | fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { | ||
| 87 | let mut buf = [0u8; 10]; | ||
| 88 | let mut n = val; | ||
| 89 | let mut i = buf.len(); | ||
| 90 | |||
| 91 | if n == 0 { | ||
| 92 | tx.blocking_write(b"0").ok(); | ||
| 93 | return; | ||
| 94 | } | ||
| 95 | |||
| 96 | while n > 0 { | ||
| 97 | i -= 1; | ||
| 98 | buf[i] = b'0' + (n % 10) as u8; | ||
| 99 | n /= 10; | ||
| 100 | } | ||
| 101 | |||
| 102 | tx.blocking_write(&buf[i..]).ok(); | ||
| 103 | } | ||
| 104 | |||
| 105 | /// Helper to print a buffer to UART | ||
| 106 | fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { | ||
| 107 | tx.blocking_write(b"[").ok(); | ||
| 108 | unsafe { | ||
| 109 | for i in 0..len { | ||
| 110 | write_u32(tx, *buf_ptr.add(i)); | ||
| 111 | if i < len - 1 { | ||
| 112 | tx.blocking_write(b", ").ok(); | ||
| 113 | } | ||
| 114 | } | ||
| 115 | } | ||
| 116 | tx.blocking_write(b"]").ok(); | ||
| 117 | } | ||
| 118 | |||
| 119 | #[embassy_executor::main] | ||
| 120 | async fn main(_spawner: Spawner) { | ||
| 121 | // Small delay to allow probe-rs to attach after reset | ||
| 122 | for _ in 0..100_000 { | ||
| 123 | cortex_m::asm::nop(); | ||
| 124 | } | ||
| 125 | |||
| 126 | let mut cfg = hal::config::Config::default(); | ||
| 127 | cfg.clock_cfg.sirc.fro_12m_enabled = true; | ||
| 128 | cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); | ||
| 129 | let p = hal::init(cfg); | ||
| 130 | |||
| 131 | defmt::info!("DMA ping-pong transfer example starting..."); | ||
| 132 | |||
| 133 | // Enable DMA0 clock and release reset | ||
| 134 | unsafe { | ||
| 135 | hal::peripherals::DMA0::enable_clock(); | ||
| 136 | hal::peripherals::DMA0::release_reset(); | ||
| 137 | } | ||
| 138 | |||
| 139 | let pac_periphs = unsafe { pac::Peripherals::steal() }; | ||
| 140 | |||
| 141 | unsafe { | ||
| 142 | dma::init(&pac_periphs); | ||
| 143 | } | ||
| 144 | |||
| 145 | // Use edma_tcd() accessor instead of passing register block around | ||
| 146 | let edma = edma_tcd(); | ||
| 147 | |||
| 148 | // Enable DMA interrupt | ||
| 149 | unsafe { | ||
| 150 | cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); | ||
| 151 | } | ||
| 152 | |||
| 153 | let config = Config { | ||
| 154 | baudrate_bps: 115_200, | ||
| 155 | enable_tx: true, | ||
| 156 | enable_rx: false, | ||
| 157 | ..Default::default() | ||
| 158 | }; | ||
| 159 | |||
| 160 | let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); | ||
| 161 | let (mut tx, _rx) = lpuart.split(); | ||
| 162 | |||
| 163 | tx.blocking_write(b"EDMA ping-pong transfer example begin.\r\n\r\n") | ||
| 164 | .unwrap(); | ||
| 165 | |||
| 166 | // Initialize buffers | ||
| 167 | unsafe { | ||
| 168 | SRC = [1, 2, 3, 4, 5, 6, 7, 8]; | ||
| 169 | DST = [0; 8]; | ||
| 170 | } | ||
| 171 | |||
| 172 | tx.blocking_write(b"Source Buffer: ").unwrap(); | ||
| 173 | print_buffer(&mut tx, core::ptr::addr_of!(SRC) as *const u32, 8); | ||
| 174 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 175 | |||
| 176 | tx.blocking_write(b"Destination Buffer (before): ").unwrap(); | ||
| 177 | print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); | ||
| 178 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 179 | |||
| 180 | tx.blocking_write(b"Configuring ping-pong DMA with Embassy-style API...\r\n") | ||
| 181 | .unwrap(); | ||
| 182 | |||
| 183 | let dma_ch0 = DmaChannel::new(p.DMA_CH0); | ||
| 184 | |||
| 185 | // Configure ping-pong transfer using direct TCD access: | ||
| 186 | // This sets up TCD0 and TCD1 in RAM, and loads TCD0 into the channel. | ||
| 187 | // TCD0 transfers first half (SRC[0..4] -> DST[0..4]), links to TCD1. | ||
| 188 | // TCD1 transfers second half (SRC[4..8] -> DST[4..8]), links to TCD0. | ||
| 189 | unsafe { | ||
| 190 | let tcds = &mut *core::ptr::addr_of_mut!(TCD_POOL.0); | ||
| 191 | let src_ptr = core::ptr::addr_of!(SRC) as *const u32; | ||
| 192 | let dst_ptr = core::ptr::addr_of_mut!(DST) as *mut u32; | ||
| 193 | |||
| 194 | let half_len = 4usize; | ||
| 195 | let half_bytes = (half_len * 4) as u32; | ||
| 196 | |||
| 197 | let tcd0_addr = &tcds[0] as *const _ as u32; | ||
| 198 | let tcd1_addr = &tcds[1] as *const _ as u32; | ||
| 199 | |||
| 200 | // TCD0: First half -> Links to TCD1 | ||
| 201 | tcds[0] = Tcd { | ||
| 202 | saddr: src_ptr as u32, | ||
| 203 | soff: 4, | ||
| 204 | attr: 0x0202, // 32-bit src/dst | ||
| 205 | nbytes: half_bytes, | ||
| 206 | slast: 0, | ||
| 207 | daddr: dst_ptr as u32, | ||
| 208 | doff: 4, | ||
| 209 | citer: 1, | ||
| 210 | dlast_sga: tcd1_addr as i32, | ||
| 211 | csr: 0x0012, // ESG | INTMAJOR | ||
| 212 | biter: 1, | ||
| 213 | }; | ||
| 214 | |||
| 215 | // TCD1: Second half -> Links to TCD0 | ||
| 216 | tcds[1] = Tcd { | ||
| 217 | saddr: src_ptr.add(half_len) as u32, | ||
| 218 | soff: 4, | ||
| 219 | attr: 0x0202, | ||
| 220 | nbytes: half_bytes, | ||
| 221 | slast: 0, | ||
| 222 | daddr: dst_ptr.add(half_len) as u32, | ||
| 223 | doff: 4, | ||
| 224 | citer: 1, | ||
| 225 | dlast_sga: tcd0_addr as i32, | ||
| 226 | csr: 0x0012, | ||
| 227 | biter: 1, | ||
| 228 | }; | ||
| 229 | |||
| 230 | // Load TCD0 into hardware registers | ||
| 231 | dma_ch0.load_tcd(edma, &tcds[0]); | ||
| 232 | } | ||
| 233 | |||
| 234 | tx.blocking_write(b"Triggering first half transfer...\r\n").unwrap(); | ||
| 235 | |||
| 236 | // Trigger first transfer (first half: SRC[0..4] -> DST[0..4]) | ||
| 237 | unsafe { | ||
| 238 | dma_ch0.trigger_start(edma); | ||
| 239 | } | ||
| 240 | |||
| 241 | // Wait for first half | ||
| 242 | while !TRANSFER_DONE.load(Ordering::Acquire) { | ||
| 243 | cortex_m::asm::nop(); | ||
| 244 | } | ||
| 245 | TRANSFER_DONE.store(false, Ordering::Release); | ||
| 246 | |||
| 247 | tx.blocking_write(b"First half transferred.\r\n").unwrap(); | ||
| 248 | tx.blocking_write(b"Triggering second half transfer...\r\n").unwrap(); | ||
| 249 | |||
| 250 | // Trigger second transfer (second half: SRC[4..8] -> DST[4..8]) | ||
| 251 | unsafe { | ||
| 252 | dma_ch0.trigger_start(edma); | ||
| 253 | } | ||
| 254 | |||
| 255 | // Wait for second half | ||
| 256 | while !TRANSFER_DONE.load(Ordering::Acquire) { | ||
| 257 | cortex_m::asm::nop(); | ||
| 258 | } | ||
| 259 | TRANSFER_DONE.store(false, Ordering::Release); | ||
| 260 | |||
| 261 | tx.blocking_write(b"Second half transferred.\r\n\r\n").unwrap(); | ||
| 262 | |||
| 263 | tx.blocking_write(b"EDMA ping-pong transfer example finish.\r\n\r\n") | ||
| 264 | .unwrap(); | ||
| 265 | tx.blocking_write(b"Destination Buffer (after): ").unwrap(); | ||
| 266 | print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); | ||
| 267 | tx.blocking_write(b"\r\n\r\n").unwrap(); | ||
| 268 | |||
| 269 | // Verify: DST should match SRC | ||
| 270 | let mut mismatch = false; | ||
| 271 | unsafe { | ||
| 272 | let src_ptr = core::ptr::addr_of!(SRC) as *const u32; | ||
| 273 | let dst_ptr = core::ptr::addr_of!(DST) as *const u32; | ||
| 274 | for i in 0..8 { | ||
| 275 | if *src_ptr.add(i) != *dst_ptr.add(i) { | ||
| 276 | mismatch = true; | ||
| 277 | break; | ||
| 278 | } | ||
| 279 | } | ||
| 280 | } | ||
| 281 | |||
| 282 | if mismatch { | ||
| 283 | tx.blocking_write(b"FAIL: Approach 1 mismatch detected!\r\n").unwrap(); | ||
| 284 | defmt::error!("FAIL: Approach 1 mismatch detected!"); | ||
| 285 | } else { | ||
| 286 | tx.blocking_write(b"PASS: Approach 1 data verified.\r\n\r\n").unwrap(); | ||
| 287 | defmt::info!("PASS: Approach 1 data verified."); | ||
| 288 | } | ||
| 289 | |||
| 290 | // ========================================================================= | ||
| 291 | // Approach 2: Half-Transfer Interrupt with wait_half() (NEW!) | ||
| 292 | // ========================================================================= | ||
| 293 | // | ||
| 294 | // This approach uses a single continuous DMA transfer with half-transfer | ||
| 295 | // interrupt enabled. The wait_half() method allows you to be notified | ||
| 296 | // when the first half of the buffer is complete, so you can process it | ||
| 297 | // while the second half is still being filled. | ||
| 298 | // | ||
| 299 | // Benefits: | ||
| 300 | // - Simpler setup (no TCD pool needed) | ||
| 301 | // - True async/await support | ||
| 302 | // - Good for streaming data processing | ||
| 303 | |||
| 304 | tx.blocking_write(b"--- Approach 2: wait_half() demo ---\r\n\r\n").unwrap(); | ||
| 305 | |||
| 306 | // Enable DMA CH1 interrupt | ||
| 307 | unsafe { | ||
| 308 | cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH1); | ||
| 309 | } | ||
| 310 | |||
| 311 | // Initialize approach 2 buffers | ||
| 312 | unsafe { | ||
| 313 | SRC2 = [0xA1, 0xA2, 0xA3, 0xA4, 0xB1, 0xB2, 0xB3, 0xB4]; | ||
| 314 | DST2 = [0; 8]; | ||
| 315 | } | ||
| 316 | |||
| 317 | tx.blocking_write(b"SRC2: ").unwrap(); | ||
| 318 | print_buffer(&mut tx, core::ptr::addr_of!(SRC2) as *const u32, 8); | ||
| 319 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 320 | |||
| 321 | let dma_ch1 = DmaChannel::new(p.DMA_CH1); | ||
| 322 | |||
| 323 | // Configure transfer with half-transfer interrupt enabled | ||
| 324 | let mut options = TransferOptions::default(); | ||
| 325 | options.half_transfer_interrupt = true; // Enable half-transfer interrupt | ||
| 326 | options.complete_transfer_interrupt = true; | ||
| 327 | |||
| 328 | tx.blocking_write(b"Starting transfer with half_transfer_interrupt...\r\n").unwrap(); | ||
| 329 | |||
| 330 | unsafe { | ||
| 331 | let src = &*core::ptr::addr_of!(SRC2); | ||
| 332 | let dst = &mut *core::ptr::addr_of_mut!(DST2); | ||
| 333 | |||
| 334 | // Create the transfer | ||
| 335 | let mut transfer = dma_ch1.mem_to_mem(src, dst, options); | ||
| 336 | |||
| 337 | // Wait for half-transfer (first 4 elements) | ||
| 338 | tx.blocking_write(b"Waiting for first half...\r\n").unwrap(); | ||
| 339 | let half_ok = transfer.wait_half().await; | ||
| 340 | |||
| 341 | if half_ok { | ||
| 342 | tx.blocking_write(b"Half-transfer complete! First half of DST2: ").unwrap(); | ||
| 343 | print_buffer(&mut tx, core::ptr::addr_of!(DST2) as *const u32, 4); | ||
| 344 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 345 | tx.blocking_write(b"(Processing first half while second half transfers...)\r\n").unwrap(); | ||
| 346 | } | ||
| 347 | |||
| 348 | // Wait for complete transfer | ||
| 349 | tx.blocking_write(b"Waiting for second half...\r\n").unwrap(); | ||
| 350 | transfer.await; | ||
| 351 | } | ||
| 352 | |||
| 353 | tx.blocking_write(b"Transfer complete! Full DST2: ").unwrap(); | ||
| 354 | print_buffer(&mut tx, core::ptr::addr_of!(DST2) as *const u32, 8); | ||
| 355 | tx.blocking_write(b"\r\n\r\n").unwrap(); | ||
| 356 | |||
| 357 | // Verify approach 2 | ||
| 358 | let mut mismatch2 = false; | ||
| 359 | unsafe { | ||
| 360 | let src_ptr = core::ptr::addr_of!(SRC2) as *const u32; | ||
| 361 | let dst_ptr = core::ptr::addr_of!(DST2) as *const u32; | ||
| 362 | for i in 0..8 { | ||
| 363 | if *src_ptr.add(i) != *dst_ptr.add(i) { | ||
| 364 | mismatch2 = true; | ||
| 365 | break; | ||
| 366 | } | ||
| 367 | } | ||
| 368 | } | ||
| 369 | |||
| 370 | if mismatch2 { | ||
| 371 | tx.blocking_write(b"FAIL: Approach 2 mismatch!\r\n").unwrap(); | ||
| 372 | defmt::error!("FAIL: Approach 2 mismatch!"); | ||
| 373 | } else { | ||
| 374 | tx.blocking_write(b"PASS: Approach 2 verified.\r\n").unwrap(); | ||
| 375 | defmt::info!("PASS: Approach 2 verified."); | ||
| 376 | } | ||
| 377 | |||
| 378 | tx.blocking_write(b"\r\n=== All ping-pong demos complete ===\r\n").unwrap(); | ||
| 379 | |||
| 380 | loop { | ||
| 381 | cortex_m::asm::wfe(); | ||
| 382 | } | ||
| 383 | } | ||
| 384 | |||
diff --git a/examples/src/bin/dma_scatter_gather.rs b/examples/src/bin/dma_scatter_gather.rs new file mode 100644 index 000000000..86dd881cd --- /dev/null +++ b/examples/src/bin/dma_scatter_gather.rs | |||
| @@ -0,0 +1,281 @@ | |||
| 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 | //! - `dma::edma_tcd()` accessor for simplified register access | ||
| 9 | //! - `DmaChannel::new()` for channel creation | ||
| 10 | //! - Scatter/gather with chained TCDs | ||
| 11 | |||
| 12 | #![no_std] | ||
| 13 | #![no_main] | ||
| 14 | |||
| 15 | use core::sync::atomic::{AtomicBool, Ordering}; | ||
| 16 | use embassy_executor::Spawner; | ||
| 17 | use embassy_mcxa::clocks::config::Div8; | ||
| 18 | use embassy_mcxa::clocks::Gate; | ||
| 19 | use embassy_mcxa::dma::{edma_tcd, DmaChannel, Tcd}; | ||
| 20 | use embassy_mcxa::{bind_interrupts, dma}; | ||
| 21 | use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; | ||
| 22 | use embassy_mcxa::pac; | ||
| 23 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 24 | |||
| 25 | // Source and destination buffers | ||
| 26 | static mut SRC: [u32; 8] = [1, 2, 3, 4, 5, 6, 7, 8]; | ||
| 27 | static mut DST: [u32; 8] = [0; 8]; | ||
| 28 | |||
| 29 | // TCD pool for scatter/gather - must be 32-byte aligned | ||
| 30 | #[repr(C, align(32))] | ||
| 31 | struct TcdPool([Tcd; 2]); | ||
| 32 | |||
| 33 | static mut TCD_POOL: TcdPool = TcdPool([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 | static TRANSFER_DONE: AtomicBool = AtomicBool::new(false); | ||
| 48 | |||
| 49 | // Custom DMA interrupt handler for scatter-gather transfer | ||
| 50 | // We need a custom handler because we signal completion via TRANSFER_DONE flag | ||
| 51 | // and need to conditionally clear DONE bit based on ESG status | ||
| 52 | pub struct ScatterGatherDmaHandler; | ||
| 53 | |||
| 54 | impl embassy_mcxa::interrupt::typelevel::Handler<embassy_mcxa::interrupt::typelevel::DMA_CH0> for ScatterGatherDmaHandler { | ||
| 55 | unsafe fn on_interrupt() { | ||
| 56 | let edma = edma_tcd(); | ||
| 57 | |||
| 58 | // Clear interrupt flag | ||
| 59 | edma.tcd(0).ch_int().write(|w| w.int().clear_bit_by_one()); | ||
| 60 | |||
| 61 | // If ESG=1 (Scatter/Gather), the hardware loads the next TCD and clears DONE. | ||
| 62 | // If ESG=0 (Last TCD), DONE remains set and must be cleared. | ||
| 63 | if edma.tcd(0).ch_csr().read().done().bit_is_set() { | ||
| 64 | edma.tcd(0).ch_csr().write(|w| w.done().clear_bit_by_one()); | ||
| 65 | } | ||
| 66 | |||
| 67 | TRANSFER_DONE.store(true, Ordering::Release); | ||
| 68 | } | ||
| 69 | } | ||
| 70 | |||
| 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 | // Enable DMA0 clock and release reset | ||
| 124 | unsafe { | ||
| 125 | hal::peripherals::DMA0::enable_clock(); | ||
| 126 | hal::peripherals::DMA0::release_reset(); | ||
| 127 | } | ||
| 128 | |||
| 129 | let pac_periphs = unsafe { pac::Peripherals::steal() }; | ||
| 130 | |||
| 131 | unsafe { | ||
| 132 | dma::init(&pac_periphs); | ||
| 133 | } | ||
| 134 | |||
| 135 | // Use edma_tcd() accessor instead of passing register block around | ||
| 136 | let edma = edma_tcd(); | ||
| 137 | |||
| 138 | // Enable DMA interrupt | ||
| 139 | unsafe { | ||
| 140 | cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); | ||
| 141 | } | ||
| 142 | |||
| 143 | let config = Config { | ||
| 144 | baudrate_bps: 115_200, | ||
| 145 | enable_tx: true, | ||
| 146 | enable_rx: false, | ||
| 147 | ..Default::default() | ||
| 148 | }; | ||
| 149 | |||
| 150 | let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); | ||
| 151 | let (mut tx, _rx) = lpuart.split(); | ||
| 152 | |||
| 153 | tx.blocking_write(b"EDMA scatter-gather transfer example begin.\r\n\r\n") | ||
| 154 | .unwrap(); | ||
| 155 | |||
| 156 | // Initialize buffers | ||
| 157 | unsafe { | ||
| 158 | SRC = [1, 2, 3, 4, 5, 6, 7, 8]; | ||
| 159 | DST = [0; 8]; | ||
| 160 | } | ||
| 161 | |||
| 162 | tx.blocking_write(b"Source Buffer: ").unwrap(); | ||
| 163 | print_buffer(&mut tx, core::ptr::addr_of!(SRC) as *const u32, 8); | ||
| 164 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 165 | |||
| 166 | tx.blocking_write(b"Destination Buffer (before): ").unwrap(); | ||
| 167 | print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); | ||
| 168 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 169 | |||
| 170 | tx.blocking_write(b"Configuring scatter-gather DMA with Embassy-style API...\r\n") | ||
| 171 | .unwrap(); | ||
| 172 | |||
| 173 | let dma_ch0 = DmaChannel::new(p.DMA_CH0); | ||
| 174 | |||
| 175 | // Configure scatter-gather transfer using direct TCD access: | ||
| 176 | // This sets up TCD0 and TCD1 in RAM, and loads TCD0 into the channel. | ||
| 177 | // TCD0 transfers first half (SRC[0..4] -> DST[0..4]), then loads TCD1. | ||
| 178 | // TCD1 transfers second half (SRC[4..8] -> DST[4..8]), last TCD. | ||
| 179 | unsafe { | ||
| 180 | let tcds = core::slice::from_raw_parts_mut( | ||
| 181 | core::ptr::addr_of_mut!(TCD_POOL.0) as *mut Tcd, | ||
| 182 | 2, | ||
| 183 | ); | ||
| 184 | let src_ptr = core::ptr::addr_of!(SRC) as *const u32; | ||
| 185 | let dst_ptr = core::ptr::addr_of_mut!(DST) as *mut u32; | ||
| 186 | |||
| 187 | let num_tcds = 2usize; | ||
| 188 | let chunk_len = 4usize; // 8 / 2 | ||
| 189 | let chunk_bytes = (chunk_len * 4) as u32; | ||
| 190 | |||
| 191 | for i in 0..num_tcds { | ||
| 192 | let is_last = i == num_tcds - 1; | ||
| 193 | let next_tcd_addr = if is_last { | ||
| 194 | 0 // No next TCD | ||
| 195 | } else { | ||
| 196 | &tcds[i + 1] as *const _ as u32 | ||
| 197 | }; | ||
| 198 | |||
| 199 | tcds[i] = Tcd { | ||
| 200 | saddr: src_ptr.add(i * chunk_len) as u32, | ||
| 201 | soff: 4, | ||
| 202 | attr: 0x0202, // 32-bit src/dst | ||
| 203 | nbytes: chunk_bytes, | ||
| 204 | slast: 0, | ||
| 205 | daddr: dst_ptr.add(i * chunk_len) as u32, | ||
| 206 | doff: 4, | ||
| 207 | citer: 1, | ||
| 208 | dlast_sga: next_tcd_addr as i32, | ||
| 209 | // ESG (scatter/gather) for non-last, INTMAJOR for all | ||
| 210 | csr: if is_last { 0x0002 } else { 0x0012 }, | ||
| 211 | biter: 1, | ||
| 212 | }; | ||
| 213 | } | ||
| 214 | |||
| 215 | // Load TCD0 into hardware registers | ||
| 216 | dma_ch0.load_tcd(edma, &tcds[0]); | ||
| 217 | } | ||
| 218 | |||
| 219 | tx.blocking_write(b"Triggering first half transfer...\r\n").unwrap(); | ||
| 220 | |||
| 221 | // Trigger first transfer (first half: SRC[0..4] -> DST[0..4]) | ||
| 222 | // TCD0 is currently loaded. | ||
| 223 | unsafe { | ||
| 224 | dma_ch0.trigger_start(edma); | ||
| 225 | } | ||
| 226 | |||
| 227 | // Wait for first half | ||
| 228 | while !TRANSFER_DONE.load(Ordering::Acquire) { | ||
| 229 | cortex_m::asm::nop(); | ||
| 230 | } | ||
| 231 | TRANSFER_DONE.store(false, Ordering::Release); | ||
| 232 | |||
| 233 | tx.blocking_write(b"First half transferred.\r\n").unwrap(); | ||
| 234 | tx.blocking_write(b"Triggering second half transfer...\r\n").unwrap(); | ||
| 235 | |||
| 236 | // Trigger second transfer (second half: SRC[4..8] -> DST[4..8]) | ||
| 237 | // TCD1 should have been loaded by the scatter/gather engine. | ||
| 238 | unsafe { | ||
| 239 | dma_ch0.trigger_start(edma); | ||
| 240 | } | ||
| 241 | |||
| 242 | // Wait for second half | ||
| 243 | while !TRANSFER_DONE.load(Ordering::Acquire) { | ||
| 244 | cortex_m::asm::nop(); | ||
| 245 | } | ||
| 246 | TRANSFER_DONE.store(false, Ordering::Release); | ||
| 247 | |||
| 248 | tx.blocking_write(b"Second half transferred.\r\n\r\n").unwrap(); | ||
| 249 | |||
| 250 | tx.blocking_write(b"EDMA scatter-gather transfer example finish.\r\n\r\n") | ||
| 251 | .unwrap(); | ||
| 252 | tx.blocking_write(b"Destination Buffer (after): ").unwrap(); | ||
| 253 | print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); | ||
| 254 | tx.blocking_write(b"\r\n\r\n").unwrap(); | ||
| 255 | |||
| 256 | // Verify: DST should match SRC | ||
| 257 | let mut mismatch = false; | ||
| 258 | unsafe { | ||
| 259 | let src_ptr = core::ptr::addr_of!(SRC) as *const u32; | ||
| 260 | let dst_ptr = core::ptr::addr_of!(DST) as *const u32; | ||
| 261 | for i in 0..8 { | ||
| 262 | if *src_ptr.add(i) != *dst_ptr.add(i) { | ||
| 263 | mismatch = true; | ||
| 264 | break; | ||
| 265 | } | ||
| 266 | } | ||
| 267 | } | ||
| 268 | |||
| 269 | if mismatch { | ||
| 270 | tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); | ||
| 271 | defmt::error!("FAIL: Mismatch detected!"); | ||
| 272 | } else { | ||
| 273 | tx.blocking_write(b"PASS: Data verified.\r\n").unwrap(); | ||
| 274 | defmt::info!("PASS: Data verified."); | ||
| 275 | } | ||
| 276 | |||
| 277 | loop { | ||
| 278 | cortex_m::asm::wfe(); | ||
| 279 | } | ||
| 280 | } | ||
| 281 | |||
diff --git a/examples/src/bin/dma_scatter_gather_builder.rs b/examples/src/bin/dma_scatter_gather_builder.rs new file mode 100644 index 000000000..078e26c60 --- /dev/null +++ b/examples/src/bin/dma_scatter_gather_builder.rs | |||
| @@ -0,0 +1,244 @@ | |||
| 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::clocks::Gate; | ||
| 26 | use embassy_mcxa::dma::{DmaChannel, DmaCh0InterruptHandler, ScatterGatherBuilder}; | ||
| 27 | use embassy_mcxa::{bind_interrupts, dma}; | ||
| 28 | use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; | ||
| 29 | use embassy_mcxa::pac; | ||
| 30 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 31 | |||
| 32 | // Bind DMA channel 0 interrupt | ||
| 33 | bind_interrupts!(struct Irqs { | ||
| 34 | DMA_CH0 => DmaCh0InterruptHandler; | ||
| 35 | }); | ||
| 36 | |||
| 37 | // Source buffers (multiple segments) | ||
| 38 | static mut SRC1: [u32; 4] = [0x11111111, 0x22222222, 0x33333333, 0x44444444]; | ||
| 39 | static mut SRC2: [u32; 4] = [0xAAAAAAAA, 0xBBBBBBBB, 0xCCCCCCCC, 0xDDDDDDDD]; | ||
| 40 | static mut SRC3: [u32; 4] = [0x12345678, 0x9ABCDEF0, 0xFEDCBA98, 0x76543210]; | ||
| 41 | |||
| 42 | // Destination buffers (one per segment) | ||
| 43 | static mut DST1: [u32; 4] = [0; 4]; | ||
| 44 | static mut DST2: [u32; 4] = [0; 4]; | ||
| 45 | static mut DST3: [u32; 4] = [0; 4]; | ||
| 46 | |||
| 47 | /// Helper to write a u32 as hex to UART | ||
| 48 | fn write_hex(tx: &mut LpuartTx<'_, Blocking>, val: u32) { | ||
| 49 | const HEX: &[u8; 16] = b"0123456789ABCDEF"; | ||
| 50 | for i in (0..8).rev() { | ||
| 51 | let nibble = ((val >> (i * 4)) & 0xF) as usize; | ||
| 52 | tx.blocking_write(&[HEX[nibble]]).ok(); | ||
| 53 | } | ||
| 54 | } | ||
| 55 | |||
| 56 | /// Helper to print a buffer to UART | ||
| 57 | fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { | ||
| 58 | tx.blocking_write(b"[").ok(); | ||
| 59 | unsafe { | ||
| 60 | for i in 0..len { | ||
| 61 | write_hex(tx, *buf_ptr.add(i)); | ||
| 62 | if i < len - 1 { | ||
| 63 | tx.blocking_write(b", ").ok(); | ||
| 64 | } | ||
| 65 | } | ||
| 66 | } | ||
| 67 | tx.blocking_write(b"]").ok(); | ||
| 68 | } | ||
| 69 | |||
| 70 | #[embassy_executor::main] | ||
| 71 | async fn main(_spawner: Spawner) { | ||
| 72 | // Small delay to allow probe-rs to attach after reset | ||
| 73 | for _ in 0..100_000 { | ||
| 74 | cortex_m::asm::nop(); | ||
| 75 | } | ||
| 76 | |||
| 77 | let mut cfg = hal::config::Config::default(); | ||
| 78 | cfg.clock_cfg.sirc.fro_12m_enabled = true; | ||
| 79 | cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); | ||
| 80 | let p = hal::init(cfg); | ||
| 81 | |||
| 82 | defmt::info!("DMA Scatter-Gather Builder example starting..."); | ||
| 83 | |||
| 84 | // Enable DMA0 clock and release reset | ||
| 85 | unsafe { | ||
| 86 | hal::peripherals::DMA0::enable_clock(); | ||
| 87 | hal::peripherals::DMA0::release_reset(); | ||
| 88 | } | ||
| 89 | |||
| 90 | let pac_periphs = unsafe { pac::Peripherals::steal() }; | ||
| 91 | |||
| 92 | // Initialize DMA | ||
| 93 | unsafe { | ||
| 94 | dma::init(&pac_periphs); | ||
| 95 | } | ||
| 96 | |||
| 97 | // Enable DMA interrupt | ||
| 98 | unsafe { | ||
| 99 | cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); | ||
| 100 | } | ||
| 101 | |||
| 102 | // Create UART for debug output | ||
| 103 | let config = Config { | ||
| 104 | baudrate_bps: 115_200, | ||
| 105 | enable_tx: true, | ||
| 106 | enable_rx: false, | ||
| 107 | ..Default::default() | ||
| 108 | }; | ||
| 109 | |||
| 110 | let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); | ||
| 111 | let (mut tx, _rx) = lpuart.split(); | ||
| 112 | |||
| 113 | tx.blocking_write(b"DMA Scatter-Gather Builder Example\r\n").unwrap(); | ||
| 114 | tx.blocking_write(b"===================================\r\n\r\n").unwrap(); | ||
| 115 | |||
| 116 | // Show source buffers | ||
| 117 | tx.blocking_write(b"Source buffers:\r\n").unwrap(); | ||
| 118 | tx.blocking_write(b" SRC1: ").unwrap(); | ||
| 119 | print_buffer(&mut tx, core::ptr::addr_of!(SRC1) as *const u32, 4); | ||
| 120 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 121 | tx.blocking_write(b" SRC2: ").unwrap(); | ||
| 122 | print_buffer(&mut tx, core::ptr::addr_of!(SRC2) as *const u32, 4); | ||
| 123 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 124 | tx.blocking_write(b" SRC3: ").unwrap(); | ||
| 125 | print_buffer(&mut tx, core::ptr::addr_of!(SRC3) as *const u32, 4); | ||
| 126 | tx.blocking_write(b"\r\n\r\n").unwrap(); | ||
| 127 | |||
| 128 | tx.blocking_write(b"Destination buffers (before):\r\n").unwrap(); | ||
| 129 | tx.blocking_write(b" DST1: ").unwrap(); | ||
| 130 | print_buffer(&mut tx, core::ptr::addr_of!(DST1) as *const u32, 4); | ||
| 131 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 132 | tx.blocking_write(b" DST2: ").unwrap(); | ||
| 133 | print_buffer(&mut tx, core::ptr::addr_of!(DST2) as *const u32, 4); | ||
| 134 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 135 | tx.blocking_write(b" DST3: ").unwrap(); | ||
| 136 | print_buffer(&mut tx, core::ptr::addr_of!(DST3) as *const u32, 4); | ||
| 137 | tx.blocking_write(b"\r\n\r\n").unwrap(); | ||
| 138 | |||
| 139 | // Create DMA channel | ||
| 140 | let dma_ch0 = DmaChannel::new(p.DMA_CH0); | ||
| 141 | |||
| 142 | tx.blocking_write(b"Building scatter-gather chain with builder API...\r\n").unwrap(); | ||
| 143 | |||
| 144 | // ========================================================================= | ||
| 145 | // ScatterGatherBuilder API demonstration | ||
| 146 | // ========================================================================= | ||
| 147 | // | ||
| 148 | // The builder pattern makes scatter-gather transfers much easier: | ||
| 149 | // 1. Create a builder | ||
| 150 | // 2. Add transfer segments with add_transfer() | ||
| 151 | // 3. Call build() to start the entire chain | ||
| 152 | // No manual TCD manipulation required! | ||
| 153 | |||
| 154 | let mut builder = ScatterGatherBuilder::<u32>::new(); | ||
| 155 | |||
| 156 | // Add three transfer segments - the builder handles TCD linking automatically | ||
| 157 | unsafe { | ||
| 158 | let src1 = &*core::ptr::addr_of!(SRC1); | ||
| 159 | let dst1 = &mut *core::ptr::addr_of_mut!(DST1); | ||
| 160 | builder.add_transfer(src1, dst1); | ||
| 161 | } | ||
| 162 | |||
| 163 | unsafe { | ||
| 164 | let src2 = &*core::ptr::addr_of!(SRC2); | ||
| 165 | let dst2 = &mut *core::ptr::addr_of_mut!(DST2); | ||
| 166 | builder.add_transfer(src2, dst2); | ||
| 167 | } | ||
| 168 | |||
| 169 | unsafe { | ||
| 170 | let src3 = &*core::ptr::addr_of!(SRC3); | ||
| 171 | let dst3 = &mut *core::ptr::addr_of_mut!(DST3); | ||
| 172 | builder.add_transfer(src3, dst3); | ||
| 173 | } | ||
| 174 | |||
| 175 | tx.blocking_write(b"Added 3 transfer segments to chain.\r\n").unwrap(); | ||
| 176 | tx.blocking_write(b"Starting scatter-gather transfer with .await...\r\n\r\n").unwrap(); | ||
| 177 | |||
| 178 | // Build and execute the scatter-gather chain | ||
| 179 | // The build() method: | ||
| 180 | // - Links all TCDs together with ESG bit | ||
| 181 | // - Sets INTMAJOR on all TCDs | ||
| 182 | // - Loads the first TCD into hardware | ||
| 183 | // - Returns a Transfer future | ||
| 184 | unsafe { | ||
| 185 | let transfer = builder.build(&dma_ch0).expect("Failed to build scatter-gather"); | ||
| 186 | transfer.blocking_wait(); | ||
| 187 | } | ||
| 188 | |||
| 189 | tx.blocking_write(b"Scatter-gather transfer complete!\r\n\r\n").unwrap(); | ||
| 190 | |||
| 191 | // Show results | ||
| 192 | tx.blocking_write(b"Destination buffers (after):\r\n").unwrap(); | ||
| 193 | tx.blocking_write(b" DST1: ").unwrap(); | ||
| 194 | print_buffer(&mut tx, core::ptr::addr_of!(DST1) as *const u32, 4); | ||
| 195 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 196 | tx.blocking_write(b" DST2: ").unwrap(); | ||
| 197 | print_buffer(&mut tx, core::ptr::addr_of!(DST2) as *const u32, 4); | ||
| 198 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 199 | tx.blocking_write(b" DST3: ").unwrap(); | ||
| 200 | print_buffer(&mut tx, core::ptr::addr_of!(DST3) as *const u32, 4); | ||
| 201 | tx.blocking_write(b"\r\n\r\n").unwrap(); | ||
| 202 | |||
| 203 | // Verify all three segments | ||
| 204 | let mut all_ok = true; | ||
| 205 | unsafe { | ||
| 206 | let src1 = core::ptr::addr_of!(SRC1) as *const u32; | ||
| 207 | let dst1 = core::ptr::addr_of!(DST1) as *const u32; | ||
| 208 | for i in 0..4 { | ||
| 209 | if *src1.add(i) != *dst1.add(i) { | ||
| 210 | all_ok = false; | ||
| 211 | } | ||
| 212 | } | ||
| 213 | |||
| 214 | let src2 = core::ptr::addr_of!(SRC2) as *const u32; | ||
| 215 | let dst2 = core::ptr::addr_of!(DST2) as *const u32; | ||
| 216 | for i in 0..4 { | ||
| 217 | if *src2.add(i) != *dst2.add(i) { | ||
| 218 | all_ok = false; | ||
| 219 | } | ||
| 220 | } | ||
| 221 | |||
| 222 | let src3 = core::ptr::addr_of!(SRC3) as *const u32; | ||
| 223 | let dst3 = core::ptr::addr_of!(DST3) as *const u32; | ||
| 224 | for i in 0..4 { | ||
| 225 | if *src3.add(i) != *dst3.add(i) { | ||
| 226 | all_ok = false; | ||
| 227 | } | ||
| 228 | } | ||
| 229 | } | ||
| 230 | |||
| 231 | if all_ok { | ||
| 232 | tx.blocking_write(b"PASS: All segments verified!\r\n").unwrap(); | ||
| 233 | defmt::info!("PASS: All segments verified!"); | ||
| 234 | } else { | ||
| 235 | tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); | ||
| 236 | defmt::error!("FAIL: Mismatch detected!"); | ||
| 237 | } | ||
| 238 | |||
| 239 | tx.blocking_write(b"\r\n=== Scatter-Gather Builder example complete ===\r\n").unwrap(); | ||
| 240 | |||
| 241 | loop { | ||
| 242 | cortex_m::asm::wfe(); | ||
| 243 | } | ||
| 244 | } | ||
diff --git a/examples/src/bin/dma_wrap_transfer.rs b/examples/src/bin/dma_wrap_transfer.rs new file mode 100644 index 000000000..b115a2c19 --- /dev/null +++ b/examples/src/bin/dma_wrap_transfer.rs | |||
| @@ -0,0 +1,231 @@ | |||
| 1 | //! DMA wrap transfer example for MCXA276. | ||
| 2 | //! | ||
| 3 | //! This example demonstrates using DMA with modulo addressing to wrap around | ||
| 4 | //! a source buffer, effectively repeating the source data in the destination. | ||
| 5 | //! | ||
| 6 | //! # Embassy-style features demonstrated: | ||
| 7 | //! - `dma::edma_tcd()` accessor for simplified register access | ||
| 8 | //! - `DmaChannel::is_done()` and `clear_done()` helper methods | ||
| 9 | //! - No need to pass register block around | ||
| 10 | |||
| 11 | #![no_std] | ||
| 12 | #![no_main] | ||
| 13 | |||
| 14 | use embassy_executor::Spawner; | ||
| 15 | use embassy_mcxa::clocks::config::Div8; | ||
| 16 | use embassy_mcxa::clocks::Gate; | ||
| 17 | use embassy_mcxa::dma::{edma_tcd, DmaChannel, DmaCh0InterruptHandler}; | ||
| 18 | use embassy_mcxa::{bind_interrupts, dma}; | ||
| 19 | use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; | ||
| 20 | use embassy_mcxa::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 | // Source buffer: 4 words (16 bytes), aligned to 16 bytes for modulo | ||
| 29 | #[repr(align(16))] | ||
| 30 | struct AlignedSrc([u32; 4]); | ||
| 31 | |||
| 32 | static mut SRC: AlignedSrc = AlignedSrc([0; 4]); | ||
| 33 | static mut DST: [u32; 8] = [0; 8]; | ||
| 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]; | ||
| 38 | let mut n = val; | ||
| 39 | let mut i = buf.len(); | ||
| 40 | |||
| 41 | if n == 0 { | ||
| 42 | tx.blocking_write(b"0").ok(); | ||
| 43 | return; | ||
| 44 | } | ||
| 45 | |||
| 46 | while n > 0 { | ||
| 47 | i -= 1; | ||
| 48 | buf[i] = b'0' + (n % 10) as u8; | ||
| 49 | n /= 10; | ||
| 50 | } | ||
| 51 | |||
| 52 | tx.blocking_write(&buf[i..]).ok(); | ||
| 53 | } | ||
| 54 | |||
| 55 | /// Helper to print a buffer to UART | ||
| 56 | fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { | ||
| 57 | tx.blocking_write(b"[").ok(); | ||
| 58 | unsafe { | ||
| 59 | for i in 0..len { | ||
| 60 | write_u32(tx, *buf_ptr.add(i)); | ||
| 61 | if i < len - 1 { | ||
| 62 | tx.blocking_write(b", ").ok(); | ||
| 63 | } | ||
| 64 | } | ||
| 65 | } | ||
| 66 | tx.blocking_write(b"]").ok(); | ||
| 67 | } | ||
| 68 | |||
| 69 | #[embassy_executor::main] | ||
| 70 | async fn main(_spawner: Spawner) { | ||
| 71 | // Small delay to allow probe-rs to attach after reset | ||
| 72 | for _ in 0..100_000 { | ||
| 73 | cortex_m::asm::nop(); | ||
| 74 | } | ||
| 75 | |||
| 76 | let mut cfg = hal::config::Config::default(); | ||
| 77 | cfg.clock_cfg.sirc.fro_12m_enabled = true; | ||
| 78 | cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); | ||
| 79 | let p = hal::init(cfg); | ||
| 80 | |||
| 81 | defmt::info!("DMA wrap transfer example starting..."); | ||
| 82 | |||
| 83 | // Enable DMA0 clock and release reset | ||
| 84 | unsafe { | ||
| 85 | hal::peripherals::DMA0::enable_clock(); | ||
| 86 | hal::peripherals::DMA0::release_reset(); | ||
| 87 | } | ||
| 88 | |||
| 89 | let pac_periphs = unsafe { pac::Peripherals::steal() }; | ||
| 90 | |||
| 91 | unsafe { | ||
| 92 | dma::init(&pac_periphs); | ||
| 93 | } | ||
| 94 | |||
| 95 | // Enable DMA interrupt | ||
| 96 | unsafe { | ||
| 97 | cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); | ||
| 98 | } | ||
| 99 | |||
| 100 | let config = Config { | ||
| 101 | baudrate_bps: 115_200, | ||
| 102 | enable_tx: true, | ||
| 103 | enable_rx: false, | ||
| 104 | ..Default::default() | ||
| 105 | }; | ||
| 106 | |||
| 107 | let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); | ||
| 108 | let (mut tx, _rx) = lpuart.split(); | ||
| 109 | |||
| 110 | tx.blocking_write(b"EDMA wrap transfer example begin.\r\n\r\n") | ||
| 111 | .unwrap(); | ||
| 112 | |||
| 113 | // Initialize buffers | ||
| 114 | unsafe { | ||
| 115 | SRC.0 = [1, 2, 3, 4]; | ||
| 116 | DST = [0; 8]; | ||
| 117 | } | ||
| 118 | |||
| 119 | tx.blocking_write(b"Source Buffer: ").unwrap(); | ||
| 120 | print_buffer(&mut tx, unsafe { core::ptr::addr_of!(SRC.0) } as *const u32, 4); | ||
| 121 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 122 | |||
| 123 | tx.blocking_write(b"Destination Buffer (before): ").unwrap(); | ||
| 124 | print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); | ||
| 125 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 126 | |||
| 127 | tx.blocking_write(b"Configuring DMA with Embassy-style API...\r\n") | ||
| 128 | .unwrap(); | ||
| 129 | |||
| 130 | // Create DMA channel using Embassy-style API | ||
| 131 | let dma_ch0 = DmaChannel::new(p.DMA_CH0); | ||
| 132 | |||
| 133 | // Use edma_tcd() accessor instead of passing register block around | ||
| 134 | let edma = edma_tcd(); | ||
| 135 | |||
| 136 | // Configure wrap transfer using direct TCD access: | ||
| 137 | // SRC is 16 bytes (4 * u32). We want to transfer 32 bytes (8 * u32). | ||
| 138 | // SRC modulo is 16 bytes (2^4 = 16) - wraps source address. | ||
| 139 | // DST modulo is 0 (disabled). | ||
| 140 | // This causes the source address to wrap around after 16 bytes, | ||
| 141 | // effectively repeating the source data. | ||
| 142 | unsafe { | ||
| 143 | let t = edma.tcd(0); | ||
| 144 | |||
| 145 | // Reset channel state | ||
| 146 | t.ch_csr().write(|w| { | ||
| 147 | w.erq().disable() | ||
| 148 | .earq().disable() | ||
| 149 | .eei().no_error() | ||
| 150 | .ebw().disable() | ||
| 151 | .done().clear_bit_by_one() | ||
| 152 | }); | ||
| 153 | t.ch_es().write(|w| w.bits(0)); | ||
| 154 | t.ch_int().write(|w| w.int().clear_bit_by_one()); | ||
| 155 | |||
| 156 | // Source/destination addresses | ||
| 157 | t.tcd_saddr().write(|w| w.saddr().bits(core::ptr::addr_of!(SRC.0) as u32)); | ||
| 158 | t.tcd_daddr().write(|w| w.daddr().bits(core::ptr::addr_of_mut!(DST) as u32)); | ||
| 159 | |||
| 160 | // Offsets: both increment by 4 bytes | ||
| 161 | t.tcd_soff().write(|w| w.soff().bits(4)); | ||
| 162 | t.tcd_doff().write(|w| w.doff().bits(4)); | ||
| 163 | |||
| 164 | // Attributes: 32-bit transfers (size = 2) | ||
| 165 | // SMOD = 4 (2^4 = 16 byte modulo for source), DMOD = 0 (disabled) | ||
| 166 | t.tcd_attr().write(|w| { | ||
| 167 | w.ssize().bits(2) | ||
| 168 | .dsize().bits(2) | ||
| 169 | .smod().bits(4) // Source modulo: 2^4 = 16 bytes | ||
| 170 | .dmod().bits(0) // Dest modulo: disabled | ||
| 171 | }); | ||
| 172 | |||
| 173 | // Transfer 32 bytes total in one minor loop | ||
| 174 | let nbytes = 32u32; | ||
| 175 | t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(nbytes)); | ||
| 176 | |||
| 177 | // Source wraps via modulo, no adjustment needed | ||
| 178 | t.tcd_slast_sda().write(|w| w.slast_sda().bits(0)); | ||
| 179 | // Reset dest address after major loop | ||
| 180 | t.tcd_dlast_sga().write(|w| w.dlast_sga().bits(-(nbytes as i32) as u32)); | ||
| 181 | |||
| 182 | // Major loop count = 1 | ||
| 183 | t.tcd_biter_elinkno().write(|w| w.biter().bits(1)); | ||
| 184 | t.tcd_citer_elinkno().write(|w| w.citer().bits(1)); | ||
| 185 | |||
| 186 | // Enable interrupt on major loop completion | ||
| 187 | t.tcd_csr().write(|w| w.intmajor().set_bit()); | ||
| 188 | |||
| 189 | cortex_m::asm::dsb(); | ||
| 190 | |||
| 191 | tx.blocking_write(b"Triggering transfer...\r\n").unwrap(); | ||
| 192 | dma_ch0.trigger_start(edma); | ||
| 193 | } | ||
| 194 | |||
| 195 | // Wait for completion using channel helper method | ||
| 196 | while !dma_ch0.is_done(edma) { | ||
| 197 | cortex_m::asm::nop(); | ||
| 198 | } | ||
| 199 | unsafe { dma_ch0.clear_done(edma); } | ||
| 200 | |||
| 201 | tx.blocking_write(b"\r\nEDMA wrap transfer example finish.\r\n\r\n") | ||
| 202 | .unwrap(); | ||
| 203 | tx.blocking_write(b"Destination Buffer (after): ").unwrap(); | ||
| 204 | print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); | ||
| 205 | tx.blocking_write(b"\r\n\r\n").unwrap(); | ||
| 206 | |||
| 207 | // Verify: DST should be [1, 2, 3, 4, 1, 2, 3, 4] | ||
| 208 | let expected = [1u32, 2, 3, 4, 1, 2, 3, 4]; | ||
| 209 | let mut mismatch = false; | ||
| 210 | unsafe { | ||
| 211 | for i in 0..8 { | ||
| 212 | if DST[i] != expected[i] { | ||
| 213 | mismatch = true; | ||
| 214 | break; | ||
| 215 | } | ||
| 216 | } | ||
| 217 | } | ||
| 218 | |||
| 219 | if mismatch { | ||
| 220 | tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); | ||
| 221 | defmt::error!("FAIL: Mismatch detected!"); | ||
| 222 | } else { | ||
| 223 | tx.blocking_write(b"PASS: Data verified.\r\n").unwrap(); | ||
| 224 | defmt::info!("PASS: Data verified."); | ||
| 225 | } | ||
| 226 | |||
| 227 | loop { | ||
| 228 | cortex_m::asm::wfe(); | ||
| 229 | } | ||
| 230 | } | ||
| 231 | |||
diff --git a/examples/src/bin/lpuart_dma.rs b/examples/src/bin/lpuart_dma.rs new file mode 100644 index 000000000..5ccf97ecc --- /dev/null +++ b/examples/src/bin/lpuart_dma.rs | |||
| @@ -0,0 +1,127 @@ | |||
| 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 | #![no_std] | ||
| 8 | #![no_main] | ||
| 9 | |||
| 10 | use embassy_executor::Spawner; | ||
| 11 | use embassy_mcxa::clocks::config::Div8; | ||
| 12 | use embassy_mcxa::clocks::Gate; | ||
| 13 | use embassy_mcxa::dma::{self, DMA_REQ_LPUART2_RX, DMA_REQ_LPUART2_TX}; | ||
| 14 | use embassy_mcxa::lpuart::{Config, LpuartDma}; | ||
| 15 | use embassy_mcxa::pac; | ||
| 16 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 17 | |||
| 18 | // DMA interrupt handlers | ||
| 19 | #[no_mangle] | ||
| 20 | pub extern "C" fn DMA_CH0() { | ||
| 21 | unsafe { dma::on_interrupt(0) }; | ||
| 22 | } | ||
| 23 | |||
| 24 | #[no_mangle] | ||
| 25 | pub extern "C" fn DMA_CH1() { | ||
| 26 | unsafe { dma::on_interrupt(1) }; | ||
| 27 | } | ||
| 28 | |||
| 29 | #[embassy_executor::main] | ||
| 30 | async fn main(_spawner: Spawner) { | ||
| 31 | let mut cfg = hal::config::Config::default(); | ||
| 32 | cfg.clock_cfg.sirc.fro_12m_enabled = true; | ||
| 33 | cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); | ||
| 34 | let p = hal::init(cfg); | ||
| 35 | |||
| 36 | defmt::info!("LPUART DMA example starting..."); | ||
| 37 | |||
| 38 | // Enable DMA0 clock and release reset | ||
| 39 | unsafe { | ||
| 40 | hal::peripherals::DMA0::enable_clock(); | ||
| 41 | hal::peripherals::DMA0::release_reset(); | ||
| 42 | } | ||
| 43 | |||
| 44 | // Get PAC peripherals for DMA init | ||
| 45 | let pac_periphs = unsafe { pac::Peripherals::steal() }; | ||
| 46 | |||
| 47 | // Initialize DMA | ||
| 48 | unsafe { | ||
| 49 | dma::init(&pac_periphs); | ||
| 50 | } | ||
| 51 | |||
| 52 | // Get EDMA TCD register block for transfers | ||
| 53 | let edma = &pac_periphs.edma_0_tcd0; | ||
| 54 | |||
| 55 | // Enable DMA interrupts | ||
| 56 | unsafe { | ||
| 57 | cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); | ||
| 58 | cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH1); | ||
| 59 | } | ||
| 60 | |||
| 61 | // Create UART configuration | ||
| 62 | let config = Config { | ||
| 63 | baudrate_bps: 115_200, | ||
| 64 | enable_tx: true, | ||
| 65 | enable_rx: true, | ||
| 66 | ..Default::default() | ||
| 67 | }; | ||
| 68 | |||
| 69 | // Create UART instance with DMA channels | ||
| 70 | let mut lpuart = LpuartDma::new( | ||
| 71 | p.LPUART2, | ||
| 72 | p.P2_2, // TX pin | ||
| 73 | p.P2_3, // RX pin | ||
| 74 | p.DMA_CH0, // TX DMA channel | ||
| 75 | p.DMA_CH1, // RX DMA channel | ||
| 76 | config, | ||
| 77 | ) | ||
| 78 | .unwrap(); | ||
| 79 | |||
| 80 | // Send a message using DMA | ||
| 81 | let tx_msg = b"Hello from LPUART2 DMA TX!\r\n"; | ||
| 82 | lpuart | ||
| 83 | .write_dma(edma, DMA_REQ_LPUART2_TX, tx_msg) | ||
| 84 | .await | ||
| 85 | .unwrap(); | ||
| 86 | |||
| 87 | defmt::info!("TX DMA complete"); | ||
| 88 | |||
| 89 | // Send prompt | ||
| 90 | let prompt = b"Type 16 characters to echo via DMA:\r\n"; | ||
| 91 | lpuart | ||
| 92 | .write_dma(edma, DMA_REQ_LPUART2_TX, prompt) | ||
| 93 | .await | ||
| 94 | .unwrap(); | ||
| 95 | |||
| 96 | // Receive 16 characters using DMA | ||
| 97 | let mut rx_buf = [0u8; 16]; | ||
| 98 | lpuart | ||
| 99 | .read_dma(edma, DMA_REQ_LPUART2_RX, &mut rx_buf) | ||
| 100 | .await | ||
| 101 | .unwrap(); | ||
| 102 | |||
| 103 | defmt::info!("RX DMA complete"); | ||
| 104 | |||
| 105 | // Echo back the received data | ||
| 106 | let echo_prefix = b"\r\nReceived: "; | ||
| 107 | lpuart | ||
| 108 | .write_dma(edma, DMA_REQ_LPUART2_TX, echo_prefix) | ||
| 109 | .await | ||
| 110 | .unwrap(); | ||
| 111 | lpuart | ||
| 112 | .write_dma(edma, DMA_REQ_LPUART2_TX, &rx_buf) | ||
| 113 | .await | ||
| 114 | .unwrap(); | ||
| 115 | let done_msg = b"\r\nDone!\r\n"; | ||
| 116 | lpuart | ||
| 117 | .write_dma(edma, DMA_REQ_LPUART2_TX, done_msg) | ||
| 118 | .await | ||
| 119 | .unwrap(); | ||
| 120 | |||
| 121 | defmt::info!("Example complete"); | ||
| 122 | |||
| 123 | loop { | ||
| 124 | cortex_m::asm::wfe(); | ||
| 125 | } | ||
| 126 | } | ||
| 127 | |||
diff --git a/examples/src/bin/lpuart_ring_buffer.rs b/examples/src/bin/lpuart_ring_buffer.rs new file mode 100644 index 000000000..bc666560c --- /dev/null +++ b/examples/src/bin/lpuart_ring_buffer.rs | |||
| @@ -0,0 +1,162 @@ | |||
| 1 | //! LPUART Ring Buffer DMA example for MCXA276. | ||
| 2 | //! | ||
| 3 | //! This example demonstrates using the new `RingBuffer` API for continuous | ||
| 4 | //! circular DMA reception from a UART peripheral. | ||
| 5 | //! | ||
| 6 | //! # Features demonstrated: | ||
| 7 | //! - `setup_circular_read()` 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. Set up a circular DMA transfer from LPUART RX to a ring buffer | ||
| 14 | //! 2. DMA continuously writes received bytes into the buffer, wrapping around | ||
| 15 | //! 3. Application asynchronously reads data as it arrives | ||
| 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::clocks::config::Div8; | ||
| 23 | use embassy_mcxa::clocks::Gate; | ||
| 24 | use embassy_mcxa::dma::{self, DmaChannel, DmaCh0InterruptHandler, DmaCh1InterruptHandler, DMA_REQ_LPUART2_RX}; | ||
| 25 | use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; | ||
| 26 | use embassy_mcxa::{bind_interrupts, pac}; | ||
| 27 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 28 | |||
| 29 | // Bind DMA channel interrupts | ||
| 30 | bind_interrupts!(struct Irqs { | ||
| 31 | DMA_CH0 => DmaCh0InterruptHandler; | ||
| 32 | DMA_CH1 => DmaCh1InterruptHandler; | ||
| 33 | }); | ||
| 34 | |||
| 35 | // Ring buffer for RX - power of 2 is ideal for modulo efficiency | ||
| 36 | static mut RX_RING_BUFFER: [u8; 64] = [0; 64]; | ||
| 37 | |||
| 38 | /// Helper to write a byte as hex to UART | ||
| 39 | fn write_hex(tx: &mut LpuartTx<'_, Blocking>, byte: u8) { | ||
| 40 | const HEX: &[u8; 16] = b"0123456789ABCDEF"; | ||
| 41 | let buf = [HEX[(byte >> 4) as usize], HEX[(byte & 0x0F) as usize]]; | ||
| 42 | tx.blocking_write(&buf).ok(); | ||
| 43 | } | ||
| 44 | |||
| 45 | #[embassy_executor::main] | ||
| 46 | async fn main(_spawner: Spawner) { | ||
| 47 | // Small delay to allow probe-rs to attach after reset | ||
| 48 | for _ in 0..100_000 { | ||
| 49 | cortex_m::asm::nop(); | ||
| 50 | } | ||
| 51 | |||
| 52 | let mut cfg = hal::config::Config::default(); | ||
| 53 | cfg.clock_cfg.sirc.fro_12m_enabled = true; | ||
| 54 | cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); | ||
| 55 | let p = hal::init(cfg); | ||
| 56 | |||
| 57 | defmt::info!("LPUART Ring Buffer DMA example starting..."); | ||
| 58 | |||
| 59 | // Enable DMA0 clock and release reset | ||
| 60 | unsafe { | ||
| 61 | hal::peripherals::DMA0::enable_clock(); | ||
| 62 | hal::peripherals::DMA0::release_reset(); | ||
| 63 | } | ||
| 64 | |||
| 65 | let pac_periphs = unsafe { pac::Peripherals::steal() }; | ||
| 66 | |||
| 67 | // Initialize DMA | ||
| 68 | unsafe { | ||
| 69 | dma::init(&pac_periphs); | ||
| 70 | } | ||
| 71 | |||
| 72 | // Enable DMA interrupts | ||
| 73 | unsafe { | ||
| 74 | cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); | ||
| 75 | cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH1); | ||
| 76 | } | ||
| 77 | |||
| 78 | // Create UART configuration | ||
| 79 | let config = Config { | ||
| 80 | baudrate_bps: 115_200, | ||
| 81 | enable_tx: true, | ||
| 82 | enable_rx: true, | ||
| 83 | ..Default::default() | ||
| 84 | }; | ||
| 85 | |||
| 86 | // Create blocking UART for TX (we'll use DMA for RX only) | ||
| 87 | let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); | ||
| 88 | let (mut tx, _rx) = lpuart.split(); | ||
| 89 | |||
| 90 | tx.blocking_write(b"LPUART Ring Buffer DMA Example\r\n").unwrap(); | ||
| 91 | tx.blocking_write(b"==============================\r\n\r\n").unwrap(); | ||
| 92 | |||
| 93 | // Get LPUART2 RX data register address for DMA | ||
| 94 | let lpuart2 = unsafe { &*pac::Lpuart2::ptr() }; | ||
| 95 | let rx_data_addr = lpuart2.data().as_ptr() as *const u8; | ||
| 96 | |||
| 97 | // Enable RX DMA request in LPUART | ||
| 98 | lpuart2.baud().modify(|_, w| w.rdmae().enabled()); | ||
| 99 | |||
| 100 | // Create DMA channel for RX | ||
| 101 | let dma_ch_rx = DmaChannel::new(p.DMA_CH0); | ||
| 102 | let edma = dma::edma_tcd(); | ||
| 103 | |||
| 104 | // Configure the DMA mux for LPUART2 RX | ||
| 105 | unsafe { | ||
| 106 | dma_ch_rx.set_request_source(edma, DMA_REQ_LPUART2_RX); | ||
| 107 | } | ||
| 108 | |||
| 109 | tx.blocking_write(b"Setting up circular DMA for UART RX...\r\n").unwrap(); | ||
| 110 | |||
| 111 | // Set up the ring buffer with circular DMA | ||
| 112 | // This configures the DMA for continuous reception | ||
| 113 | let ring_buf = unsafe { | ||
| 114 | let buf = &mut *core::ptr::addr_of_mut!(RX_RING_BUFFER); | ||
| 115 | dma_ch_rx.setup_circular_read(rx_data_addr, buf) | ||
| 116 | }; | ||
| 117 | |||
| 118 | // Enable DMA requests to start continuous reception | ||
| 119 | unsafe { | ||
| 120 | dma_ch_rx.enable_request(edma); | ||
| 121 | } | ||
| 122 | |||
| 123 | tx.blocking_write(b"Ring buffer ready! Type characters to see them echoed.\r\n").unwrap(); | ||
| 124 | tx.blocking_write(b"The DMA continuously receives in the background.\r\n\r\n").unwrap(); | ||
| 125 | |||
| 126 | // Main loop: read from ring buffer and echo back | ||
| 127 | let mut read_buf = [0u8; 16]; | ||
| 128 | let mut total_received: usize = 0; | ||
| 129 | |||
| 130 | loop { | ||
| 131 | // Async read - waits until data is available | ||
| 132 | match ring_buf.read(&mut read_buf).await { | ||
| 133 | Ok(n) if n > 0 => { | ||
| 134 | total_received += n; | ||
| 135 | |||
| 136 | // Echo back what we received | ||
| 137 | tx.blocking_write(b"RX[").unwrap(); | ||
| 138 | for (i, &byte) in read_buf.iter().enumerate().take(n) { | ||
| 139 | write_hex(&mut tx, byte); | ||
| 140 | if i < n - 1 { | ||
| 141 | tx.blocking_write(b" ").unwrap(); | ||
| 142 | } | ||
| 143 | } | ||
| 144 | tx.blocking_write(b"]: ").unwrap(); | ||
| 145 | tx.blocking_write(&read_buf[..n]).unwrap(); | ||
| 146 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 147 | |||
| 148 | defmt::info!("Received {} bytes, total: {}", n, total_received); | ||
| 149 | } | ||
| 150 | Ok(_) => { | ||
| 151 | // No data, shouldn't happen with async read | ||
| 152 | } | ||
| 153 | Err(_) => { | ||
| 154 | // Overrun detected | ||
| 155 | tx.blocking_write(b"ERROR: Ring buffer overrun!\r\n").unwrap(); | ||
| 156 | defmt::error!("Ring buffer overrun!"); | ||
| 157 | ring_buf.clear(); | ||
| 158 | } | ||
| 159 | } | ||
| 160 | } | ||
| 161 | } | ||
| 162 | |||
