From b252db845e19603faf528cf93fe0c44757a27430 Mon Sep 17 00:00:00 2001 From: James Munns Date: Fri, 5 Dec 2025 14:28:47 +0100 Subject: Move --- examples/mcxa/src/bin/adc_interrupt.rs | 84 +++++ examples/mcxa/src/bin/adc_polling.rs | 68 ++++ examples/mcxa/src/bin/blinky.rs | 36 ++ examples/mcxa/src/bin/button.rs | 23 ++ examples/mcxa/src/bin/button_async.rs | 29 ++ examples/mcxa/src/bin/clkout.rs | 69 ++++ examples/mcxa/src/bin/dma_channel_link.rs | 372 ++++++++++++++++++++ examples/mcxa/src/bin/dma_interleave_transfer.rs | 215 ++++++++++++ examples/mcxa/src/bin/dma_mem_to_mem.rs | 229 +++++++++++++ examples/mcxa/src/bin/dma_memset.rs | 218 ++++++++++++ examples/mcxa/src/bin/dma_ping_pong_transfer.rs | 376 +++++++++++++++++++++ examples/mcxa/src/bin/dma_scatter_gather.rs | 262 ++++++++++++++ .../mcxa/src/bin/dma_scatter_gather_builder.rs | 231 +++++++++++++ examples/mcxa/src/bin/dma_wrap_transfer.rs | 222 ++++++++++++ examples/mcxa/src/bin/hello.rs | 119 +++++++ examples/mcxa/src/bin/i2c-blocking.rs | 31 ++ examples/mcxa/src/bin/i2c-scan-blocking.rs | 41 +++ examples/mcxa/src/bin/lpuart_buffered.rs | 62 ++++ examples/mcxa/src/bin/lpuart_dma.rs | 81 +++++ examples/mcxa/src/bin/lpuart_polling.rs | 47 +++ examples/mcxa/src/bin/lpuart_ring_buffer.rs | 130 +++++++ examples/mcxa/src/bin/rtc_alarm.rs | 74 ++++ examples/mcxa/src/lib.rs | 16 + 23 files changed, 3035 insertions(+) create mode 100644 examples/mcxa/src/bin/adc_interrupt.rs create mode 100644 examples/mcxa/src/bin/adc_polling.rs create mode 100644 examples/mcxa/src/bin/blinky.rs create mode 100644 examples/mcxa/src/bin/button.rs create mode 100644 examples/mcxa/src/bin/button_async.rs create mode 100644 examples/mcxa/src/bin/clkout.rs create mode 100644 examples/mcxa/src/bin/dma_channel_link.rs create mode 100644 examples/mcxa/src/bin/dma_interleave_transfer.rs create mode 100644 examples/mcxa/src/bin/dma_mem_to_mem.rs create mode 100644 examples/mcxa/src/bin/dma_memset.rs create mode 100644 examples/mcxa/src/bin/dma_ping_pong_transfer.rs create mode 100644 examples/mcxa/src/bin/dma_scatter_gather.rs create mode 100644 examples/mcxa/src/bin/dma_scatter_gather_builder.rs create mode 100644 examples/mcxa/src/bin/dma_wrap_transfer.rs create mode 100644 examples/mcxa/src/bin/hello.rs create mode 100644 examples/mcxa/src/bin/i2c-blocking.rs create mode 100644 examples/mcxa/src/bin/i2c-scan-blocking.rs create mode 100644 examples/mcxa/src/bin/lpuart_buffered.rs create mode 100644 examples/mcxa/src/bin/lpuart_dma.rs create mode 100644 examples/mcxa/src/bin/lpuart_polling.rs create mode 100644 examples/mcxa/src/bin/lpuart_ring_buffer.rs create mode 100644 examples/mcxa/src/bin/rtc_alarm.rs create mode 100644 examples/mcxa/src/lib.rs (limited to 'examples/mcxa/src') diff --git a/examples/mcxa/src/bin/adc_interrupt.rs b/examples/mcxa/src/bin/adc_interrupt.rs new file mode 100644 index 000000000..83d8046b3 --- /dev/null +++ b/examples/mcxa/src/bin/adc_interrupt.rs @@ -0,0 +1,84 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_mcxa_examples::init_adc_pins; +use hal::adc::{LpadcConfig, TriggerPriorityPolicy}; +use hal::clocks::periph_helpers::{AdcClockSel, Div4}; +use hal::clocks::PoweredClock; +use hal::pac::adc1::cfg::{Pwrsel, Refsel}; +use hal::pac::adc1::cmdl1::{Adch, Mode}; +use hal::pac::adc1::ctrl::CalAvgs; +use hal::pac::adc1::tctrl::Tcmd; +use hal::{bind_interrupts, InterruptExt}; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +bind_interrupts!(struct Irqs { + ADC1 => hal::adc::AdcHandler; +}); + +#[used] +#[no_mangle] +static KEEP_ADC: unsafe extern "C" fn() = ADC1; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = hal::init(hal::config::Config::default()); + + defmt::info!("ADC interrupt Example"); + + unsafe { + init_adc_pins(); + } + + let adc_config = LpadcConfig { + enable_in_doze_mode: true, + conversion_average_mode: CalAvgs::Average128, + enable_analog_preliminary: true, + power_up_delay: 0x80, + reference_voltage_source: Refsel::Option3, + power_level_mode: Pwrsel::Lowest, + trigger_priority_policy: TriggerPriorityPolicy::ConvPreemptImmediatelyNotAutoResumed, + enable_conv_pause: false, + conv_pause_delay: 0, + fifo_watermark: 0, + power: PoweredClock::NormalEnabledDeepSleepDisabled, + source: AdcClockSel::FroLfDiv, + div: Div4::no_div(), + }; + let adc = hal::adc::Adc::::new(p.ADC1, adc_config); + + adc.do_offset_calibration(); + adc.do_auto_calibration(); + + let mut conv_command_config = adc.get_default_conv_command_config(); + conv_command_config.channel_number = Adch::SelectCorrespondingChannel8; + conv_command_config.conversion_resolution_mode = Mode::Data16Bits; + adc.set_conv_command_config(1, &conv_command_config); + + let mut conv_trigger_config = adc.get_default_conv_trigger_config(); + conv_trigger_config.target_command_id = Tcmd::ExecuteCmd1; + conv_trigger_config.enable_hardware_trigger = false; + adc.set_conv_trigger_config(0, &conv_trigger_config); + + defmt::info!("ADC configuration done..."); + + adc.enable_interrupt(0x1); + + unsafe { + hal::interrupt::ADC1.enable(); + } + + unsafe { + cortex_m::interrupt::enable(); + } + + loop { + adc.do_software_trigger(1); + while !adc.is_interrupt_triggered() { + // Wait until the interrupt is triggered + } + defmt::info!("*** ADC interrupt TRIGGERED! ***"); + //TBD need to print the value + } +} diff --git a/examples/mcxa/src/bin/adc_polling.rs b/examples/mcxa/src/bin/adc_polling.rs new file mode 100644 index 000000000..ddf3f586b --- /dev/null +++ b/examples/mcxa/src/bin/adc_polling.rs @@ -0,0 +1,68 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_mcxa_examples::init_adc_pins; +use hal::adc::{ConvResult, LpadcConfig, TriggerPriorityPolicy}; +use hal::clocks::periph_helpers::{AdcClockSel, Div4}; +use hal::clocks::PoweredClock; +use hal::pac::adc1::cfg::{Pwrsel, Refsel}; +use hal::pac::adc1::cmdl1::{Adch, Mode}; +use hal::pac::adc1::ctrl::CalAvgs; +use hal::pac::adc1::tctrl::Tcmd; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +const G_LPADC_RESULT_SHIFT: u32 = 0; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = hal::init(hal::config::Config::default()); + + unsafe { + init_adc_pins(); + } + + defmt::info!("=== ADC polling Example ==="); + + let adc_config = LpadcConfig { + enable_in_doze_mode: true, + conversion_average_mode: CalAvgs::Average128, + enable_analog_preliminary: true, + power_up_delay: 0x80, + reference_voltage_source: Refsel::Option3, + power_level_mode: Pwrsel::Lowest, + trigger_priority_policy: TriggerPriorityPolicy::ConvPreemptImmediatelyNotAutoResumed, + enable_conv_pause: false, + conv_pause_delay: 0, + fifo_watermark: 0, + power: PoweredClock::NormalEnabledDeepSleepDisabled, + source: AdcClockSel::FroLfDiv, + div: Div4::no_div(), + }; + let adc = hal::adc::Adc::::new(p.ADC1, adc_config); + + adc.do_offset_calibration(); + adc.do_auto_calibration(); + + let mut conv_command_config = adc.get_default_conv_command_config(); + conv_command_config.channel_number = Adch::SelectCorrespondingChannel8; + conv_command_config.conversion_resolution_mode = Mode::Data16Bits; + adc.set_conv_command_config(1, &conv_command_config); + + let mut conv_trigger_config = adc.get_default_conv_trigger_config(); + conv_trigger_config.target_command_id = Tcmd::ExecuteCmd1; + conv_trigger_config.enable_hardware_trigger = false; + adc.set_conv_trigger_config(0, &conv_trigger_config); + + defmt::info!("=== ADC configuration done... ==="); + + loop { + adc.do_software_trigger(1); + let mut result: Option = None; + while result.is_none() { + result = hal::adc::get_conv_result(); + } + let value = result.unwrap().conv_value >> G_LPADC_RESULT_SHIFT; + defmt::info!("value: {=u16}", value); + } +} diff --git a/examples/mcxa/src/bin/blinky.rs b/examples/mcxa/src/bin/blinky.rs new file mode 100644 index 000000000..dd08ec0d9 --- /dev/null +++ b/examples/mcxa/src/bin/blinky.rs @@ -0,0 +1,36 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_time::Timer; +use hal::gpio::{DriveStrength, Level, Output, SlewRate}; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = hal::init(hal::config::Config::default()); + + defmt::info!("Blink example"); + + let mut red = Output::new(p.P3_18, Level::High, DriveStrength::Normal, SlewRate::Fast); + let mut green = Output::new(p.P3_19, Level::High, DriveStrength::Normal, SlewRate::Fast); + let mut blue = Output::new(p.P3_21, Level::High, DriveStrength::Normal, SlewRate::Fast); + + loop { + defmt::info!("Toggle LEDs"); + + red.toggle(); + Timer::after_millis(250).await; + + red.toggle(); + green.toggle(); + Timer::after_millis(250).await; + + green.toggle(); + blue.toggle(); + Timer::after_millis(250).await; + blue.toggle(); + + Timer::after_millis(250).await; + } +} diff --git a/examples/mcxa/src/bin/button.rs b/examples/mcxa/src/bin/button.rs new file mode 100644 index 000000000..943edbb15 --- /dev/null +++ b/examples/mcxa/src/bin/button.rs @@ -0,0 +1,23 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_time::Timer; +use hal::gpio::{Input, Pull}; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = hal::init(hal::config::Config::default()); + + defmt::info!("Button example"); + + // This button is labeled "WAKEUP" on the FRDM-MCXA276 + // The board already has a 10K pullup + let monitor = Input::new(p.P1_7, Pull::Disabled); + + loop { + defmt::info!("Pin level is {:?}", monitor.get_level()); + Timer::after_millis(1000).await; + } +} diff --git a/examples/mcxa/src/bin/button_async.rs b/examples/mcxa/src/bin/button_async.rs new file mode 100644 index 000000000..6cc7b62cd --- /dev/null +++ b/examples/mcxa/src/bin/button_async.rs @@ -0,0 +1,29 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_time::Timer; +use hal::gpio::{Input, Pull}; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = hal::init(hal::config::Config::default()); + + defmt::info!("GPIO interrupt example"); + + // This button is labeled "WAKEUP" on the FRDM-MCXA276 + // The board already has a 10K pullup + let mut pin = Input::new(p.P1_7, Pull::Disabled); + + let mut press_count = 0u32; + + loop { + pin.wait_for_falling_edge().await; + + press_count += 1; + + defmt::info!("Button pressed! Count: {}", press_count); + Timer::after_millis(50).await; + } +} diff --git a/examples/mcxa/src/bin/clkout.rs b/examples/mcxa/src/bin/clkout.rs new file mode 100644 index 000000000..bfd963540 --- /dev/null +++ b/examples/mcxa/src/bin/clkout.rs @@ -0,0 +1,69 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_mcxa::clkout::{ClockOut, ClockOutSel, Config, Div4}; +use embassy_mcxa::clocks::PoweredClock; +use embassy_mcxa::gpio::{DriveStrength, SlewRate}; +use embassy_mcxa::{Level, Output}; +use embassy_time::Timer; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +/// Demonstrate CLKOUT, using Pin P4.2 +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = hal::init(hal::config::Config::default()); + let mut pin = p.P4_2; + let mut clkout = p.CLKOUT; + + loop { + defmt::info!("Set Low..."); + let mut output = Output::new(pin.reborrow(), Level::Low, DriveStrength::Normal, SlewRate::Slow); + Timer::after_millis(500).await; + + defmt::info!("Set High..."); + output.set_high(); + Timer::after_millis(400).await; + + defmt::info!("Set Low..."); + output.set_low(); + Timer::after_millis(500).await; + + defmt::info!("16k..."); + // Run Clock Out with the 16K clock + let _clock_out = ClockOut::new( + clkout.reborrow(), + pin.reborrow(), + Config { + sel: ClockOutSel::Clk16K, + div: Div4::no_div(), + level: PoweredClock::NormalEnabledDeepSleepDisabled, + }, + ) + .unwrap(); + + Timer::after_millis(3000).await; + + defmt::info!("Set Low..."); + drop(_clock_out); + + let _output = Output::new(pin.reborrow(), Level::Low, DriveStrength::Normal, SlewRate::Slow); + Timer::after_millis(500).await; + + // Run Clock Out with the 12M clock, divided by 3 + defmt::info!("4M..."); + let _clock_out = ClockOut::new( + clkout.reborrow(), + pin.reborrow(), + Config { + sel: ClockOutSel::Fro12M, + div: const { Div4::from_divisor(3).unwrap() }, + level: PoweredClock::NormalEnabledDeepSleepDisabled, + }, + ) + .unwrap(); + + // Let it run for 3 seconds... + Timer::after_millis(3000).await; + } +} diff --git a/examples/mcxa/src/bin/dma_channel_link.rs b/examples/mcxa/src/bin/dma_channel_link.rs new file mode 100644 index 000000000..92c7a9681 --- /dev/null +++ b/examples/mcxa/src/bin/dma_channel_link.rs @@ -0,0 +1,372 @@ +//! DMA channel linking example for MCXA276. +//! +//! This example demonstrates DMA channel linking (minor and major loop linking): +//! - Channel 0: Transfers SRC_BUFFER to DEST_BUFFER0, with: +//! - Minor Link to Channel 1 (triggers CH1 after each minor loop) +//! - Major Link to Channel 2 (triggers CH2 after major loop completes) +//! - Channel 1: Transfers SRC_BUFFER to DEST_BUFFER1 (triggered by CH0 minor link) +//! - Channel 2: Transfers SRC_BUFFER to DEST_BUFFER2 (triggered by CH0 major link) +//! +//! # Embassy-style features demonstrated: +//! - `DmaChannel::new()` for channel creation +//! - `DmaChannel::is_done()` and `clear_done()` helper methods +//! - Channel linking with `set_minor_link()` and `set_major_link()` +//! - Standard `DmaCh*InterruptHandler` with `bind_interrupts!` macro + +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_mcxa::clocks::config::Div8; +use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaCh1InterruptHandler, DmaCh2InterruptHandler, DmaChannel}; +use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; +use embassy_mcxa::{bind_interrupts, pac}; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +// Buffers +static mut SRC_BUFFER: [u32; 4] = [1, 2, 3, 4]; +static mut DEST_BUFFER0: [u32; 4] = [0; 4]; +static mut DEST_BUFFER1: [u32; 4] = [0; 4]; +static mut DEST_BUFFER2: [u32; 4] = [0; 4]; + +// Bind DMA channel interrupts using Embassy-style macro +// The standard handlers call on_interrupt() which wakes wakers and clears flags +bind_interrupts!(struct Irqs { + DMA_CH0 => DmaCh0InterruptHandler; + DMA_CH1 => DmaCh1InterruptHandler; + DMA_CH2 => DmaCh2InterruptHandler; +}); + +/// Helper to write a u32 as decimal ASCII to UART +fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { + let mut buf = [0u8; 10]; + let mut n = val; + let mut i = buf.len(); + + if n == 0 { + tx.blocking_write(b"0").ok(); + return; + } + + while n > 0 { + i -= 1; + buf[i] = b'0' + (n % 10) as u8; + n /= 10; + } + + tx.blocking_write(&buf[i..]).ok(); +} + +/// Helper to print a buffer to UART +fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { + tx.blocking_write(b"[").ok(); + unsafe { + for i in 0..len { + write_u32(tx, *buf_ptr.add(i)); + if i < len - 1 { + tx.blocking_write(b", ").ok(); + } + } + } + tx.blocking_write(b"]").ok(); +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // Small delay to allow probe-rs to attach after reset + for _ in 0..100_000 { + cortex_m::asm::nop(); + } + + let mut cfg = hal::config::Config::default(); + cfg.clock_cfg.sirc.fro_12m_enabled = true; + cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); + let p = hal::init(cfg); + + defmt::info!("DMA channel link example starting..."); + + // DMA is initialized during hal::init() - no need to call ensure_init() + + let pac_periphs = unsafe { pac::Peripherals::steal() }; + let dma0 = &pac_periphs.dma0; + let edma = unsafe { &*pac::Edma0Tcd0::ptr() }; + + // Clear any residual state + for i in 0..3 { + let t = edma.tcd(i); + t.ch_csr().write(|w| w.erq().disable().done().clear_bit_by_one()); + t.ch_int().write(|w| w.int().clear_bit_by_one()); + t.ch_es().write(|w| w.err().clear_bit_by_one()); + t.ch_mux().write(|w| unsafe { w.bits(0) }); + } + + // Clear Global Halt/Error state + dma0.mp_csr().modify(|_, w| { + w.halt() + .normal_operation() + .hae() + .normal_operation() + .ecx() + .normal_operation() + .cx() + .normal_operation() + }); + + unsafe { + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH1); + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH2); + } + + let config = Config { + baudrate_bps: 115_200, + ..Default::default() + }; + + let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); + let (mut tx, _rx) = lpuart.split(); + + tx.blocking_write(b"EDMA channel link example begin.\r\n\r\n").unwrap(); + + // Initialize buffers + unsafe { + SRC_BUFFER = [1, 2, 3, 4]; + DEST_BUFFER0 = [0; 4]; + DEST_BUFFER1 = [0; 4]; + DEST_BUFFER2 = [0; 4]; + } + + tx.blocking_write(b"Source Buffer: ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(SRC_BUFFER) as *const u32, 4); + tx.blocking_write(b"\r\n").unwrap(); + + tx.blocking_write(b"DEST0 (before): ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER0) as *const u32, 4); + tx.blocking_write(b"\r\n").unwrap(); + + tx.blocking_write(b"DEST1 (before): ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER1) as *const u32, 4); + tx.blocking_write(b"\r\n").unwrap(); + + tx.blocking_write(b"DEST2 (before): ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER2) as *const u32, 4); + tx.blocking_write(b"\r\n\r\n").unwrap(); + + tx.blocking_write(b"Configuring DMA channels with Embassy-style API...\r\n") + .unwrap(); + + let ch0 = DmaChannel::new(p.DMA_CH0); + let ch1 = DmaChannel::new(p.DMA_CH1); + let ch2 = DmaChannel::new(p.DMA_CH2); + + // Configure channels using direct TCD access (advanced feature demo) + // This example demonstrates channel linking which requires direct TCD manipulation + + // Helper to configure TCD for memory-to-memory transfer + // Parameters: channel, src, dst, width, nbytes (minor loop), count (major loop), interrupt + #[allow(clippy::too_many_arguments)] + unsafe fn configure_tcd( + edma: &embassy_mcxa::pac::edma_0_tcd0::RegisterBlock, + ch: usize, + src: u32, + dst: u32, + width: u8, + nbytes: u32, + count: u16, + enable_int: bool, + ) { + let t = edma.tcd(ch); + + // Reset channel state + t.ch_csr().write(|w| { + w.erq() + .disable() + .earq() + .disable() + .eei() + .no_error() + .ebw() + .disable() + .done() + .clear_bit_by_one() + }); + t.ch_es().write(|w| w.bits(0)); + t.ch_int().write(|w| w.int().clear_bit_by_one()); + + // Source/destination addresses + t.tcd_saddr().write(|w| w.saddr().bits(src)); + t.tcd_daddr().write(|w| w.daddr().bits(dst)); + + // Offsets: increment by width + t.tcd_soff().write(|w| w.soff().bits(width as u16)); + t.tcd_doff().write(|w| w.doff().bits(width as u16)); + + // Attributes: size = log2(width) + let size = match width { + 1 => 0, + 2 => 1, + 4 => 2, + _ => 0, + }; + t.tcd_attr().write(|w| w.ssize().bits(size).dsize().bits(size)); + + // Number of bytes per minor loop + t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(nbytes)); + + // Major loop: reset source address after major loop + let total_bytes = nbytes * count as u32; + t.tcd_slast_sda() + .write(|w| w.slast_sda().bits(-(total_bytes as i32) as u32)); + t.tcd_dlast_sga() + .write(|w| w.dlast_sga().bits(-(total_bytes as i32) as u32)); + + // Major loop count + t.tcd_biter_elinkno().write(|w| w.biter().bits(count)); + t.tcd_citer_elinkno().write(|w| w.citer().bits(count)); + + // Control/status: enable interrupt if requested + if enable_int { + t.tcd_csr().write(|w| w.intmajor().set_bit()); + } else { + t.tcd_csr().write(|w| w.intmajor().clear_bit()); + } + + cortex_m::asm::dsb(); + } + + unsafe { + // Channel 0: Transfer 16 bytes total (8 bytes per minor loop, 2 major iterations) + // Minor Link -> Channel 1 + // Major Link -> Channel 2 + configure_tcd( + edma, + 0, + core::ptr::addr_of!(SRC_BUFFER) as u32, + core::ptr::addr_of_mut!(DEST_BUFFER0) as u32, + 4, // src width + 8, // nbytes (minor loop = 2 words) + 2, // count (major loop = 2 iterations) + false, // no interrupt + ); + ch0.set_minor_link(1); // Link to CH1 after each minor loop + ch0.set_major_link(2); // Link to CH2 after major loop + + // Channel 1: Transfer 16 bytes (triggered by CH0 minor link) + configure_tcd( + edma, + 1, + core::ptr::addr_of!(SRC_BUFFER) as u32, + core::ptr::addr_of_mut!(DEST_BUFFER1) as u32, + 4, + 16, // full buffer in one minor loop + 1, // 1 major iteration + false, + ); + + // Channel 2: Transfer 16 bytes (triggered by CH0 major link) + configure_tcd( + edma, + 2, + core::ptr::addr_of!(SRC_BUFFER) as u32, + core::ptr::addr_of_mut!(DEST_BUFFER2) as u32, + 4, + 16, // full buffer in one minor loop + 1, // 1 major iteration + true, // enable interrupt + ); + } + + tx.blocking_write(b"Triggering Channel 0 (1st minor loop)...\r\n") + .unwrap(); + + // Trigger first minor loop of CH0 + unsafe { + ch0.trigger_start(); + } + + // Wait for CH1 to complete (triggered by CH0 minor link) + while !ch1.is_done() { + cortex_m::asm::nop(); + } + unsafe { + ch1.clear_done(); + } + + tx.blocking_write(b"CH1 done (via minor link).\r\n").unwrap(); + tx.blocking_write(b"Triggering Channel 0 (2nd minor loop)...\r\n") + .unwrap(); + + // Trigger second minor loop of CH0 + unsafe { + ch0.trigger_start(); + } + + // Wait for CH0 major loop to complete + while !ch0.is_done() { + cortex_m::asm::nop(); + } + unsafe { + ch0.clear_done(); + } + + tx.blocking_write(b"CH0 major loop done.\r\n").unwrap(); + + // Wait for CH2 to complete (triggered by CH0 major link) + // Using is_done() instead of AtomicBool - the standard interrupt handler + // clears the interrupt flag and wakes wakers, but DONE bit remains set + while !ch2.is_done() { + cortex_m::asm::nop(); + } + unsafe { + ch2.clear_done(); + } + + tx.blocking_write(b"CH2 done (via major link).\r\n\r\n").unwrap(); + + tx.blocking_write(b"EDMA channel link example finish.\r\n\r\n").unwrap(); + + tx.blocking_write(b"DEST0 (after): ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER0) as *const u32, 4); + tx.blocking_write(b"\r\n").unwrap(); + + tx.blocking_write(b"DEST1 (after): ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER1) as *const u32, 4); + tx.blocking_write(b"\r\n").unwrap(); + + tx.blocking_write(b"DEST2 (after): ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER2) as *const u32, 4); + tx.blocking_write(b"\r\n\r\n").unwrap(); + + // Verify all buffers match source + let mut success = true; + unsafe { + let src_ptr = core::ptr::addr_of!(SRC_BUFFER) as *const u32; + let dst0_ptr = core::ptr::addr_of!(DEST_BUFFER0) as *const u32; + let dst1_ptr = core::ptr::addr_of!(DEST_BUFFER1) as *const u32; + let dst2_ptr = core::ptr::addr_of!(DEST_BUFFER2) as *const u32; + + for i in 0..4 { + if *dst0_ptr.add(i) != *src_ptr.add(i) { + success = false; + } + if *dst1_ptr.add(i) != *src_ptr.add(i) { + success = false; + } + if *dst2_ptr.add(i) != *src_ptr.add(i) { + success = false; + } + } + } + + if success { + tx.blocking_write(b"PASS: Data verified.\r\n").unwrap(); + defmt::info!("PASS: Data verified."); + } else { + tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); + defmt::error!("FAIL: Mismatch detected!"); + } + + loop { + cortex_m::asm::wfe(); + } +} diff --git a/examples/mcxa/src/bin/dma_interleave_transfer.rs b/examples/mcxa/src/bin/dma_interleave_transfer.rs new file mode 100644 index 000000000..7876e8978 --- /dev/null +++ b/examples/mcxa/src/bin/dma_interleave_transfer.rs @@ -0,0 +1,215 @@ +//! DMA interleaved transfer example for MCXA276. +//! +//! This example demonstrates using DMA with custom source/destination offsets +//! to interleave data during transfer. +//! +//! # Embassy-style features demonstrated: +//! - `TransferOptions::default()` for configuration (used internally) +//! - DMA channel with `DmaChannel::new()` + +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_mcxa::clocks::config::Div8; +use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaChannel}; +use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; +use embassy_mcxa::{bind_interrupts, pac}; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +// Bind DMA channel 0 interrupt using Embassy-style macro +bind_interrupts!(struct Irqs { + DMA_CH0 => DmaCh0InterruptHandler; +}); + +const BUFFER_LENGTH: usize = 16; +const HALF_BUFF_LENGTH: usize = BUFFER_LENGTH / 2; + +// Buffers in RAM +static mut SRC_BUFFER: [u32; HALF_BUFF_LENGTH] = [0; HALF_BUFF_LENGTH]; +static mut DEST_BUFFER: [u32; BUFFER_LENGTH] = [0; BUFFER_LENGTH]; + +/// Helper to write a u32 as decimal ASCII to UART +fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { + let mut buf = [0u8; 10]; + let mut n = val; + let mut i = buf.len(); + + if n == 0 { + tx.blocking_write(b"0").ok(); + return; + } + + while n > 0 { + i -= 1; + buf[i] = b'0' + (n % 10) as u8; + n /= 10; + } + + tx.blocking_write(&buf[i..]).ok(); +} + +/// Helper to print a buffer to UART +fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { + tx.blocking_write(b"[").ok(); + unsafe { + for i in 0..len { + write_u32(tx, *buf_ptr.add(i)); + if i < len - 1 { + tx.blocking_write(b", ").ok(); + } + } + } + tx.blocking_write(b"]").ok(); +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // Small delay to allow probe-rs to attach after reset + for _ in 0..100_000 { + cortex_m::asm::nop(); + } + + let mut cfg = hal::config::Config::default(); + cfg.clock_cfg.sirc.fro_12m_enabled = true; + cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); + let p = hal::init(cfg); + + defmt::info!("DMA interleave transfer example starting..."); + + // Enable DMA interrupt (DMA clock/reset/init is handled automatically by HAL) + unsafe { + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); + } + + let config = Config { + baudrate_bps: 115_200, + ..Default::default() + }; + + let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); + let (mut tx, _rx) = lpuart.split(); + + tx.blocking_write(b"EDMA interleave transfer example begin.\r\n\r\n") + .unwrap(); + + // Initialize buffers + unsafe { + SRC_BUFFER = [1, 2, 3, 4, 5, 6, 7, 8]; + DEST_BUFFER = [0; BUFFER_LENGTH]; + } + + tx.blocking_write(b"Source Buffer: ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(SRC_BUFFER) as *const u32, HALF_BUFF_LENGTH); + tx.blocking_write(b"\r\n").unwrap(); + + tx.blocking_write(b"Destination Buffer (before): ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER) as *const u32, BUFFER_LENGTH); + tx.blocking_write(b"\r\n").unwrap(); + + tx.blocking_write(b"Configuring DMA with Embassy-style API...\r\n") + .unwrap(); + + // Create DMA channel using Embassy-style API + let dma_ch0 = DmaChannel::new(p.DMA_CH0); + + // Configure interleaved transfer using direct TCD access: + // - src_offset = 4: advance source by 4 bytes after each read + // - dst_offset = 8: advance dest by 8 bytes after each write + // This spreads source data across every other word in destination + unsafe { + let t = dma_ch0.tcd(); + + // Reset channel state + t.ch_csr().write(|w| { + w.erq() + .disable() + .earq() + .disable() + .eei() + .no_error() + .ebw() + .disable() + .done() + .clear_bit_by_one() + }); + t.ch_es().write(|w| w.bits(0)); + t.ch_int().write(|w| w.int().clear_bit_by_one()); + + // Source/destination addresses + t.tcd_saddr() + .write(|w| w.saddr().bits(core::ptr::addr_of_mut!(SRC_BUFFER) as u32)); + t.tcd_daddr() + .write(|w| w.daddr().bits(core::ptr::addr_of_mut!(DEST_BUFFER) as u32)); + + // Custom offsets for interleaving + t.tcd_soff().write(|w| w.soff().bits(4)); // src: +4 bytes per read + t.tcd_doff().write(|w| w.doff().bits(8)); // dst: +8 bytes per write + + // Attributes: 32-bit transfers (size = 2) + t.tcd_attr().write(|w| w.ssize().bits(2).dsize().bits(2)); + + // Transfer entire source buffer in one minor loop + let nbytes = (HALF_BUFF_LENGTH * 4) as u32; + t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(nbytes)); + + // Reset source address after major loop + t.tcd_slast_sda().write(|w| w.slast_sda().bits(-(nbytes as i32) as u32)); + // Destination uses 2x offset, so adjust accordingly + let dst_total = (HALF_BUFF_LENGTH * 8) as u32; + t.tcd_dlast_sga() + .write(|w| w.dlast_sga().bits(-(dst_total as i32) as u32)); + + // Major loop count = 1 + t.tcd_biter_elinkno().write(|w| w.biter().bits(1)); + t.tcd_citer_elinkno().write(|w| w.citer().bits(1)); + + // Enable interrupt on major loop completion + t.tcd_csr().write(|w| w.intmajor().set_bit()); + + cortex_m::asm::dsb(); + + tx.blocking_write(b"Triggering transfer...\r\n").unwrap(); + dma_ch0.trigger_start(); + } + + // Wait for completion using channel helper method + while !dma_ch0.is_done() { + cortex_m::asm::nop(); + } + unsafe { + dma_ch0.clear_done(); + } + + tx.blocking_write(b"\r\nEDMA interleave transfer example finish.\r\n\r\n") + .unwrap(); + tx.blocking_write(b"Destination Buffer (after): ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER) as *const u32, BUFFER_LENGTH); + tx.blocking_write(b"\r\n\r\n").unwrap(); + + // Verify: Even indices should match SRC_BUFFER[i/2], odd indices should be 0 + let mut mismatch = false; + unsafe { + for i in 0..BUFFER_LENGTH { + if i % 2 == 0 { + if DEST_BUFFER[i] != SRC_BUFFER[i / 2] { + mismatch = true; + } + } else if DEST_BUFFER[i] != 0 { + mismatch = true; + } + } + } + + if mismatch { + tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); + defmt::error!("FAIL: Mismatch detected!"); + } else { + tx.blocking_write(b"PASS: Data verified.\r\n").unwrap(); + defmt::info!("PASS: Data verified."); + } + + loop { + cortex_m::asm::wfe(); + } +} diff --git a/examples/mcxa/src/bin/dma_mem_to_mem.rs b/examples/mcxa/src/bin/dma_mem_to_mem.rs new file mode 100644 index 000000000..68f70e742 --- /dev/null +++ b/examples/mcxa/src/bin/dma_mem_to_mem.rs @@ -0,0 +1,229 @@ +//! DMA memory-to-memory transfer example for MCXA276. +//! +//! This example demonstrates using DMA to copy data between memory buffers +//! using the Embassy-style async API with type-safe transfers. +//! +//! # Embassy-style features demonstrated: +//! - `TransferOptions` for configuration +//! - Type-safe `mem_to_mem()` method with async `.await` +//! - `Transfer` Future that can be `.await`ed +//! - `Word` trait for automatic transfer width detection +//! - `memset()` method for filling memory with a pattern + +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_mcxa::clocks::config::Div8; +use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaChannel, TransferOptions}; +use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; +use embassy_mcxa::{bind_interrupts, pac}; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +// Bind DMA channel 0 interrupt using Embassy-style macro +bind_interrupts!(struct Irqs { + DMA_CH0 => DmaCh0InterruptHandler; +}); + +const BUFFER_LENGTH: usize = 4; + +// Buffers in RAM (static mut is automatically placed in .bss/.data) +static mut SRC_BUFFER: [u32; BUFFER_LENGTH] = [0; BUFFER_LENGTH]; +static mut DEST_BUFFER: [u32; BUFFER_LENGTH] = [0; BUFFER_LENGTH]; +static mut MEMSET_BUFFER: [u32; BUFFER_LENGTH] = [0; BUFFER_LENGTH]; + +/// Helper to write a u32 as decimal ASCII to UART +fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { + let mut buf = [0u8; 10]; // u32 max is 4294967295 (10 digits) + let mut n = val; + let mut i = buf.len(); + + if n == 0 { + tx.blocking_write(b"0").ok(); + return; + } + + while n > 0 { + i -= 1; + buf[i] = b'0' + (n % 10) as u8; + n /= 10; + } + + tx.blocking_write(&buf[i..]).ok(); +} + +/// Helper to print a buffer as [v1, v2, v3, v4] to UART +/// Takes a raw pointer to avoid warnings about shared references to mutable statics +fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const [u32; BUFFER_LENGTH]) { + tx.blocking_write(b"[").ok(); + unsafe { + let buf = &*buf_ptr; + for (i, val) in buf.iter().enumerate() { + write_u32(tx, *val); + if i < buf.len() - 1 { + tx.blocking_write(b", ").ok(); + } + } + } + tx.blocking_write(b"]").ok(); +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // Small delay to allow probe-rs to attach after reset + for _ in 0..100_000 { + cortex_m::asm::nop(); + } + + let mut cfg = hal::config::Config::default(); + cfg.clock_cfg.sirc.fro_12m_enabled = true; + cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); + let p = hal::init(cfg); + + defmt::info!("DMA memory-to-memory example starting..."); + + // Enable DMA interrupt (DMA clock/reset/init is handled automatically by HAL) + unsafe { + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); + } + + // Create UART for debug output + let config = Config { + baudrate_bps: 115_200, + ..Default::default() + }; + + let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); + let (mut tx, _rx) = lpuart.split(); + + tx.blocking_write(b"EDMA memory to memory example begin.\r\n\r\n") + .unwrap(); + + // Initialize buffers + unsafe { + SRC_BUFFER = [1, 2, 3, 4]; + DEST_BUFFER = [0; BUFFER_LENGTH]; + } + + tx.blocking_write(b"Source Buffer: ").unwrap(); + print_buffer(&mut tx, &raw const SRC_BUFFER); + tx.blocking_write(b"\r\n").unwrap(); + + tx.blocking_write(b"Destination Buffer (before): ").unwrap(); + print_buffer(&mut tx, &raw const DEST_BUFFER); + tx.blocking_write(b"\r\n").unwrap(); + + tx.blocking_write(b"Configuring DMA with Embassy-style API...\r\n") + .unwrap(); + + // Create DMA channel + let dma_ch0 = DmaChannel::new(p.DMA_CH0); + + // Configure transfer options (Embassy-style) + // TransferOptions defaults to: complete_transfer_interrupt = true + let options = TransferOptions::default(); + + // ========================================================================= + // Part 1: Embassy-style async API demonstration (mem_to_mem) + // ========================================================================= + // + // Use the new type-safe `mem_to_mem()` method: + // - Automatically determines transfer width from buffer element type (u32) + // - Returns a `Transfer` future that can be `.await`ed + // - Uses TransferOptions for consistent configuration + // + // Using async `.await` - the executor can run other tasks while waiting! + + // Perform type-safe memory-to-memory transfer using Embassy-style async API + unsafe { + let src = &*core::ptr::addr_of!(SRC_BUFFER); + let dst = &mut *core::ptr::addr_of_mut!(DEST_BUFFER); + + // Using async `.await` - the executor can run other tasks while waiting! + let transfer = dma_ch0.mem_to_mem(src, dst, options); + transfer.await; + } + + tx.blocking_write(b"DMA mem-to-mem transfer complete!\r\n\r\n").unwrap(); + tx.blocking_write(b"Destination Buffer (after): ").unwrap(); + print_buffer(&mut tx, &raw const DEST_BUFFER); + tx.blocking_write(b"\r\n").unwrap(); + + // Verify data + let mut mismatch = false; + unsafe { + for i in 0..BUFFER_LENGTH { + if SRC_BUFFER[i] != DEST_BUFFER[i] { + mismatch = true; + break; + } + } + } + + if mismatch { + tx.blocking_write(b"FAIL: mem_to_mem mismatch!\r\n").unwrap(); + defmt::error!("FAIL: mem_to_mem mismatch!"); + } else { + tx.blocking_write(b"PASS: mem_to_mem verified.\r\n\r\n").unwrap(); + defmt::info!("PASS: mem_to_mem verified."); + } + + // ========================================================================= + // Part 2: memset() demonstration + // ========================================================================= + // + // The `memset()` method fills a buffer with a pattern value: + // - Fixed source address (pattern is read repeatedly) + // - Incrementing destination address + // - Uses the same Transfer future pattern + + tx.blocking_write(b"--- Demonstrating memset() feature ---\r\n\r\n") + .unwrap(); + + tx.blocking_write(b"Memset Buffer (before): ").unwrap(); + print_buffer(&mut tx, &raw const MEMSET_BUFFER); + tx.blocking_write(b"\r\n").unwrap(); + + // Fill buffer with a pattern value using DMA memset + let pattern: u32 = 0xDEADBEEF; + tx.blocking_write(b"Filling with pattern 0xDEADBEEF...\r\n").unwrap(); + + unsafe { + let dst = &mut *core::ptr::addr_of_mut!(MEMSET_BUFFER); + + // Using blocking_wait() for demonstration - also shows non-async usage + let transfer = dma_ch0.memset(&pattern, dst, options); + transfer.blocking_wait(); + } + + tx.blocking_write(b"DMA memset complete!\r\n\r\n").unwrap(); + tx.blocking_write(b"Memset Buffer (after): ").unwrap(); + print_buffer(&mut tx, &raw const MEMSET_BUFFER); + tx.blocking_write(b"\r\n").unwrap(); + + // Verify memset result + let mut memset_ok = true; + unsafe { + #[allow(clippy::needless_range_loop)] + for i in 0..BUFFER_LENGTH { + if MEMSET_BUFFER[i] != pattern { + memset_ok = false; + break; + } + } + } + + if !memset_ok { + tx.blocking_write(b"FAIL: memset mismatch!\r\n").unwrap(); + defmt::error!("FAIL: memset mismatch!"); + } else { + tx.blocking_write(b"PASS: memset verified.\r\n\r\n").unwrap(); + defmt::info!("PASS: memset verified."); + } + + tx.blocking_write(b"=== All DMA tests complete ===\r\n").unwrap(); + + loop { + cortex_m::asm::wfe(); + } +} diff --git a/examples/mcxa/src/bin/dma_memset.rs b/examples/mcxa/src/bin/dma_memset.rs new file mode 100644 index 000000000..95e365e47 --- /dev/null +++ b/examples/mcxa/src/bin/dma_memset.rs @@ -0,0 +1,218 @@ +//! DMA memset example for MCXA276. +//! +//! This example demonstrates using DMA to fill a buffer with a repeated pattern. +//! The source address stays fixed while the destination increments. +//! +//! # Embassy-style features demonstrated: +//! - `DmaChannel::is_done()` and `clear_done()` helper methods +//! - No need to pass register block around + +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_mcxa::clocks::config::Div8; +use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaChannel}; +use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; +use embassy_mcxa::{bind_interrupts, pac}; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +// Bind DMA channel 0 interrupt using Embassy-style macro +bind_interrupts!(struct Irqs { + DMA_CH0 => DmaCh0InterruptHandler; +}); + +const BUFFER_LENGTH: usize = 4; + +// Buffers in RAM +static mut PATTERN: u32 = 0; +static mut DEST_BUFFER: [u32; BUFFER_LENGTH] = [0; BUFFER_LENGTH]; + +/// Helper to write a u32 as decimal ASCII to UART +fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { + let mut buf = [0u8; 10]; + let mut n = val; + let mut i = buf.len(); + + if n == 0 { + tx.blocking_write(b"0").ok(); + return; + } + + while n > 0 { + i -= 1; + buf[i] = b'0' + (n % 10) as u8; + n /= 10; + } + + tx.blocking_write(&buf[i..]).ok(); +} + +/// Helper to print a buffer to UART +fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { + tx.blocking_write(b"[").ok(); + unsafe { + for i in 0..len { + write_u32(tx, *buf_ptr.add(i)); + if i < len - 1 { + tx.blocking_write(b", ").ok(); + } + } + } + tx.blocking_write(b"]").ok(); +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // Small delay to allow probe-rs to attach after reset + for _ in 0..100_000 { + cortex_m::asm::nop(); + } + + let mut cfg = hal::config::Config::default(); + cfg.clock_cfg.sirc.fro_12m_enabled = true; + cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); + let p = hal::init(cfg); + + defmt::info!("DMA memset example starting..."); + + // Enable DMA interrupt (DMA clock/reset/init is handled automatically by HAL) + unsafe { + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); + } + + let config = Config { + baudrate_bps: 115_200, + ..Default::default() + }; + + let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); + let (mut tx, _rx) = lpuart.split(); + + tx.blocking_write(b"EDMA memset example begin.\r\n\r\n").unwrap(); + + // Initialize buffers + unsafe { + PATTERN = 0xDEADBEEF; + DEST_BUFFER = [0; BUFFER_LENGTH]; + } + + tx.blocking_write(b"Pattern value: 0x").unwrap(); + // Print pattern in hex + unsafe { + let hex_chars = b"0123456789ABCDEF"; + let mut hex_buf = [0u8; 8]; + let mut val = PATTERN; + for i in (0..8).rev() { + hex_buf[i] = hex_chars[(val & 0xF) as usize]; + val >>= 4; + } + tx.blocking_write(&hex_buf).ok(); + } + tx.blocking_write(b"\r\n").unwrap(); + + tx.blocking_write(b"Destination Buffer (before): ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER) as *const u32, BUFFER_LENGTH); + tx.blocking_write(b"\r\n").unwrap(); + + tx.blocking_write(b"Configuring DMA with Embassy-style API...\r\n") + .unwrap(); + + // Create DMA channel using Embassy-style API + let dma_ch0 = DmaChannel::new(p.DMA_CH0); + + // Configure memset transfer using direct TCD access: + // Source stays fixed (soff = 0, reads same pattern repeatedly) + // Destination increments (doff = 4) + unsafe { + let t = dma_ch0.tcd(); + + // Reset channel state + t.ch_csr().write(|w| { + w.erq() + .disable() + .earq() + .disable() + .eei() + .no_error() + .ebw() + .disable() + .done() + .clear_bit_by_one() + }); + t.ch_es().write(|w| w.bits(0)); + t.ch_int().write(|w| w.int().clear_bit_by_one()); + + // Source address (pattern) - fixed + t.tcd_saddr() + .write(|w| w.saddr().bits(core::ptr::addr_of_mut!(PATTERN) as u32)); + // Destination address - increments + t.tcd_daddr() + .write(|w| w.daddr().bits(core::ptr::addr_of_mut!(DEST_BUFFER) as u32)); + + // Source offset = 0 (stays fixed), Dest offset = 4 (increments) + t.tcd_soff().write(|w| w.soff().bits(0)); + t.tcd_doff().write(|w| w.doff().bits(4)); + + // Attributes: 32-bit transfers (size = 2) + t.tcd_attr().write(|w| w.ssize().bits(2).dsize().bits(2)); + + // Transfer entire buffer in one minor loop + let nbytes = (BUFFER_LENGTH * 4) as u32; + t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(nbytes)); + + // Source doesn't need adjustment (stays fixed) + t.tcd_slast_sda().write(|w| w.slast_sda().bits(0)); + // Reset dest address after major loop + t.tcd_dlast_sga().write(|w| w.dlast_sga().bits(-(nbytes as i32) as u32)); + + // Major loop count = 1 + t.tcd_biter_elinkno().write(|w| w.biter().bits(1)); + t.tcd_citer_elinkno().write(|w| w.citer().bits(1)); + + // Enable interrupt on major loop completion + t.tcd_csr().write(|w| w.intmajor().set_bit()); + + cortex_m::asm::dsb(); + + tx.blocking_write(b"Triggering transfer...\r\n").unwrap(); + dma_ch0.trigger_start(); + } + + // Wait for completion using channel helper method + while !dma_ch0.is_done() { + cortex_m::asm::nop(); + } + unsafe { + dma_ch0.clear_done(); + } + + tx.blocking_write(b"\r\nEDMA memset example finish.\r\n\r\n").unwrap(); + tx.blocking_write(b"Destination Buffer (after): ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER) as *const u32, BUFFER_LENGTH); + tx.blocking_write(b"\r\n\r\n").unwrap(); + + // Verify: All elements should equal PATTERN + let mut mismatch = false; + unsafe { + #[allow(clippy::needless_range_loop)] + for i in 0..BUFFER_LENGTH { + if DEST_BUFFER[i] != PATTERN { + mismatch = true; + break; + } + } + } + + if mismatch { + tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); + defmt::error!("FAIL: Mismatch detected!"); + } else { + tx.blocking_write(b"PASS: Data verified.\r\n").unwrap(); + defmt::info!("PASS: Data verified."); + } + + loop { + cortex_m::asm::wfe(); + } +} diff --git a/examples/mcxa/src/bin/dma_ping_pong_transfer.rs b/examples/mcxa/src/bin/dma_ping_pong_transfer.rs new file mode 100644 index 000000000..f8f543382 --- /dev/null +++ b/examples/mcxa/src/bin/dma_ping_pong_transfer.rs @@ -0,0 +1,376 @@ +//! DMA ping-pong/double-buffer transfer example for MCXA276. +//! +//! This example demonstrates two approaches for ping-pong/double-buffering: +//! +//! ## Approach 1: Scatter/Gather with linked TCDs (manual) +//! - Two TCDs link to each other for alternating transfers +//! - Uses custom handler that delegates to on_interrupt() then signals completion +//! - Note: With ESG=1, DONE bit is cleared by hardware when next TCD loads, +//! so we need an AtomicBool to track completion +//! +//! ## Approach 2: Half-transfer interrupt with wait_half() (NEW!) +//! - Single continuous transfer over entire buffer +//! - Uses half-transfer interrupt to know when first half is ready +//! - Application can process first half while second half is being filled +//! +//! # Embassy-style features demonstrated: +//! - `DmaChannel::new()` for channel creation +//! - Scatter/gather with linked TCDs +//! - Custom handler that delegates to HAL's `on_interrupt()` (best practice) +//! - Standard `DmaCh1InterruptHandler` with `bind_interrupts!` macro +//! - NEW: `wait_half()` for half-transfer interrupt handling + +#![no_std] +#![no_main] + +use core::sync::atomic::{AtomicBool, Ordering}; + +use embassy_executor::Spawner; +use embassy_mcxa::clocks::config::Div8; +use embassy_mcxa::dma::{self, DmaCh1InterruptHandler, DmaChannel, Tcd, TransferOptions}; +use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; +use embassy_mcxa::{bind_interrupts, pac}; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +// Source and destination buffers for Approach 1 (scatter/gather) +static mut SRC: [u32; 8] = [1, 2, 3, 4, 5, 6, 7, 8]; +static mut DST: [u32; 8] = [0; 8]; + +// Source and destination buffers for Approach 2 (wait_half) +static mut SRC2: [u32; 8] = [0xA1, 0xA2, 0xA3, 0xA4, 0xB1, 0xB2, 0xB3, 0xB4]; +static mut DST2: [u32; 8] = [0; 8]; + +// TCD pool for scatter/gather - must be 32-byte aligned +#[repr(C, align(32))] +struct TcdPool([Tcd; 2]); + +static mut TCD_POOL: TcdPool = TcdPool( + [Tcd { + saddr: 0, + soff: 0, + attr: 0, + nbytes: 0, + slast: 0, + daddr: 0, + doff: 0, + citer: 0, + dlast_sga: 0, + csr: 0, + biter: 0, + }; 2], +); + +// AtomicBool to track scatter/gather completion +// Note: With ESG=1, DONE bit is cleared by hardware when next TCD loads, +// so we need this flag to detect when each transfer completes +static TRANSFER_DONE: AtomicBool = AtomicBool::new(false); + +// Custom handler for scatter/gather that delegates to HAL's on_interrupt() +// This follows the "interrupts as threads" pattern - the handler does minimal work +// (delegates to HAL + sets a flag) and the main task does the actual processing +pub struct PingPongDmaHandler; + +impl embassy_mcxa::interrupt::typelevel::Handler for PingPongDmaHandler { + unsafe fn on_interrupt() { + // Delegate to HAL's on_interrupt() which clears INT flag and wakes wakers + dma::on_interrupt(0); + // Signal completion for polling (needed because ESG clears DONE bit) + TRANSFER_DONE.store(true, Ordering::Release); + } +} + +// Bind DMA channel interrupts +// CH0: Custom handler for scatter/gather (delegates to on_interrupt + sets flag) +// CH1: Standard handler for wait_half() demo +bind_interrupts!(struct Irqs { + DMA_CH0 => PingPongDmaHandler; + DMA_CH1 => DmaCh1InterruptHandler; +}); + +/// Helper to write a u32 as decimal ASCII to UART +fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { + let mut buf = [0u8; 10]; + let mut n = val; + let mut i = buf.len(); + + if n == 0 { + tx.blocking_write(b"0").ok(); + return; + } + + while n > 0 { + i -= 1; + buf[i] = b'0' + (n % 10) as u8; + n /= 10; + } + + tx.blocking_write(&buf[i..]).ok(); +} + +/// Helper to print a buffer to UART +fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { + tx.blocking_write(b"[").ok(); + unsafe { + for i in 0..len { + write_u32(tx, *buf_ptr.add(i)); + if i < len - 1 { + tx.blocking_write(b", ").ok(); + } + } + } + tx.blocking_write(b"]").ok(); +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // Small delay to allow probe-rs to attach after reset + for _ in 0..100_000 { + cortex_m::asm::nop(); + } + + let mut cfg = hal::config::Config::default(); + cfg.clock_cfg.sirc.fro_12m_enabled = true; + cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); + let p = hal::init(cfg); + + defmt::info!("DMA ping-pong transfer example starting..."); + + // Enable DMA interrupt (DMA clock/reset/init is handled automatically by HAL) + unsafe { + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); + } + + let config = Config { + baudrate_bps: 115_200, + ..Default::default() + }; + + let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); + let (mut tx, _rx) = lpuart.split(); + + tx.blocking_write(b"EDMA ping-pong transfer example begin.\r\n\r\n") + .unwrap(); + + // Initialize buffers + unsafe { + SRC = [1, 2, 3, 4, 5, 6, 7, 8]; + DST = [0; 8]; + } + + tx.blocking_write(b"Source Buffer: ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(SRC) as *const u32, 8); + tx.blocking_write(b"\r\n").unwrap(); + + tx.blocking_write(b"Destination Buffer (before): ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); + tx.blocking_write(b"\r\n").unwrap(); + + tx.blocking_write(b"Configuring ping-pong DMA with Embassy-style API...\r\n") + .unwrap(); + + let dma_ch0 = DmaChannel::new(p.DMA_CH0); + + // Configure ping-pong transfer using direct TCD access: + // This sets up TCD0 and TCD1 in RAM, and loads TCD0 into the channel. + // TCD0 transfers first half (SRC[0..4] -> DST[0..4]), links to TCD1. + // TCD1 transfers second half (SRC[4..8] -> DST[4..8]), links to TCD0. + unsafe { + let tcds = &mut *core::ptr::addr_of_mut!(TCD_POOL.0); + let src_ptr = core::ptr::addr_of!(SRC) as *const u32; + let dst_ptr = core::ptr::addr_of_mut!(DST) as *mut u32; + + let half_len = 4usize; + let half_bytes = (half_len * 4) as u32; + + let tcd0_addr = &tcds[0] as *const _ as u32; + let tcd1_addr = &tcds[1] as *const _ as u32; + + // TCD0: First half -> Links to TCD1 + tcds[0] = Tcd { + saddr: src_ptr as u32, + soff: 4, + attr: 0x0202, // 32-bit src/dst + nbytes: half_bytes, + slast: 0, + daddr: dst_ptr as u32, + doff: 4, + citer: 1, + dlast_sga: tcd1_addr as i32, + csr: 0x0012, // ESG | INTMAJOR + biter: 1, + }; + + // TCD1: Second half -> Links to TCD0 + tcds[1] = Tcd { + saddr: src_ptr.add(half_len) as u32, + soff: 4, + attr: 0x0202, + nbytes: half_bytes, + slast: 0, + daddr: dst_ptr.add(half_len) as u32, + doff: 4, + citer: 1, + dlast_sga: tcd0_addr as i32, + csr: 0x0012, + biter: 1, + }; + + // Load TCD0 into hardware registers + dma_ch0.load_tcd(&tcds[0]); + } + + tx.blocking_write(b"Triggering first half transfer...\r\n").unwrap(); + + // Trigger first transfer (first half: SRC[0..4] -> DST[0..4]) + unsafe { + dma_ch0.trigger_start(); + } + + // Wait for first half + while !TRANSFER_DONE.load(Ordering::Acquire) { + cortex_m::asm::nop(); + } + TRANSFER_DONE.store(false, Ordering::Release); + + tx.blocking_write(b"First half transferred.\r\n").unwrap(); + tx.blocking_write(b"Triggering second half transfer...\r\n").unwrap(); + + // Trigger second transfer (second half: SRC[4..8] -> DST[4..8]) + unsafe { + dma_ch0.trigger_start(); + } + + // Wait for second half + while !TRANSFER_DONE.load(Ordering::Acquire) { + cortex_m::asm::nop(); + } + TRANSFER_DONE.store(false, Ordering::Release); + + tx.blocking_write(b"Second half transferred.\r\n\r\n").unwrap(); + + tx.blocking_write(b"EDMA ping-pong transfer example finish.\r\n\r\n") + .unwrap(); + tx.blocking_write(b"Destination Buffer (after): ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); + tx.blocking_write(b"\r\n\r\n").unwrap(); + + // Verify: DST should match SRC + let mut mismatch = false; + unsafe { + let src_ptr = core::ptr::addr_of!(SRC) as *const u32; + let dst_ptr = core::ptr::addr_of!(DST) as *const u32; + for i in 0..8 { + if *src_ptr.add(i) != *dst_ptr.add(i) { + mismatch = true; + break; + } + } + } + + if mismatch { + tx.blocking_write(b"FAIL: Approach 1 mismatch detected!\r\n").unwrap(); + defmt::error!("FAIL: Approach 1 mismatch detected!"); + } else { + tx.blocking_write(b"PASS: Approach 1 data verified.\r\n\r\n").unwrap(); + defmt::info!("PASS: Approach 1 data verified."); + } + + // ========================================================================= + // Approach 2: Half-Transfer Interrupt with wait_half() (NEW!) + // ========================================================================= + // + // This approach uses a single continuous DMA transfer with half-transfer + // interrupt enabled. The wait_half() method allows you to be notified + // when the first half of the buffer is complete, so you can process it + // while the second half is still being filled. + // + // Benefits: + // - Simpler setup (no TCD pool needed) + // - True async/await support + // - Good for streaming data processing + + tx.blocking_write(b"--- Approach 2: wait_half() demo ---\r\n\r\n") + .unwrap(); + + // Enable DMA CH1 interrupt + unsafe { + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH1); + } + + // Initialize approach 2 buffers + unsafe { + SRC2 = [0xA1, 0xA2, 0xA3, 0xA4, 0xB1, 0xB2, 0xB3, 0xB4]; + DST2 = [0; 8]; + } + + tx.blocking_write(b"SRC2: ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(SRC2) as *const u32, 8); + tx.blocking_write(b"\r\n").unwrap(); + + let dma_ch1 = DmaChannel::new(p.DMA_CH1); + + // Configure transfer with half-transfer interrupt enabled + let mut options = TransferOptions::default(); + options.half_transfer_interrupt = true; // Enable half-transfer interrupt + options.complete_transfer_interrupt = true; + + tx.blocking_write(b"Starting transfer with half_transfer_interrupt...\r\n") + .unwrap(); + + unsafe { + let src = &*core::ptr::addr_of!(SRC2); + let dst = &mut *core::ptr::addr_of_mut!(DST2); + + // Create the transfer + let mut transfer = dma_ch1.mem_to_mem(src, dst, options); + + // Wait for half-transfer (first 4 elements) + tx.blocking_write(b"Waiting for first half...\r\n").unwrap(); + let half_ok = transfer.wait_half().await; + + if half_ok { + tx.blocking_write(b"Half-transfer complete! First half of DST2: ") + .unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DST2) as *const u32, 4); + tx.blocking_write(b"\r\n").unwrap(); + tx.blocking_write(b"(Processing first half while second half transfers...)\r\n") + .unwrap(); + } + + // Wait for complete transfer + tx.blocking_write(b"Waiting for second half...\r\n").unwrap(); + transfer.await; + } + + tx.blocking_write(b"Transfer complete! Full DST2: ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DST2) as *const u32, 8); + tx.blocking_write(b"\r\n\r\n").unwrap(); + + // Verify approach 2 + let mut mismatch2 = false; + unsafe { + let src_ptr = core::ptr::addr_of!(SRC2) as *const u32; + let dst_ptr = core::ptr::addr_of!(DST2) as *const u32; + for i in 0..8 { + if *src_ptr.add(i) != *dst_ptr.add(i) { + mismatch2 = true; + break; + } + } + } + + if mismatch2 { + tx.blocking_write(b"FAIL: Approach 2 mismatch!\r\n").unwrap(); + defmt::error!("FAIL: Approach 2 mismatch!"); + } else { + tx.blocking_write(b"PASS: Approach 2 verified.\r\n").unwrap(); + defmt::info!("PASS: Approach 2 verified."); + } + + tx.blocking_write(b"\r\n=== All ping-pong demos complete ===\r\n") + .unwrap(); + + loop { + cortex_m::asm::wfe(); + } +} diff --git a/examples/mcxa/src/bin/dma_scatter_gather.rs b/examples/mcxa/src/bin/dma_scatter_gather.rs new file mode 100644 index 000000000..4b26bc2ed --- /dev/null +++ b/examples/mcxa/src/bin/dma_scatter_gather.rs @@ -0,0 +1,262 @@ +//! DMA scatter-gather transfer example for MCXA276. +//! +//! This example demonstrates using DMA with scatter/gather to chain multiple +//! transfer descriptors. The first TCD transfers the first half of the buffer, +//! then automatically loads the second TCD to transfer the second half. +//! +//! # Embassy-style features demonstrated: +//! - `DmaChannel::new()` for channel creation +//! - Scatter/gather with chained TCDs +//! - Custom handler that delegates to HAL's `on_interrupt()` (best practice) + +#![no_std] +#![no_main] + +use core::sync::atomic::{AtomicBool, Ordering}; + +use embassy_executor::Spawner; +use embassy_mcxa::clocks::config::Div8; +use embassy_mcxa::dma::{self, DmaChannel, Tcd}; +use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; +use embassy_mcxa::{bind_interrupts, pac}; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +// Source and destination buffers +static mut SRC: [u32; 8] = [1, 2, 3, 4, 5, 6, 7, 8]; +static mut DST: [u32; 8] = [0; 8]; + +// TCD pool for scatter/gather - must be 32-byte aligned +#[repr(C, align(32))] +struct TcdPool([Tcd; 2]); + +static mut TCD_POOL: TcdPool = TcdPool( + [Tcd { + saddr: 0, + soff: 0, + attr: 0, + nbytes: 0, + slast: 0, + daddr: 0, + doff: 0, + citer: 0, + dlast_sga: 0, + csr: 0, + biter: 0, + }; 2], +); + +// AtomicBool to track scatter/gather completion +// Note: With ESG=1, DONE bit is cleared by hardware when next TCD loads, +// so we need this flag to detect when each transfer completes +static TRANSFER_DONE: AtomicBool = AtomicBool::new(false); + +// Custom handler for scatter/gather that delegates to HAL's on_interrupt() +// This follows the "interrupts as threads" pattern - the handler does minimal work +// (delegates to HAL + sets a flag) and the main task does the actual processing +pub struct ScatterGatherDmaHandler; + +impl embassy_mcxa::interrupt::typelevel::Handler + for ScatterGatherDmaHandler +{ + unsafe fn on_interrupt() { + // Delegate to HAL's on_interrupt() which clears INT flag and wakes wakers + dma::on_interrupt(0); + // Signal completion for polling (needed because ESG clears DONE bit) + TRANSFER_DONE.store(true, Ordering::Release); + } +} + +// Bind DMA channel interrupt +// Custom handler for scatter/gather (delegates to on_interrupt + sets flag) +bind_interrupts!(struct Irqs { + DMA_CH0 => ScatterGatherDmaHandler; +}); + +/// Helper to write a u32 as decimal ASCII to UART +fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { + let mut buf = [0u8; 10]; + let mut n = val; + let mut i = buf.len(); + + if n == 0 { + tx.blocking_write(b"0").ok(); + return; + } + + while n > 0 { + i -= 1; + buf[i] = b'0' + (n % 10) as u8; + n /= 10; + } + + tx.blocking_write(&buf[i..]).ok(); +} + +/// Helper to print a buffer to UART +fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { + tx.blocking_write(b"[").ok(); + unsafe { + for i in 0..len { + write_u32(tx, *buf_ptr.add(i)); + if i < len - 1 { + tx.blocking_write(b", ").ok(); + } + } + } + tx.blocking_write(b"]").ok(); +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // Small delay to allow probe-rs to attach after reset + for _ in 0..100_000 { + cortex_m::asm::nop(); + } + + let mut cfg = hal::config::Config::default(); + cfg.clock_cfg.sirc.fro_12m_enabled = true; + cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); + let p = hal::init(cfg); + + defmt::info!("DMA scatter-gather transfer example starting..."); + + // DMA is initialized during hal::init() - no need to call ensure_init() + + // Enable DMA interrupt + unsafe { + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); + } + + let config = Config { + baudrate_bps: 115_200, + ..Default::default() + }; + + let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); + let (mut tx, _rx) = lpuart.split(); + + tx.blocking_write(b"EDMA scatter-gather transfer example begin.\r\n\r\n") + .unwrap(); + + // Initialize buffers + unsafe { + SRC = [1, 2, 3, 4, 5, 6, 7, 8]; + DST = [0; 8]; + } + + tx.blocking_write(b"Source Buffer: ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(SRC) as *const u32, 8); + tx.blocking_write(b"\r\n").unwrap(); + + tx.blocking_write(b"Destination Buffer (before): ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); + tx.blocking_write(b"\r\n").unwrap(); + + tx.blocking_write(b"Configuring scatter-gather DMA with Embassy-style API...\r\n") + .unwrap(); + + let dma_ch0 = DmaChannel::new(p.DMA_CH0); + + // Configure scatter-gather transfer using direct TCD access: + // This sets up TCD0 and TCD1 in RAM, and loads TCD0 into the channel. + // TCD0 transfers first half (SRC[0..4] -> DST[0..4]), then loads TCD1. + // TCD1 transfers second half (SRC[4..8] -> DST[4..8]), last TCD. + unsafe { + let tcds = core::slice::from_raw_parts_mut(core::ptr::addr_of_mut!(TCD_POOL.0) as *mut Tcd, 2); + let src_ptr = core::ptr::addr_of!(SRC) as *const u32; + let dst_ptr = core::ptr::addr_of_mut!(DST) as *mut u32; + + let num_tcds = 2usize; + let chunk_len = 4usize; // 8 / 2 + let chunk_bytes = (chunk_len * 4) as u32; + + for i in 0..num_tcds { + let is_last = i == num_tcds - 1; + let next_tcd_addr = if is_last { + 0 // No next TCD + } else { + &tcds[i + 1] as *const _ as u32 + }; + + tcds[i] = Tcd { + saddr: src_ptr.add(i * chunk_len) as u32, + soff: 4, + attr: 0x0202, // 32-bit src/dst + nbytes: chunk_bytes, + slast: 0, + daddr: dst_ptr.add(i * chunk_len) as u32, + doff: 4, + citer: 1, + dlast_sga: next_tcd_addr as i32, + // ESG (scatter/gather) for non-last, INTMAJOR for all + csr: if is_last { 0x0002 } else { 0x0012 }, + biter: 1, + }; + } + + // Load TCD0 into hardware registers + dma_ch0.load_tcd(&tcds[0]); + } + + tx.blocking_write(b"Triggering first half transfer...\r\n").unwrap(); + + // Trigger first transfer (first half: SRC[0..4] -> DST[0..4]) + // TCD0 is currently loaded. + unsafe { + dma_ch0.trigger_start(); + } + + // Wait for first half + while !TRANSFER_DONE.load(Ordering::Acquire) { + cortex_m::asm::nop(); + } + TRANSFER_DONE.store(false, Ordering::Release); + + tx.blocking_write(b"First half transferred.\r\n").unwrap(); + tx.blocking_write(b"Triggering second half transfer...\r\n").unwrap(); + + // Trigger second transfer (second half: SRC[4..8] -> DST[4..8]) + // TCD1 should have been loaded by the scatter/gather engine. + unsafe { + dma_ch0.trigger_start(); + } + + // Wait for second half + while !TRANSFER_DONE.load(Ordering::Acquire) { + cortex_m::asm::nop(); + } + TRANSFER_DONE.store(false, Ordering::Release); + + tx.blocking_write(b"Second half transferred.\r\n\r\n").unwrap(); + + tx.blocking_write(b"EDMA scatter-gather transfer example finish.\r\n\r\n") + .unwrap(); + tx.blocking_write(b"Destination Buffer (after): ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); + tx.blocking_write(b"\r\n\r\n").unwrap(); + + // Verify: DST should match SRC + let mut mismatch = false; + unsafe { + let src_ptr = core::ptr::addr_of!(SRC) as *const u32; + let dst_ptr = core::ptr::addr_of!(DST) as *const u32; + for i in 0..8 { + if *src_ptr.add(i) != *dst_ptr.add(i) { + mismatch = true; + break; + } + } + } + + if mismatch { + tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); + defmt::error!("FAIL: Mismatch detected!"); + } else { + tx.blocking_write(b"PASS: Data verified.\r\n").unwrap(); + defmt::info!("PASS: Data verified."); + } + + loop { + cortex_m::asm::wfe(); + } +} diff --git a/examples/mcxa/src/bin/dma_scatter_gather_builder.rs b/examples/mcxa/src/bin/dma_scatter_gather_builder.rs new file mode 100644 index 000000000..e483bb81f --- /dev/null +++ b/examples/mcxa/src/bin/dma_scatter_gather_builder.rs @@ -0,0 +1,231 @@ +//! DMA Scatter-Gather Builder example for MCXA276. +//! +//! This example demonstrates using the new `ScatterGatherBuilder` API for +//! chaining multiple DMA transfers with a type-safe builder pattern. +//! +//! # Features demonstrated: +//! - `ScatterGatherBuilder::new()` for creating a builder +//! - `add_transfer()` for adding memory-to-memory segments +//! - `build()` to start the chained transfer +//! - Automatic TCD linking and ESG bit management +//! +//! # Comparison with manual scatter-gather: +//! The manual approach (see `dma_scatter_gather.rs`) requires: +//! - Manual TCD pool allocation and alignment +//! - Manual CSR/ESG/INTMAJOR bit manipulation +//! - Manual dlast_sga address calculations +//! +//! The builder approach handles all of this automatically! + +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_mcxa::clocks::config::Div8; +use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaChannel, ScatterGatherBuilder}; +use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; +use embassy_mcxa::{bind_interrupts, pac}; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +// Bind DMA channel 0 interrupt +bind_interrupts!(struct Irqs { + DMA_CH0 => DmaCh0InterruptHandler; +}); + +// Source buffers (multiple segments) +static mut SRC1: [u32; 4] = [0x11111111, 0x22222222, 0x33333333, 0x44444444]; +static mut SRC2: [u32; 4] = [0xAAAAAAAA, 0xBBBBBBBB, 0xCCCCCCCC, 0xDDDDDDDD]; +static mut SRC3: [u32; 4] = [0x12345678, 0x9ABCDEF0, 0xFEDCBA98, 0x76543210]; + +// Destination buffers (one per segment) +static mut DST1: [u32; 4] = [0; 4]; +static mut DST2: [u32; 4] = [0; 4]; +static mut DST3: [u32; 4] = [0; 4]; + +/// Helper to write a u32 as hex to UART +fn write_hex(tx: &mut LpuartTx<'_, Blocking>, val: u32) { + const HEX: &[u8; 16] = b"0123456789ABCDEF"; + for i in (0..8).rev() { + let nibble = ((val >> (i * 4)) & 0xF) as usize; + tx.blocking_write(&[HEX[nibble]]).ok(); + } +} + +/// Helper to print a buffer to UART +fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { + tx.blocking_write(b"[").ok(); + unsafe { + for i in 0..len { + write_hex(tx, *buf_ptr.add(i)); + if i < len - 1 { + tx.blocking_write(b", ").ok(); + } + } + } + tx.blocking_write(b"]").ok(); +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // Small delay to allow probe-rs to attach after reset + for _ in 0..100_000 { + cortex_m::asm::nop(); + } + + let mut cfg = hal::config::Config::default(); + cfg.clock_cfg.sirc.fro_12m_enabled = true; + cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); + let p = hal::init(cfg); + + defmt::info!("DMA Scatter-Gather Builder example starting..."); + + // Enable DMA interrupt (DMA clock/reset/init is handled automatically by HAL) + unsafe { + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); + } + + // Create UART for debug output + let config = Config { + baudrate_bps: 115_200, + ..Default::default() + }; + + let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); + let (mut tx, _rx) = lpuart.split(); + + tx.blocking_write(b"DMA Scatter-Gather Builder Example\r\n").unwrap(); + tx.blocking_write(b"===================================\r\n\r\n") + .unwrap(); + + // Show source buffers + tx.blocking_write(b"Source buffers:\r\n").unwrap(); + tx.blocking_write(b" SRC1: ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(SRC1) as *const u32, 4); + tx.blocking_write(b"\r\n").unwrap(); + tx.blocking_write(b" SRC2: ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(SRC2) as *const u32, 4); + tx.blocking_write(b"\r\n").unwrap(); + tx.blocking_write(b" SRC3: ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(SRC3) as *const u32, 4); + tx.blocking_write(b"\r\n\r\n").unwrap(); + + tx.blocking_write(b"Destination buffers (before):\r\n").unwrap(); + tx.blocking_write(b" DST1: ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DST1) as *const u32, 4); + tx.blocking_write(b"\r\n").unwrap(); + tx.blocking_write(b" DST2: ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DST2) as *const u32, 4); + tx.blocking_write(b"\r\n").unwrap(); + tx.blocking_write(b" DST3: ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DST3) as *const u32, 4); + tx.blocking_write(b"\r\n\r\n").unwrap(); + + // Create DMA channel + let dma_ch0 = DmaChannel::new(p.DMA_CH0); + + tx.blocking_write(b"Building scatter-gather chain with builder API...\r\n") + .unwrap(); + + // ========================================================================= + // ScatterGatherBuilder API demonstration + // ========================================================================= + // + // The builder pattern makes scatter-gather transfers much easier: + // 1. Create a builder + // 2. Add transfer segments with add_transfer() + // 3. Call build() to start the entire chain + // No manual TCD manipulation required! + + let mut builder = ScatterGatherBuilder::::new(); + + // Add three transfer segments - the builder handles TCD linking automatically + unsafe { + let src1 = &*core::ptr::addr_of!(SRC1); + let dst1 = &mut *core::ptr::addr_of_mut!(DST1); + builder.add_transfer(src1, dst1); + } + + unsafe { + let src2 = &*core::ptr::addr_of!(SRC2); + let dst2 = &mut *core::ptr::addr_of_mut!(DST2); + builder.add_transfer(src2, dst2); + } + + unsafe { + let src3 = &*core::ptr::addr_of!(SRC3); + let dst3 = &mut *core::ptr::addr_of_mut!(DST3); + builder.add_transfer(src3, dst3); + } + + tx.blocking_write(b"Added 3 transfer segments to chain.\r\n").unwrap(); + tx.blocking_write(b"Starting scatter-gather transfer with .await...\r\n\r\n") + .unwrap(); + + // Build and execute the scatter-gather chain + // The build() method: + // - Links all TCDs together with ESG bit + // - Sets INTMAJOR on all TCDs + // - Loads the first TCD into hardware + // - Returns a Transfer future + unsafe { + let transfer = builder.build(&dma_ch0).expect("Failed to build scatter-gather"); + transfer.blocking_wait(); + } + + tx.blocking_write(b"Scatter-gather transfer complete!\r\n\r\n").unwrap(); + + // Show results + tx.blocking_write(b"Destination buffers (after):\r\n").unwrap(); + tx.blocking_write(b" DST1: ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DST1) as *const u32, 4); + tx.blocking_write(b"\r\n").unwrap(); + tx.blocking_write(b" DST2: ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DST2) as *const u32, 4); + tx.blocking_write(b"\r\n").unwrap(); + tx.blocking_write(b" DST3: ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DST3) as *const u32, 4); + tx.blocking_write(b"\r\n\r\n").unwrap(); + + // Verify all three segments + let mut all_ok = true; + unsafe { + let src1 = core::ptr::addr_of!(SRC1) as *const u32; + let dst1 = core::ptr::addr_of!(DST1) as *const u32; + for i in 0..4 { + if *src1.add(i) != *dst1.add(i) { + all_ok = false; + } + } + + let src2 = core::ptr::addr_of!(SRC2) as *const u32; + let dst2 = core::ptr::addr_of!(DST2) as *const u32; + for i in 0..4 { + if *src2.add(i) != *dst2.add(i) { + all_ok = false; + } + } + + let src3 = core::ptr::addr_of!(SRC3) as *const u32; + let dst3 = core::ptr::addr_of!(DST3) as *const u32; + for i in 0..4 { + if *src3.add(i) != *dst3.add(i) { + all_ok = false; + } + } + } + + if all_ok { + tx.blocking_write(b"PASS: All segments verified!\r\n").unwrap(); + defmt::info!("PASS: All segments verified!"); + } else { + tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); + defmt::error!("FAIL: Mismatch detected!"); + } + + tx.blocking_write(b"\r\n=== Scatter-Gather Builder example complete ===\r\n") + .unwrap(); + + loop { + cortex_m::asm::wfe(); + } +} diff --git a/examples/mcxa/src/bin/dma_wrap_transfer.rs b/examples/mcxa/src/bin/dma_wrap_transfer.rs new file mode 100644 index 000000000..82936d9d0 --- /dev/null +++ b/examples/mcxa/src/bin/dma_wrap_transfer.rs @@ -0,0 +1,222 @@ +//! DMA wrap transfer example for MCXA276. +//! +//! This example demonstrates using DMA with modulo addressing to wrap around +//! a source buffer, effectively repeating the source data in the destination. +//! +//! # Embassy-style features demonstrated: +//! - `DmaChannel::is_done()` and `clear_done()` helper methods +//! - No need to pass register block around + +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_mcxa::clocks::config::Div8; +use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaChannel}; +use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; +use embassy_mcxa::{bind_interrupts, pac}; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +// Bind DMA channel 0 interrupt using Embassy-style macro +bind_interrupts!(struct Irqs { + DMA_CH0 => DmaCh0InterruptHandler; +}); + +// Source buffer: 4 words (16 bytes), aligned to 16 bytes for modulo +#[repr(align(16))] +struct AlignedSrc([u32; 4]); + +static mut SRC: AlignedSrc = AlignedSrc([0; 4]); +static mut DST: [u32; 8] = [0; 8]; + +/// Helper to write a u32 as decimal ASCII to UART +fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { + let mut buf = [0u8; 10]; + let mut n = val; + let mut i = buf.len(); + + if n == 0 { + tx.blocking_write(b"0").ok(); + return; + } + + while n > 0 { + i -= 1; + buf[i] = b'0' + (n % 10) as u8; + n /= 10; + } + + tx.blocking_write(&buf[i..]).ok(); +} + +/// Helper to print a buffer to UART +fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { + tx.blocking_write(b"[").ok(); + unsafe { + for i in 0..len { + write_u32(tx, *buf_ptr.add(i)); + if i < len - 1 { + tx.blocking_write(b", ").ok(); + } + } + } + tx.blocking_write(b"]").ok(); +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // Small delay to allow probe-rs to attach after reset + for _ in 0..100_000 { + cortex_m::asm::nop(); + } + + let mut cfg = hal::config::Config::default(); + cfg.clock_cfg.sirc.fro_12m_enabled = true; + cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); + let p = hal::init(cfg); + + defmt::info!("DMA wrap transfer example starting..."); + + // Enable DMA interrupt (DMA clock/reset/init is handled automatically by HAL) + unsafe { + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); + } + + let config = Config { + baudrate_bps: 115_200, + ..Default::default() + }; + + let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); + let (mut tx, _rx) = lpuart.split(); + + tx.blocking_write(b"EDMA wrap transfer example begin.\r\n\r\n").unwrap(); + + // Initialize buffers + unsafe { + SRC.0 = [1, 2, 3, 4]; + DST = [0; 8]; + } + + tx.blocking_write(b"Source Buffer: ").unwrap(); + print_buffer(&mut tx, unsafe { core::ptr::addr_of!(SRC.0) } as *const u32, 4); + tx.blocking_write(b"\r\n").unwrap(); + + tx.blocking_write(b"Destination Buffer (before): ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); + tx.blocking_write(b"\r\n").unwrap(); + + tx.blocking_write(b"Configuring DMA with Embassy-style API...\r\n") + .unwrap(); + + // Create DMA channel using Embassy-style API + let dma_ch0 = DmaChannel::new(p.DMA_CH0); + + // Configure wrap transfer using direct TCD access: + // SRC is 16 bytes (4 * u32). We want to transfer 32 bytes (8 * u32). + // SRC modulo is 16 bytes (2^4 = 16) - wraps source address. + // DST modulo is 0 (disabled). + // This causes the source address to wrap around after 16 bytes, + // effectively repeating the source data. + unsafe { + let t = dma_ch0.tcd(); + + // Reset channel state + t.ch_csr().write(|w| { + w.erq() + .disable() + .earq() + .disable() + .eei() + .no_error() + .ebw() + .disable() + .done() + .clear_bit_by_one() + }); + t.ch_es().write(|w| w.bits(0)); + t.ch_int().write(|w| w.int().clear_bit_by_one()); + + // Source/destination addresses + t.tcd_saddr() + .write(|w| w.saddr().bits(core::ptr::addr_of!(SRC.0) as u32)); + t.tcd_daddr() + .write(|w| w.daddr().bits(core::ptr::addr_of_mut!(DST) as u32)); + + // Offsets: both increment by 4 bytes + t.tcd_soff().write(|w| w.soff().bits(4)); + t.tcd_doff().write(|w| w.doff().bits(4)); + + // Attributes: 32-bit transfers (size = 2) + // SMOD = 4 (2^4 = 16 byte modulo for source), DMOD = 0 (disabled) + t.tcd_attr().write(|w| { + w.ssize() + .bits(2) + .dsize() + .bits(2) + .smod() + .bits(4) // Source modulo: 2^4 = 16 bytes + .dmod() + .bits(0) // Dest modulo: disabled + }); + + // Transfer 32 bytes total in one minor loop + let nbytes = 32u32; + t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(nbytes)); + + // Source wraps via modulo, no adjustment needed + t.tcd_slast_sda().write(|w| w.slast_sda().bits(0)); + // Reset dest address after major loop + t.tcd_dlast_sga().write(|w| w.dlast_sga().bits(-(nbytes as i32) as u32)); + + // Major loop count = 1 + t.tcd_biter_elinkno().write(|w| w.biter().bits(1)); + t.tcd_citer_elinkno().write(|w| w.citer().bits(1)); + + // Enable interrupt on major loop completion + t.tcd_csr().write(|w| w.intmajor().set_bit()); + + cortex_m::asm::dsb(); + + tx.blocking_write(b"Triggering transfer...\r\n").unwrap(); + dma_ch0.trigger_start(); + } + + // Wait for completion using channel helper method + while !dma_ch0.is_done() { + cortex_m::asm::nop(); + } + unsafe { + dma_ch0.clear_done(); + } + + tx.blocking_write(b"\r\nEDMA wrap transfer example finish.\r\n\r\n") + .unwrap(); + tx.blocking_write(b"Destination Buffer (after): ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); + tx.blocking_write(b"\r\n\r\n").unwrap(); + + // Verify: DST should be [1, 2, 3, 4, 1, 2, 3, 4] + let expected = [1u32, 2, 3, 4, 1, 2, 3, 4]; + let mut mismatch = false; + unsafe { + for i in 0..8 { + if DST[i] != expected[i] { + mismatch = true; + break; + } + } + } + + if mismatch { + tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); + defmt::error!("FAIL: Mismatch detected!"); + } else { + tx.blocking_write(b"PASS: Data verified.\r\n").unwrap(); + defmt::info!("PASS: Data verified."); + } + + loop { + cortex_m::asm::wfe(); + } +} diff --git a/examples/mcxa/src/bin/hello.rs b/examples/mcxa/src/bin/hello.rs new file mode 100644 index 000000000..e371d9413 --- /dev/null +++ b/examples/mcxa/src/bin/hello.rs @@ -0,0 +1,119 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_mcxa::clocks::config::Div8; +use hal::lpuart::{Blocking, Config, Lpuart}; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +/// Simple helper to write a byte as hex to UART +fn write_hex_byte(uart: &mut Lpuart<'_, Blocking>, byte: u8) { + const HEX_DIGITS: &[u8] = b"0123456789ABCDEF"; + let _ = uart.write_byte(HEX_DIGITS[(byte >> 4) as usize]); + let _ = uart.write_byte(HEX_DIGITS[(byte & 0xF) as usize]); +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut cfg = hal::config::Config::default(); + cfg.clock_cfg.sirc.fro_12m_enabled = true; + cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); + let p = hal::init(cfg); + + defmt::info!("boot"); + + // Create UART configuration + let config = Config { + baudrate_bps: 115_200, + ..Default::default() + }; + + // Create UART instance using LPUART2 with P2_2 as TX and P2_3 as RX + let mut uart = Lpuart::new_blocking( + p.LPUART2, // Peripheral + p.P2_2, // TX pin + p.P2_3, // RX pin + config, + ) + .unwrap(); + + // Print welcome message before any async delays to guarantee early console output + uart.write_str_blocking("\r\n=== MCXA276 UART Echo Demo ===\r\n"); + uart.write_str_blocking("Available commands:\r\n"); + uart.write_str_blocking(" help - Show this help\r\n"); + uart.write_str_blocking(" echo - Echo back the text\r\n"); + uart.write_str_blocking(" hex - Display byte in hex (0-255)\r\n"); + uart.write_str_blocking("Type a command: "); + + let mut buffer = [0u8; 64]; + let mut buf_idx = 0; + + loop { + // Read a byte from UART + let byte = uart.read_byte_blocking(); + + // Echo the character back + if byte == b'\r' || byte == b'\n' { + // Enter pressed - process command + uart.write_str_blocking("\r\n"); + + if buf_idx > 0 { + let command = &buffer[0..buf_idx]; + + if command == b"help" { + uart.write_str_blocking("Available commands:\r\n"); + uart.write_str_blocking(" help - Show this help\r\n"); + uart.write_str_blocking(" echo - Echo back the text\r\n"); + uart.write_str_blocking(" hex - Display byte in hex (0-255)\r\n"); + } else if command.starts_with(b"echo ") && command.len() > 5 { + uart.write_str_blocking("Echo: "); + uart.write_str_blocking(core::str::from_utf8(&command[5..]).unwrap_or("")); + uart.write_str_blocking("\r\n"); + } else if command.starts_with(b"hex ") && command.len() > 4 { + // Parse the byte value + let num_str = &command[4..]; + if let Ok(num) = parse_u8(num_str) { + uart.write_str_blocking("Hex: 0x"); + write_hex_byte(&mut uart, num); + uart.write_str_blocking("\r\n"); + } else { + uart.write_str_blocking("Invalid number for hex command\r\n"); + } + } else if !command.is_empty() { + uart.write_str_blocking("Unknown command: "); + uart.write_str_blocking(core::str::from_utf8(command).unwrap_or("")); + uart.write_str_blocking("\r\n"); + } + } + + // Reset buffer and prompt + buf_idx = 0; + uart.write_str_blocking("Type a command: "); + } else if byte == 8 || byte == 127 { + // Backspace + if buf_idx > 0 { + buf_idx -= 1; + uart.write_str_blocking("\x08 \x08"); // Erase character + } + } else if buf_idx < buffer.len() - 1 { + // Regular character + buffer[buf_idx] = byte; + buf_idx += 1; + let _ = uart.write_byte(byte); + } + } +} + +/// Simple parser for u8 from ASCII bytes +fn parse_u8(bytes: &[u8]) -> Result { + let mut result = 0u8; + for &b in bytes { + if b.is_ascii_digit() { + result = result.checked_mul(10).ok_or(())?; + result = result.checked_add(b - b'0').ok_or(())?; + } else { + return Err(()); + } + } + Ok(result) +} diff --git a/examples/mcxa/src/bin/i2c-blocking.rs b/examples/mcxa/src/bin/i2c-blocking.rs new file mode 100644 index 000000000..0f6c8cbae --- /dev/null +++ b/examples/mcxa/src/bin/i2c-blocking.rs @@ -0,0 +1,31 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_time::Timer; +use hal::clocks::config::Div8; +use hal::config::Config; +use hal::i2c::controller::{self, I2c, Speed}; +use tmp108::Tmp108; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + config.clock_cfg.sirc.fro_lf_div = Div8::from_divisor(1); + + let p = hal::init(config); + + defmt::info!("I2C example"); + + let mut config = controller::Config::default(); + config.speed = Speed::Standard; + let i2c = I2c::new_blocking(p.LPI2C3, p.P3_27, p.P3_28, config).unwrap(); + let mut tmp = Tmp108::new_with_a0_gnd(i2c); + + loop { + let temperature = tmp.temperature().unwrap(); + defmt::info!("Temperature: {}C", temperature); + Timer::after_secs(1).await; + } +} diff --git a/examples/mcxa/src/bin/i2c-scan-blocking.rs b/examples/mcxa/src/bin/i2c-scan-blocking.rs new file mode 100644 index 000000000..4e203597b --- /dev/null +++ b/examples/mcxa/src/bin/i2c-scan-blocking.rs @@ -0,0 +1,41 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_mcxa::gpio::Pull; +use embassy_mcxa::Input; +use embassy_time::Timer; +use hal::clocks::config::Div8; +use hal::config::Config; +use hal::i2c::controller::{self, I2c, Speed}; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + config.clock_cfg.sirc.fro_lf_div = Div8::from_divisor(1); + + let p = hal::init(config); + + defmt::info!("I2C example"); + + let mut config = controller::Config::default(); + config.speed = Speed::Standard; + + // Note: P0_2 is connected to P1_8 on the FRDM_MCXA276 via a resistor, and + // defaults to SWO on the debug peripheral. Explicitly make it a high-z + // input. + let _pin = Input::new(p.P0_2, Pull::Disabled); + let mut i2c = I2c::new_blocking(p.LPI2C2, p.P1_9, p.P1_8, config).unwrap(); + + for addr in 0x01..=0x7f { + let result = i2c.blocking_write(addr, &[]); + if result.is_ok() { + defmt::info!("Device found at addr {:02x}", addr); + } + } + + loop { + Timer::after_secs(10).await; + } +} diff --git a/examples/mcxa/src/bin/lpuart_buffered.rs b/examples/mcxa/src/bin/lpuart_buffered.rs new file mode 100644 index 000000000..420589d00 --- /dev/null +++ b/examples/mcxa/src/bin/lpuart_buffered.rs @@ -0,0 +1,62 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_mcxa::clocks::config::Div8; +use embassy_mcxa::lpuart::buffered::BufferedLpuart; +use embassy_mcxa::lpuart::Config; +use embassy_mcxa::{bind_interrupts, lpuart}; +use embedded_io_async::Write; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +// Bind OS_EVENT for timers plus LPUART2 IRQ for the buffered driver +bind_interrupts!(struct Irqs { + LPUART2 => lpuart::buffered::BufferedInterruptHandler::; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut cfg = hal::config::Config::default(); + cfg.clock_cfg.sirc.fro_12m_enabled = true; + cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); + let p = hal::init(cfg); + + // Configure NVIC for LPUART2 + hal::interrupt::LPUART2.configure_for_uart(hal::interrupt::Priority::P3); + + // UART configuration (enable both TX and RX) + let config = Config { + baudrate_bps: 115_200, + rx_fifo_watermark: 0, + tx_fifo_watermark: 0, + ..Default::default() + }; + + let mut tx_buf = [0u8; 256]; + let mut rx_buf = [0u8; 256]; + + // Create a buffered LPUART2 instance with both TX and RX + let mut uart = BufferedLpuart::new( + p.LPUART2, + p.P2_2, // TX pin + p.P2_3, // RX pin + Irqs, + &mut tx_buf, + &mut rx_buf, + config, + ) + .unwrap(); + + // Split into TX and RX parts + let (tx, rx) = uart.split_ref(); + + tx.write(b"Hello buffered LPUART.\r\n").await.unwrap(); + tx.write(b"Type characters to echo them back.\r\n").await.unwrap(); + + // Echo loop + let mut buf = [0u8; 4]; + loop { + let used = rx.read(&mut buf).await.unwrap(); + tx.write_all(&buf[..used]).await.unwrap(); + } +} diff --git a/examples/mcxa/src/bin/lpuart_dma.rs b/examples/mcxa/src/bin/lpuart_dma.rs new file mode 100644 index 000000000..5497f8646 --- /dev/null +++ b/examples/mcxa/src/bin/lpuart_dma.rs @@ -0,0 +1,81 @@ +//! LPUART DMA example for MCXA276. +//! +//! This example demonstrates using DMA for UART TX and RX operations. +//! It sends a message using DMA, then waits for 16 characters to be received +//! via DMA and echoes them back. +//! +//! The DMA request sources are automatically derived from the LPUART instance type. +//! DMA clock/reset/init is handled automatically by the HAL. + +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_mcxa::clocks::config::Div8; +use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaCh1InterruptHandler}; +use embassy_mcxa::lpuart::{Config, LpuartDma}; +use embassy_mcxa::{bind_interrupts, pac}; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +// Bind DMA channel interrupts using Embassy-style macro +bind_interrupts!(struct Irqs { + DMA_CH0 => DmaCh0InterruptHandler; + DMA_CH1 => DmaCh1InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut cfg = hal::config::Config::default(); + cfg.clock_cfg.sirc.fro_12m_enabled = true; + cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); + let p = hal::init(cfg); + + defmt::info!("LPUART DMA example starting..."); + + // Enable DMA interrupts (per-channel, as needed) + unsafe { + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH1); + } + + // Create UART configuration + let config = Config { + baudrate_bps: 115_200, + ..Default::default() + }; + + // Create UART instance with DMA channels + let mut lpuart = LpuartDma::new( + p.LPUART2, p.P2_2, // TX pin + p.P2_3, // RX pin + p.DMA_CH0, // TX DMA channel + p.DMA_CH1, // RX DMA channel + config, + ) + .unwrap(); + + // Send a message using DMA (DMA request source is automatically derived from LPUART2) + let tx_msg = b"Hello from LPUART2 DMA TX!\r\n"; + lpuart.write_dma(tx_msg).await.unwrap(); + + defmt::info!("TX DMA complete"); + + // Send prompt + let prompt = b"Type 16 characters to echo via DMA:\r\n"; + lpuart.write_dma(prompt).await.unwrap(); + + // Receive 16 characters using DMA + let mut rx_buf = [0u8; 16]; + lpuart.read_dma(&mut rx_buf).await.unwrap(); + + defmt::info!("RX DMA complete"); + + // Echo back the received data + let echo_prefix = b"\r\nReceived: "; + lpuart.write_dma(echo_prefix).await.unwrap(); + lpuart.write_dma(&rx_buf).await.unwrap(); + let done_msg = b"\r\nDone!\r\n"; + lpuart.write_dma(done_msg).await.unwrap(); + + defmt::info!("Example complete"); +} diff --git a/examples/mcxa/src/bin/lpuart_polling.rs b/examples/mcxa/src/bin/lpuart_polling.rs new file mode 100644 index 000000000..b80668834 --- /dev/null +++ b/examples/mcxa/src/bin/lpuart_polling.rs @@ -0,0 +1,47 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_mcxa::clocks::config::Div8; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +use crate::hal::lpuart::{Config, Lpuart}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut cfg = hal::config::Config::default(); + cfg.clock_cfg.sirc.fro_12m_enabled = true; + cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); + let p = hal::init(cfg); + + defmt::info!("boot"); + + // Create UART configuration + let config = Config { + baudrate_bps: 115_200, + ..Default::default() + }; + + // Create UART instance using LPUART2 with P2_2 as TX and P2_3 as RX + let lpuart = Lpuart::new_blocking( + p.LPUART2, // Peripheral + p.P2_2, // TX pin + p.P2_3, // RX pin + config, + ) + .unwrap(); + + // Split into separate TX and RX parts + let (mut tx, mut rx) = lpuart.split(); + + // Write hello messages + tx.blocking_write(b"Hello world.\r\n").unwrap(); + tx.blocking_write(b"Echoing. Type characters...\r\n").unwrap(); + + // Echo loop + loop { + let mut buf = [0u8; 1]; + rx.blocking_read(&mut buf).unwrap(); + tx.blocking_write(&buf).unwrap(); + } +} diff --git a/examples/mcxa/src/bin/lpuart_ring_buffer.rs b/examples/mcxa/src/bin/lpuart_ring_buffer.rs new file mode 100644 index 000000000..1d1a51970 --- /dev/null +++ b/examples/mcxa/src/bin/lpuart_ring_buffer.rs @@ -0,0 +1,130 @@ +//! LPUART Ring Buffer DMA example for MCXA276. +//! +//! This example demonstrates using the high-level `LpuartRxDma::setup_ring_buffer()` +//! API for continuous circular DMA reception from a UART peripheral. +//! +//! # Features demonstrated: +//! - `LpuartRxDma::setup_ring_buffer()` for continuous peripheral-to-memory DMA +//! - `RingBuffer` for async reading of received data +//! - Handling of potential overrun conditions +//! - Half-transfer and complete-transfer interrupts for timely wakeups +//! +//! # How it works: +//! 1. Create an `LpuartRxDma` driver with a DMA channel +//! 2. Call `setup_ring_buffer()` which handles all low-level DMA configuration +//! 3. Application asynchronously reads data as it arrives via `ring_buf.read()` +//! 4. Both half-transfer and complete-transfer interrupts wake the reader + +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_mcxa::bind_interrupts; +use embassy_mcxa::clocks::config::Div8; +use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaCh1InterruptHandler}; +use embassy_mcxa::lpuart::{Config, LpuartDma, LpuartTxDma}; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +// Bind DMA channel interrupts +bind_interrupts!(struct Irqs { + DMA_CH0 => DmaCh0InterruptHandler; + DMA_CH1 => DmaCh1InterruptHandler; +}); + +// Ring buffer for RX - power of 2 is ideal for modulo efficiency +static mut RX_RING_BUFFER: [u8; 64] = [0; 64]; + +/// Helper to write a byte as hex to UART +fn write_hex( + tx: &mut LpuartTxDma<'_, T, C>, + byte: u8, +) { + const HEX: &[u8; 16] = b"0123456789ABCDEF"; + let buf = [HEX[(byte >> 4) as usize], HEX[(byte & 0x0F) as usize]]; + tx.blocking_write(&buf).ok(); +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // Small delay to allow probe-rs to attach after reset + for _ in 0..100_000 { + cortex_m::asm::nop(); + } + + let mut cfg = hal::config::Config::default(); + cfg.clock_cfg.sirc.fro_12m_enabled = true; + cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); + let p = hal::init(cfg); + + defmt::info!("LPUART Ring Buffer DMA example starting..."); + + // Create UART configuration + let config = Config { + baudrate_bps: 115_200, + ..Default::default() + }; + + // Create LPUART with DMA support for both TX and RX, then split + // This is the proper Embassy pattern - create once, split into TX and RX + let lpuart = LpuartDma::new(p.LPUART2, p.P2_2, p.P2_3, p.DMA_CH1, p.DMA_CH0, config).unwrap(); + let (mut tx, rx) = lpuart.split(); + + tx.blocking_write(b"LPUART Ring Buffer DMA Example\r\n").unwrap(); + tx.blocking_write(b"==============================\r\n\r\n").unwrap(); + + tx.blocking_write(b"Setting up circular DMA for UART RX...\r\n") + .unwrap(); + + // Set up the ring buffer with circular DMA + // The HAL handles: DMA request source, RDMAE enable, circular transfer config, NVIC enable + let ring_buf = unsafe { + let buf = &mut *core::ptr::addr_of_mut!(RX_RING_BUFFER); + rx.setup_ring_buffer(buf) + }; + + // Enable DMA requests to start continuous reception + unsafe { + rx.enable_dma_request(); + } + + tx.blocking_write(b"Ring buffer ready! Type characters to see them echoed.\r\n") + .unwrap(); + tx.blocking_write(b"The DMA continuously receives in the background.\r\n\r\n") + .unwrap(); + + // Main loop: read from ring buffer and echo back + let mut read_buf = [0u8; 16]; + let mut total_received: usize = 0; + + loop { + // Async read - waits until data is available + match ring_buf.read(&mut read_buf).await { + Ok(n) if n > 0 => { + total_received += n; + + // Echo back what we received + tx.blocking_write(b"RX[").unwrap(); + for (i, &byte) in read_buf.iter().enumerate().take(n) { + write_hex(&mut tx, byte); + if i < n - 1 { + tx.blocking_write(b" ").unwrap(); + } + } + tx.blocking_write(b"]: ").unwrap(); + tx.blocking_write(&read_buf[..n]).unwrap(); + tx.blocking_write(b"\r\n").unwrap(); + + defmt::info!("Received {} bytes, total: {}", n, total_received); + } + Ok(_) => { + // No data, shouldn't happen with async read + } + Err(_) => { + // Overrun detected + tx.blocking_write(b"ERROR: Ring buffer overrun!\r\n").unwrap(); + defmt::error!("Ring buffer overrun!"); + ring_buf.clear(); + } + } + } +} diff --git a/examples/mcxa/src/bin/rtc_alarm.rs b/examples/mcxa/src/bin/rtc_alarm.rs new file mode 100644 index 000000000..a7800a2d1 --- /dev/null +++ b/examples/mcxa/src/bin/rtc_alarm.rs @@ -0,0 +1,74 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_mcxa as hal; +use hal::rtc::{RtcDateTime, RtcInterruptEnable}; +use hal::InterruptExt; + +type MyRtc = hal::rtc::Rtc<'static, hal::rtc::Rtc0>; + +use embassy_mcxa::bind_interrupts; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + RTC => hal::rtc::RtcHandler; +}); + +#[used] +#[no_mangle] +static KEEP_RTC: unsafe extern "C" fn() = RTC; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = hal::init(hal::config::Config::default()); + + defmt::info!("=== RTC Alarm Example ==="); + + let rtc_config = hal::rtc::get_default_config(); + + let rtc = MyRtc::new(p.RTC0, rtc_config); + + let now = RtcDateTime { + year: 2025, + month: 10, + day: 15, + hour: 14, + minute: 30, + second: 0, + }; + + rtc.stop(); + + defmt::info!("Time set to: 2025-10-15 14:30:00"); + rtc.set_datetime(now); + + let mut alarm = now; + alarm.second += 10; + + rtc.set_alarm(alarm); + defmt::info!("Alarm set for: 2025-10-15 14:30:10 (+10 seconds)"); + + rtc.set_interrupt(RtcInterruptEnable::RTC_ALARM_INTERRUPT_ENABLE); + + unsafe { + hal::interrupt::RTC.enable(); + } + + unsafe { + cortex_m::interrupt::enable(); + } + + rtc.start(); + + defmt::info!("RTC started, waiting for alarm..."); + + loop { + if rtc.is_alarm_triggered() { + defmt::info!("*** ALARM TRIGGERED! ***"); + break; + } + } + + defmt::info!("Example complete - Test PASSED!"); +} diff --git a/examples/mcxa/src/lib.rs b/examples/mcxa/src/lib.rs new file mode 100644 index 000000000..2573a6adc --- /dev/null +++ b/examples/mcxa/src/lib.rs @@ -0,0 +1,16 @@ +#![no_std] +#![allow(clippy::missing_safety_doc)] + +//! Shared board-specific helpers for the FRDM-MCXA276 examples. +//! These live with the examples so the HAL stays generic. + +use hal::{clocks, pins}; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +/// Initialize clocks and pin muxing for ADC. +pub unsafe fn init_adc_pins() { + // NOTE: Lpuart has been updated to properly enable + reset its own clocks. + // GPIO has not. + _ = clocks::enable_and_reset::(&clocks::periph_helpers::NoConfig); + pins::configure_adc_pins(); +} -- cgit