diff options
| author | James Munns <[email protected]> | 2025-12-05 14:28:47 +0100 |
|---|---|---|
| committer | James Munns <[email protected]> | 2025-12-05 14:28:47 +0100 |
| commit | b252db845e19603faf528cf93fe0c44757a27430 (patch) | |
| tree | 99e646d17bed747df244dd607a15f5a67baa530a /examples/mcxa/src | |
| parent | 6a1eed83b9df8ffa81b93860f530f5bb3252d996 (diff) | |
Move
Diffstat (limited to 'examples/mcxa/src')
23 files changed, 3035 insertions, 0 deletions
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 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | use embassy_executor::Spawner; | ||
| 5 | use embassy_mcxa_examples::init_adc_pins; | ||
| 6 | use hal::adc::{LpadcConfig, TriggerPriorityPolicy}; | ||
| 7 | use hal::clocks::periph_helpers::{AdcClockSel, Div4}; | ||
| 8 | use hal::clocks::PoweredClock; | ||
| 9 | use hal::pac::adc1::cfg::{Pwrsel, Refsel}; | ||
| 10 | use hal::pac::adc1::cmdl1::{Adch, Mode}; | ||
| 11 | use hal::pac::adc1::ctrl::CalAvgs; | ||
| 12 | use hal::pac::adc1::tctrl::Tcmd; | ||
| 13 | use hal::{bind_interrupts, InterruptExt}; | ||
| 14 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 15 | |||
| 16 | bind_interrupts!(struct Irqs { | ||
| 17 | ADC1 => hal::adc::AdcHandler; | ||
| 18 | }); | ||
| 19 | |||
| 20 | #[used] | ||
| 21 | #[no_mangle] | ||
| 22 | static KEEP_ADC: unsafe extern "C" fn() = ADC1; | ||
| 23 | |||
| 24 | #[embassy_executor::main] | ||
| 25 | async fn main(_spawner: Spawner) { | ||
| 26 | let p = hal::init(hal::config::Config::default()); | ||
| 27 | |||
| 28 | defmt::info!("ADC interrupt Example"); | ||
| 29 | |||
| 30 | unsafe { | ||
| 31 | init_adc_pins(); | ||
| 32 | } | ||
| 33 | |||
| 34 | let adc_config = LpadcConfig { | ||
| 35 | enable_in_doze_mode: true, | ||
| 36 | conversion_average_mode: CalAvgs::Average128, | ||
| 37 | enable_analog_preliminary: true, | ||
| 38 | power_up_delay: 0x80, | ||
| 39 | reference_voltage_source: Refsel::Option3, | ||
| 40 | power_level_mode: Pwrsel::Lowest, | ||
| 41 | trigger_priority_policy: TriggerPriorityPolicy::ConvPreemptImmediatelyNotAutoResumed, | ||
| 42 | enable_conv_pause: false, | ||
| 43 | conv_pause_delay: 0, | ||
| 44 | fifo_watermark: 0, | ||
| 45 | power: PoweredClock::NormalEnabledDeepSleepDisabled, | ||
| 46 | source: AdcClockSel::FroLfDiv, | ||
| 47 | div: Div4::no_div(), | ||
| 48 | }; | ||
| 49 | let adc = hal::adc::Adc::<hal::adc::Adc1>::new(p.ADC1, adc_config); | ||
| 50 | |||
| 51 | adc.do_offset_calibration(); | ||
| 52 | adc.do_auto_calibration(); | ||
| 53 | |||
| 54 | let mut conv_command_config = adc.get_default_conv_command_config(); | ||
| 55 | conv_command_config.channel_number = Adch::SelectCorrespondingChannel8; | ||
| 56 | conv_command_config.conversion_resolution_mode = Mode::Data16Bits; | ||
| 57 | adc.set_conv_command_config(1, &conv_command_config); | ||
| 58 | |||
| 59 | let mut conv_trigger_config = adc.get_default_conv_trigger_config(); | ||
| 60 | conv_trigger_config.target_command_id = Tcmd::ExecuteCmd1; | ||
| 61 | conv_trigger_config.enable_hardware_trigger = false; | ||
| 62 | adc.set_conv_trigger_config(0, &conv_trigger_config); | ||
| 63 | |||
| 64 | defmt::info!("ADC configuration done..."); | ||
| 65 | |||
| 66 | adc.enable_interrupt(0x1); | ||
| 67 | |||
| 68 | unsafe { | ||
| 69 | hal::interrupt::ADC1.enable(); | ||
| 70 | } | ||
| 71 | |||
| 72 | unsafe { | ||
| 73 | cortex_m::interrupt::enable(); | ||
| 74 | } | ||
| 75 | |||
| 76 | loop { | ||
| 77 | adc.do_software_trigger(1); | ||
| 78 | while !adc.is_interrupt_triggered() { | ||
| 79 | // Wait until the interrupt is triggered | ||
| 80 | } | ||
| 81 | defmt::info!("*** ADC interrupt TRIGGERED! ***"); | ||
| 82 | //TBD need to print the value | ||
| 83 | } | ||
| 84 | } | ||
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 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | use embassy_executor::Spawner; | ||
| 5 | use embassy_mcxa_examples::init_adc_pins; | ||
| 6 | use hal::adc::{ConvResult, LpadcConfig, TriggerPriorityPolicy}; | ||
| 7 | use hal::clocks::periph_helpers::{AdcClockSel, Div4}; | ||
| 8 | use hal::clocks::PoweredClock; | ||
| 9 | use hal::pac::adc1::cfg::{Pwrsel, Refsel}; | ||
| 10 | use hal::pac::adc1::cmdl1::{Adch, Mode}; | ||
| 11 | use hal::pac::adc1::ctrl::CalAvgs; | ||
| 12 | use hal::pac::adc1::tctrl::Tcmd; | ||
| 13 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 14 | |||
| 15 | const G_LPADC_RESULT_SHIFT: u32 = 0; | ||
| 16 | |||
| 17 | #[embassy_executor::main] | ||
| 18 | async fn main(_spawner: Spawner) { | ||
| 19 | let p = hal::init(hal::config::Config::default()); | ||
| 20 | |||
| 21 | unsafe { | ||
| 22 | init_adc_pins(); | ||
| 23 | } | ||
| 24 | |||
| 25 | defmt::info!("=== ADC polling Example ==="); | ||
| 26 | |||
| 27 | let adc_config = LpadcConfig { | ||
| 28 | enable_in_doze_mode: true, | ||
| 29 | conversion_average_mode: CalAvgs::Average128, | ||
| 30 | enable_analog_preliminary: true, | ||
| 31 | power_up_delay: 0x80, | ||
| 32 | reference_voltage_source: Refsel::Option3, | ||
| 33 | power_level_mode: Pwrsel::Lowest, | ||
| 34 | trigger_priority_policy: TriggerPriorityPolicy::ConvPreemptImmediatelyNotAutoResumed, | ||
| 35 | enable_conv_pause: false, | ||
| 36 | conv_pause_delay: 0, | ||
| 37 | fifo_watermark: 0, | ||
| 38 | power: PoweredClock::NormalEnabledDeepSleepDisabled, | ||
| 39 | source: AdcClockSel::FroLfDiv, | ||
| 40 | div: Div4::no_div(), | ||
| 41 | }; | ||
| 42 | let adc = hal::adc::Adc::<hal::adc::Adc1>::new(p.ADC1, adc_config); | ||
| 43 | |||
| 44 | adc.do_offset_calibration(); | ||
| 45 | adc.do_auto_calibration(); | ||
| 46 | |||
| 47 | let mut conv_command_config = adc.get_default_conv_command_config(); | ||
| 48 | conv_command_config.channel_number = Adch::SelectCorrespondingChannel8; | ||
| 49 | conv_command_config.conversion_resolution_mode = Mode::Data16Bits; | ||
| 50 | adc.set_conv_command_config(1, &conv_command_config); | ||
| 51 | |||
| 52 | let mut conv_trigger_config = adc.get_default_conv_trigger_config(); | ||
| 53 | conv_trigger_config.target_command_id = Tcmd::ExecuteCmd1; | ||
| 54 | conv_trigger_config.enable_hardware_trigger = false; | ||
| 55 | adc.set_conv_trigger_config(0, &conv_trigger_config); | ||
| 56 | |||
| 57 | defmt::info!("=== ADC configuration done... ==="); | ||
| 58 | |||
| 59 | loop { | ||
| 60 | adc.do_software_trigger(1); | ||
| 61 | let mut result: Option<ConvResult> = None; | ||
| 62 | while result.is_none() { | ||
| 63 | result = hal::adc::get_conv_result(); | ||
| 64 | } | ||
| 65 | let value = result.unwrap().conv_value >> G_LPADC_RESULT_SHIFT; | ||
| 66 | defmt::info!("value: {=u16}", value); | ||
| 67 | } | ||
| 68 | } | ||
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 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | use embassy_executor::Spawner; | ||
| 5 | use embassy_time::Timer; | ||
| 6 | use hal::gpio::{DriveStrength, Level, Output, SlewRate}; | ||
| 7 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 8 | |||
| 9 | #[embassy_executor::main] | ||
| 10 | async fn main(_spawner: Spawner) { | ||
| 11 | let p = hal::init(hal::config::Config::default()); | ||
| 12 | |||
| 13 | defmt::info!("Blink example"); | ||
| 14 | |||
| 15 | let mut red = Output::new(p.P3_18, Level::High, DriveStrength::Normal, SlewRate::Fast); | ||
| 16 | let mut green = Output::new(p.P3_19, Level::High, DriveStrength::Normal, SlewRate::Fast); | ||
| 17 | let mut blue = Output::new(p.P3_21, Level::High, DriveStrength::Normal, SlewRate::Fast); | ||
| 18 | |||
| 19 | loop { | ||
| 20 | defmt::info!("Toggle LEDs"); | ||
| 21 | |||
| 22 | red.toggle(); | ||
| 23 | Timer::after_millis(250).await; | ||
| 24 | |||
| 25 | red.toggle(); | ||
| 26 | green.toggle(); | ||
| 27 | Timer::after_millis(250).await; | ||
| 28 | |||
| 29 | green.toggle(); | ||
| 30 | blue.toggle(); | ||
| 31 | Timer::after_millis(250).await; | ||
| 32 | blue.toggle(); | ||
| 33 | |||
| 34 | Timer::after_millis(250).await; | ||
| 35 | } | ||
| 36 | } | ||
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 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | use embassy_executor::Spawner; | ||
| 5 | use embassy_time::Timer; | ||
| 6 | use hal::gpio::{Input, Pull}; | ||
| 7 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 8 | |||
| 9 | #[embassy_executor::main] | ||
| 10 | async fn main(_spawner: Spawner) { | ||
| 11 | let p = hal::init(hal::config::Config::default()); | ||
| 12 | |||
| 13 | defmt::info!("Button example"); | ||
| 14 | |||
| 15 | // This button is labeled "WAKEUP" on the FRDM-MCXA276 | ||
| 16 | // The board already has a 10K pullup | ||
| 17 | let monitor = Input::new(p.P1_7, Pull::Disabled); | ||
| 18 | |||
| 19 | loop { | ||
| 20 | defmt::info!("Pin level is {:?}", monitor.get_level()); | ||
| 21 | Timer::after_millis(1000).await; | ||
| 22 | } | ||
| 23 | } | ||
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 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | use embassy_executor::Spawner; | ||
| 5 | use embassy_time::Timer; | ||
| 6 | use hal::gpio::{Input, Pull}; | ||
| 7 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 8 | |||
| 9 | #[embassy_executor::main] | ||
| 10 | async fn main(_spawner: Spawner) { | ||
| 11 | let p = hal::init(hal::config::Config::default()); | ||
| 12 | |||
| 13 | defmt::info!("GPIO interrupt example"); | ||
| 14 | |||
| 15 | // This button is labeled "WAKEUP" on the FRDM-MCXA276 | ||
| 16 | // The board already has a 10K pullup | ||
| 17 | let mut pin = Input::new(p.P1_7, Pull::Disabled); | ||
| 18 | |||
| 19 | let mut press_count = 0u32; | ||
| 20 | |||
| 21 | loop { | ||
| 22 | pin.wait_for_falling_edge().await; | ||
| 23 | |||
| 24 | press_count += 1; | ||
| 25 | |||
| 26 | defmt::info!("Button pressed! Count: {}", press_count); | ||
| 27 | Timer::after_millis(50).await; | ||
| 28 | } | ||
| 29 | } | ||
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 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | use embassy_executor::Spawner; | ||
| 5 | use embassy_mcxa::clkout::{ClockOut, ClockOutSel, Config, Div4}; | ||
| 6 | use embassy_mcxa::clocks::PoweredClock; | ||
| 7 | use embassy_mcxa::gpio::{DriveStrength, SlewRate}; | ||
| 8 | use embassy_mcxa::{Level, Output}; | ||
| 9 | use embassy_time::Timer; | ||
| 10 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 11 | |||
| 12 | /// Demonstrate CLKOUT, using Pin P4.2 | ||
| 13 | #[embassy_executor::main] | ||
| 14 | async fn main(_spawner: Spawner) { | ||
| 15 | let p = hal::init(hal::config::Config::default()); | ||
| 16 | let mut pin = p.P4_2; | ||
| 17 | let mut clkout = p.CLKOUT; | ||
| 18 | |||
| 19 | loop { | ||
| 20 | defmt::info!("Set Low..."); | ||
| 21 | let mut output = Output::new(pin.reborrow(), Level::Low, DriveStrength::Normal, SlewRate::Slow); | ||
| 22 | Timer::after_millis(500).await; | ||
| 23 | |||
| 24 | defmt::info!("Set High..."); | ||
| 25 | output.set_high(); | ||
| 26 | Timer::after_millis(400).await; | ||
| 27 | |||
| 28 | defmt::info!("Set Low..."); | ||
| 29 | output.set_low(); | ||
| 30 | Timer::after_millis(500).await; | ||
| 31 | |||
| 32 | defmt::info!("16k..."); | ||
| 33 | // Run Clock Out with the 16K clock | ||
| 34 | let _clock_out = ClockOut::new( | ||
| 35 | clkout.reborrow(), | ||
| 36 | pin.reborrow(), | ||
| 37 | Config { | ||
| 38 | sel: ClockOutSel::Clk16K, | ||
| 39 | div: Div4::no_div(), | ||
| 40 | level: PoweredClock::NormalEnabledDeepSleepDisabled, | ||
| 41 | }, | ||
| 42 | ) | ||
| 43 | .unwrap(); | ||
| 44 | |||
| 45 | Timer::after_millis(3000).await; | ||
| 46 | |||
| 47 | defmt::info!("Set Low..."); | ||
| 48 | drop(_clock_out); | ||
| 49 | |||
| 50 | let _output = Output::new(pin.reborrow(), Level::Low, DriveStrength::Normal, SlewRate::Slow); | ||
| 51 | Timer::after_millis(500).await; | ||
| 52 | |||
| 53 | // Run Clock Out with the 12M clock, divided by 3 | ||
| 54 | defmt::info!("4M..."); | ||
| 55 | let _clock_out = ClockOut::new( | ||
| 56 | clkout.reborrow(), | ||
| 57 | pin.reborrow(), | ||
| 58 | Config { | ||
| 59 | sel: ClockOutSel::Fro12M, | ||
| 60 | div: const { Div4::from_divisor(3).unwrap() }, | ||
| 61 | level: PoweredClock::NormalEnabledDeepSleepDisabled, | ||
| 62 | }, | ||
| 63 | ) | ||
| 64 | .unwrap(); | ||
| 65 | |||
| 66 | // Let it run for 3 seconds... | ||
| 67 | Timer::after_millis(3000).await; | ||
| 68 | } | ||
| 69 | } | ||
diff --git a/examples/mcxa/src/bin/dma_channel_link.rs b/examples/mcxa/src/bin/dma_channel_link.rs new file mode 100644 index 000000000..92c7a9681 --- /dev/null +++ b/examples/mcxa/src/bin/dma_channel_link.rs | |||
| @@ -0,0 +1,372 @@ | |||
| 1 | //! DMA channel linking example for MCXA276. | ||
| 2 | //! | ||
| 3 | //! This example demonstrates DMA channel linking (minor and major loop linking): | ||
| 4 | //! - Channel 0: Transfers SRC_BUFFER to DEST_BUFFER0, with: | ||
| 5 | //! - Minor Link to Channel 1 (triggers CH1 after each minor loop) | ||
| 6 | //! - Major Link to Channel 2 (triggers CH2 after major loop completes) | ||
| 7 | //! - Channel 1: Transfers SRC_BUFFER to DEST_BUFFER1 (triggered by CH0 minor link) | ||
| 8 | //! - Channel 2: Transfers SRC_BUFFER to DEST_BUFFER2 (triggered by CH0 major link) | ||
| 9 | //! | ||
| 10 | //! # Embassy-style features demonstrated: | ||
| 11 | //! - `DmaChannel::new()` for channel creation | ||
| 12 | //! - `DmaChannel::is_done()` and `clear_done()` helper methods | ||
| 13 | //! - Channel linking with `set_minor_link()` and `set_major_link()` | ||
| 14 | //! - Standard `DmaCh*InterruptHandler` with `bind_interrupts!` macro | ||
| 15 | |||
| 16 | #![no_std] | ||
| 17 | #![no_main] | ||
| 18 | |||
| 19 | use embassy_executor::Spawner; | ||
| 20 | use embassy_mcxa::clocks::config::Div8; | ||
| 21 | use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaCh1InterruptHandler, DmaCh2InterruptHandler, DmaChannel}; | ||
| 22 | use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; | ||
| 23 | use embassy_mcxa::{bind_interrupts, pac}; | ||
| 24 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 25 | |||
| 26 | // Buffers | ||
| 27 | static mut SRC_BUFFER: [u32; 4] = [1, 2, 3, 4]; | ||
| 28 | static mut DEST_BUFFER0: [u32; 4] = [0; 4]; | ||
| 29 | static mut DEST_BUFFER1: [u32; 4] = [0; 4]; | ||
| 30 | static mut DEST_BUFFER2: [u32; 4] = [0; 4]; | ||
| 31 | |||
| 32 | // Bind DMA channel interrupts using Embassy-style macro | ||
| 33 | // The standard handlers call on_interrupt() which wakes wakers and clears flags | ||
| 34 | bind_interrupts!(struct Irqs { | ||
| 35 | DMA_CH0 => DmaCh0InterruptHandler; | ||
| 36 | DMA_CH1 => DmaCh1InterruptHandler; | ||
| 37 | DMA_CH2 => DmaCh2InterruptHandler; | ||
| 38 | }); | ||
| 39 | |||
| 40 | /// Helper to write a u32 as decimal ASCII to UART | ||
| 41 | fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { | ||
| 42 | let mut buf = [0u8; 10]; | ||
| 43 | let mut n = val; | ||
| 44 | let mut i = buf.len(); | ||
| 45 | |||
| 46 | if n == 0 { | ||
| 47 | tx.blocking_write(b"0").ok(); | ||
| 48 | return; | ||
| 49 | } | ||
| 50 | |||
| 51 | while n > 0 { | ||
| 52 | i -= 1; | ||
| 53 | buf[i] = b'0' + (n % 10) as u8; | ||
| 54 | n /= 10; | ||
| 55 | } | ||
| 56 | |||
| 57 | tx.blocking_write(&buf[i..]).ok(); | ||
| 58 | } | ||
| 59 | |||
| 60 | /// Helper to print a buffer to UART | ||
| 61 | fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { | ||
| 62 | tx.blocking_write(b"[").ok(); | ||
| 63 | unsafe { | ||
| 64 | for i in 0..len { | ||
| 65 | write_u32(tx, *buf_ptr.add(i)); | ||
| 66 | if i < len - 1 { | ||
| 67 | tx.blocking_write(b", ").ok(); | ||
| 68 | } | ||
| 69 | } | ||
| 70 | } | ||
| 71 | tx.blocking_write(b"]").ok(); | ||
| 72 | } | ||
| 73 | |||
| 74 | #[embassy_executor::main] | ||
| 75 | async fn main(_spawner: Spawner) { | ||
| 76 | // Small delay to allow probe-rs to attach after reset | ||
| 77 | for _ in 0..100_000 { | ||
| 78 | cortex_m::asm::nop(); | ||
| 79 | } | ||
| 80 | |||
| 81 | let mut cfg = hal::config::Config::default(); | ||
| 82 | cfg.clock_cfg.sirc.fro_12m_enabled = true; | ||
| 83 | cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); | ||
| 84 | let p = hal::init(cfg); | ||
| 85 | |||
| 86 | defmt::info!("DMA channel link example starting..."); | ||
| 87 | |||
| 88 | // DMA is initialized during hal::init() - no need to call ensure_init() | ||
| 89 | |||
| 90 | let pac_periphs = unsafe { pac::Peripherals::steal() }; | ||
| 91 | let dma0 = &pac_periphs.dma0; | ||
| 92 | let edma = unsafe { &*pac::Edma0Tcd0::ptr() }; | ||
| 93 | |||
| 94 | // Clear any residual state | ||
| 95 | for i in 0..3 { | ||
| 96 | let t = edma.tcd(i); | ||
| 97 | t.ch_csr().write(|w| w.erq().disable().done().clear_bit_by_one()); | ||
| 98 | t.ch_int().write(|w| w.int().clear_bit_by_one()); | ||
| 99 | t.ch_es().write(|w| w.err().clear_bit_by_one()); | ||
| 100 | t.ch_mux().write(|w| unsafe { w.bits(0) }); | ||
| 101 | } | ||
| 102 | |||
| 103 | // Clear Global Halt/Error state | ||
| 104 | dma0.mp_csr().modify(|_, w| { | ||
| 105 | w.halt() | ||
| 106 | .normal_operation() | ||
| 107 | .hae() | ||
| 108 | .normal_operation() | ||
| 109 | .ecx() | ||
| 110 | .normal_operation() | ||
| 111 | .cx() | ||
| 112 | .normal_operation() | ||
| 113 | }); | ||
| 114 | |||
| 115 | unsafe { | ||
| 116 | cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); | ||
| 117 | cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH1); | ||
| 118 | cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH2); | ||
| 119 | } | ||
| 120 | |||
| 121 | let config = Config { | ||
| 122 | baudrate_bps: 115_200, | ||
| 123 | ..Default::default() | ||
| 124 | }; | ||
| 125 | |||
| 126 | let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); | ||
| 127 | let (mut tx, _rx) = lpuart.split(); | ||
| 128 | |||
| 129 | tx.blocking_write(b"EDMA channel link example begin.\r\n\r\n").unwrap(); | ||
| 130 | |||
| 131 | // Initialize buffers | ||
| 132 | unsafe { | ||
| 133 | SRC_BUFFER = [1, 2, 3, 4]; | ||
| 134 | DEST_BUFFER0 = [0; 4]; | ||
| 135 | DEST_BUFFER1 = [0; 4]; | ||
| 136 | DEST_BUFFER2 = [0; 4]; | ||
| 137 | } | ||
| 138 | |||
| 139 | tx.blocking_write(b"Source Buffer: ").unwrap(); | ||
| 140 | print_buffer(&mut tx, core::ptr::addr_of!(SRC_BUFFER) as *const u32, 4); | ||
| 141 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 142 | |||
| 143 | tx.blocking_write(b"DEST0 (before): ").unwrap(); | ||
| 144 | print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER0) as *const u32, 4); | ||
| 145 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 146 | |||
| 147 | tx.blocking_write(b"DEST1 (before): ").unwrap(); | ||
| 148 | print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER1) as *const u32, 4); | ||
| 149 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 150 | |||
| 151 | tx.blocking_write(b"DEST2 (before): ").unwrap(); | ||
| 152 | print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER2) as *const u32, 4); | ||
| 153 | tx.blocking_write(b"\r\n\r\n").unwrap(); | ||
| 154 | |||
| 155 | tx.blocking_write(b"Configuring DMA channels with Embassy-style API...\r\n") | ||
| 156 | .unwrap(); | ||
| 157 | |||
| 158 | let ch0 = DmaChannel::new(p.DMA_CH0); | ||
| 159 | let ch1 = DmaChannel::new(p.DMA_CH1); | ||
| 160 | let ch2 = DmaChannel::new(p.DMA_CH2); | ||
| 161 | |||
| 162 | // Configure channels using direct TCD access (advanced feature demo) | ||
| 163 | // This example demonstrates channel linking which requires direct TCD manipulation | ||
| 164 | |||
| 165 | // Helper to configure TCD for memory-to-memory transfer | ||
| 166 | // Parameters: channel, src, dst, width, nbytes (minor loop), count (major loop), interrupt | ||
| 167 | #[allow(clippy::too_many_arguments)] | ||
| 168 | unsafe fn configure_tcd( | ||
| 169 | edma: &embassy_mcxa::pac::edma_0_tcd0::RegisterBlock, | ||
| 170 | ch: usize, | ||
| 171 | src: u32, | ||
| 172 | dst: u32, | ||
| 173 | width: u8, | ||
| 174 | nbytes: u32, | ||
| 175 | count: u16, | ||
| 176 | enable_int: bool, | ||
| 177 | ) { | ||
| 178 | let t = edma.tcd(ch); | ||
| 179 | |||
| 180 | // Reset channel state | ||
| 181 | t.ch_csr().write(|w| { | ||
| 182 | w.erq() | ||
| 183 | .disable() | ||
| 184 | .earq() | ||
| 185 | .disable() | ||
| 186 | .eei() | ||
| 187 | .no_error() | ||
| 188 | .ebw() | ||
| 189 | .disable() | ||
| 190 | .done() | ||
| 191 | .clear_bit_by_one() | ||
| 192 | }); | ||
| 193 | t.ch_es().write(|w| w.bits(0)); | ||
| 194 | t.ch_int().write(|w| w.int().clear_bit_by_one()); | ||
| 195 | |||
| 196 | // Source/destination addresses | ||
| 197 | t.tcd_saddr().write(|w| w.saddr().bits(src)); | ||
| 198 | t.tcd_daddr().write(|w| w.daddr().bits(dst)); | ||
| 199 | |||
| 200 | // Offsets: increment by width | ||
| 201 | t.tcd_soff().write(|w| w.soff().bits(width as u16)); | ||
| 202 | t.tcd_doff().write(|w| w.doff().bits(width as u16)); | ||
| 203 | |||
| 204 | // Attributes: size = log2(width) | ||
| 205 | let size = match width { | ||
| 206 | 1 => 0, | ||
| 207 | 2 => 1, | ||
| 208 | 4 => 2, | ||
| 209 | _ => 0, | ||
| 210 | }; | ||
| 211 | t.tcd_attr().write(|w| w.ssize().bits(size).dsize().bits(size)); | ||
| 212 | |||
| 213 | // Number of bytes per minor loop | ||
| 214 | t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(nbytes)); | ||
| 215 | |||
| 216 | // Major loop: reset source address after major loop | ||
| 217 | let total_bytes = nbytes * count as u32; | ||
| 218 | t.tcd_slast_sda() | ||
| 219 | .write(|w| w.slast_sda().bits(-(total_bytes as i32) as u32)); | ||
| 220 | t.tcd_dlast_sga() | ||
| 221 | .write(|w| w.dlast_sga().bits(-(total_bytes as i32) as u32)); | ||
| 222 | |||
| 223 | // Major loop count | ||
| 224 | t.tcd_biter_elinkno().write(|w| w.biter().bits(count)); | ||
| 225 | t.tcd_citer_elinkno().write(|w| w.citer().bits(count)); | ||
| 226 | |||
| 227 | // Control/status: enable interrupt if requested | ||
| 228 | if enable_int { | ||
| 229 | t.tcd_csr().write(|w| w.intmajor().set_bit()); | ||
| 230 | } else { | ||
| 231 | t.tcd_csr().write(|w| w.intmajor().clear_bit()); | ||
| 232 | } | ||
| 233 | |||
| 234 | cortex_m::asm::dsb(); | ||
| 235 | } | ||
| 236 | |||
| 237 | unsafe { | ||
| 238 | // Channel 0: Transfer 16 bytes total (8 bytes per minor loop, 2 major iterations) | ||
| 239 | // Minor Link -> Channel 1 | ||
| 240 | // Major Link -> Channel 2 | ||
| 241 | configure_tcd( | ||
| 242 | edma, | ||
| 243 | 0, | ||
| 244 | core::ptr::addr_of!(SRC_BUFFER) as u32, | ||
| 245 | core::ptr::addr_of_mut!(DEST_BUFFER0) as u32, | ||
| 246 | 4, // src width | ||
| 247 | 8, // nbytes (minor loop = 2 words) | ||
| 248 | 2, // count (major loop = 2 iterations) | ||
| 249 | false, // no interrupt | ||
| 250 | ); | ||
| 251 | ch0.set_minor_link(1); // Link to CH1 after each minor loop | ||
| 252 | ch0.set_major_link(2); // Link to CH2 after major loop | ||
| 253 | |||
| 254 | // Channel 1: Transfer 16 bytes (triggered by CH0 minor link) | ||
| 255 | configure_tcd( | ||
| 256 | edma, | ||
| 257 | 1, | ||
| 258 | core::ptr::addr_of!(SRC_BUFFER) as u32, | ||
| 259 | core::ptr::addr_of_mut!(DEST_BUFFER1) as u32, | ||
| 260 | 4, | ||
| 261 | 16, // full buffer in one minor loop | ||
| 262 | 1, // 1 major iteration | ||
| 263 | false, | ||
| 264 | ); | ||
| 265 | |||
| 266 | // Channel 2: Transfer 16 bytes (triggered by CH0 major link) | ||
| 267 | configure_tcd( | ||
| 268 | edma, | ||
| 269 | 2, | ||
| 270 | core::ptr::addr_of!(SRC_BUFFER) as u32, | ||
| 271 | core::ptr::addr_of_mut!(DEST_BUFFER2) as u32, | ||
| 272 | 4, | ||
| 273 | 16, // full buffer in one minor loop | ||
| 274 | 1, // 1 major iteration | ||
| 275 | true, // enable interrupt | ||
| 276 | ); | ||
| 277 | } | ||
| 278 | |||
| 279 | tx.blocking_write(b"Triggering Channel 0 (1st minor loop)...\r\n") | ||
| 280 | .unwrap(); | ||
| 281 | |||
| 282 | // Trigger first minor loop of CH0 | ||
| 283 | unsafe { | ||
| 284 | ch0.trigger_start(); | ||
| 285 | } | ||
| 286 | |||
| 287 | // Wait for CH1 to complete (triggered by CH0 minor link) | ||
| 288 | while !ch1.is_done() { | ||
| 289 | cortex_m::asm::nop(); | ||
| 290 | } | ||
| 291 | unsafe { | ||
| 292 | ch1.clear_done(); | ||
| 293 | } | ||
| 294 | |||
| 295 | tx.blocking_write(b"CH1 done (via minor link).\r\n").unwrap(); | ||
| 296 | tx.blocking_write(b"Triggering Channel 0 (2nd minor loop)...\r\n") | ||
| 297 | .unwrap(); | ||
| 298 | |||
| 299 | // Trigger second minor loop of CH0 | ||
| 300 | unsafe { | ||
| 301 | ch0.trigger_start(); | ||
| 302 | } | ||
| 303 | |||
| 304 | // Wait for CH0 major loop to complete | ||
| 305 | while !ch0.is_done() { | ||
| 306 | cortex_m::asm::nop(); | ||
| 307 | } | ||
| 308 | unsafe { | ||
| 309 | ch0.clear_done(); | ||
| 310 | } | ||
| 311 | |||
| 312 | tx.blocking_write(b"CH0 major loop done.\r\n").unwrap(); | ||
| 313 | |||
| 314 | // Wait for CH2 to complete (triggered by CH0 major link) | ||
| 315 | // Using is_done() instead of AtomicBool - the standard interrupt handler | ||
| 316 | // clears the interrupt flag and wakes wakers, but DONE bit remains set | ||
| 317 | while !ch2.is_done() { | ||
| 318 | cortex_m::asm::nop(); | ||
| 319 | } | ||
| 320 | unsafe { | ||
| 321 | ch2.clear_done(); | ||
| 322 | } | ||
| 323 | |||
| 324 | tx.blocking_write(b"CH2 done (via major link).\r\n\r\n").unwrap(); | ||
| 325 | |||
| 326 | tx.blocking_write(b"EDMA channel link example finish.\r\n\r\n").unwrap(); | ||
| 327 | |||
| 328 | tx.blocking_write(b"DEST0 (after): ").unwrap(); | ||
| 329 | print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER0) as *const u32, 4); | ||
| 330 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 331 | |||
| 332 | tx.blocking_write(b"DEST1 (after): ").unwrap(); | ||
| 333 | print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER1) as *const u32, 4); | ||
| 334 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 335 | |||
| 336 | tx.blocking_write(b"DEST2 (after): ").unwrap(); | ||
| 337 | print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER2) as *const u32, 4); | ||
| 338 | tx.blocking_write(b"\r\n\r\n").unwrap(); | ||
| 339 | |||
| 340 | // Verify all buffers match source | ||
| 341 | let mut success = true; | ||
| 342 | unsafe { | ||
| 343 | let src_ptr = core::ptr::addr_of!(SRC_BUFFER) as *const u32; | ||
| 344 | let dst0_ptr = core::ptr::addr_of!(DEST_BUFFER0) as *const u32; | ||
| 345 | let dst1_ptr = core::ptr::addr_of!(DEST_BUFFER1) as *const u32; | ||
| 346 | let dst2_ptr = core::ptr::addr_of!(DEST_BUFFER2) as *const u32; | ||
| 347 | |||
| 348 | for i in 0..4 { | ||
| 349 | if *dst0_ptr.add(i) != *src_ptr.add(i) { | ||
| 350 | success = false; | ||
| 351 | } | ||
| 352 | if *dst1_ptr.add(i) != *src_ptr.add(i) { | ||
| 353 | success = false; | ||
| 354 | } | ||
| 355 | if *dst2_ptr.add(i) != *src_ptr.add(i) { | ||
| 356 | success = false; | ||
| 357 | } | ||
| 358 | } | ||
| 359 | } | ||
| 360 | |||
| 361 | if success { | ||
| 362 | tx.blocking_write(b"PASS: Data verified.\r\n").unwrap(); | ||
| 363 | defmt::info!("PASS: Data verified."); | ||
| 364 | } else { | ||
| 365 | tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); | ||
| 366 | defmt::error!("FAIL: Mismatch detected!"); | ||
| 367 | } | ||
| 368 | |||
| 369 | loop { | ||
| 370 | cortex_m::asm::wfe(); | ||
| 371 | } | ||
| 372 | } | ||
diff --git a/examples/mcxa/src/bin/dma_interleave_transfer.rs b/examples/mcxa/src/bin/dma_interleave_transfer.rs new file mode 100644 index 000000000..7876e8978 --- /dev/null +++ b/examples/mcxa/src/bin/dma_interleave_transfer.rs | |||
| @@ -0,0 +1,215 @@ | |||
| 1 | //! DMA interleaved transfer example for MCXA276. | ||
| 2 | //! | ||
| 3 | //! This example demonstrates using DMA with custom source/destination offsets | ||
| 4 | //! to interleave data during transfer. | ||
| 5 | //! | ||
| 6 | //! # Embassy-style features demonstrated: | ||
| 7 | //! - `TransferOptions::default()` for configuration (used internally) | ||
| 8 | //! - DMA channel with `DmaChannel::new()` | ||
| 9 | |||
| 10 | #![no_std] | ||
| 11 | #![no_main] | ||
| 12 | |||
| 13 | use embassy_executor::Spawner; | ||
| 14 | use embassy_mcxa::clocks::config::Div8; | ||
| 15 | use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaChannel}; | ||
| 16 | use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; | ||
| 17 | use embassy_mcxa::{bind_interrupts, pac}; | ||
| 18 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 19 | |||
| 20 | // Bind DMA channel 0 interrupt using Embassy-style macro | ||
| 21 | bind_interrupts!(struct Irqs { | ||
| 22 | DMA_CH0 => DmaCh0InterruptHandler; | ||
| 23 | }); | ||
| 24 | |||
| 25 | const BUFFER_LENGTH: usize = 16; | ||
| 26 | const HALF_BUFF_LENGTH: usize = BUFFER_LENGTH / 2; | ||
| 27 | |||
| 28 | // Buffers in RAM | ||
| 29 | static mut SRC_BUFFER: [u32; HALF_BUFF_LENGTH] = [0; HALF_BUFF_LENGTH]; | ||
| 30 | static mut DEST_BUFFER: [u32; BUFFER_LENGTH] = [0; BUFFER_LENGTH]; | ||
| 31 | |||
| 32 | /// Helper to write a u32 as decimal ASCII to UART | ||
| 33 | fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { | ||
| 34 | let mut buf = [0u8; 10]; | ||
| 35 | let mut n = val; | ||
| 36 | let mut i = buf.len(); | ||
| 37 | |||
| 38 | if n == 0 { | ||
| 39 | tx.blocking_write(b"0").ok(); | ||
| 40 | return; | ||
| 41 | } | ||
| 42 | |||
| 43 | while n > 0 { | ||
| 44 | i -= 1; | ||
| 45 | buf[i] = b'0' + (n % 10) as u8; | ||
| 46 | n /= 10; | ||
| 47 | } | ||
| 48 | |||
| 49 | tx.blocking_write(&buf[i..]).ok(); | ||
| 50 | } | ||
| 51 | |||
| 52 | /// Helper to print a buffer to UART | ||
| 53 | fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { | ||
| 54 | tx.blocking_write(b"[").ok(); | ||
| 55 | unsafe { | ||
| 56 | for i in 0..len { | ||
| 57 | write_u32(tx, *buf_ptr.add(i)); | ||
| 58 | if i < len - 1 { | ||
| 59 | tx.blocking_write(b", ").ok(); | ||
| 60 | } | ||
| 61 | } | ||
| 62 | } | ||
| 63 | tx.blocking_write(b"]").ok(); | ||
| 64 | } | ||
| 65 | |||
| 66 | #[embassy_executor::main] | ||
| 67 | async fn main(_spawner: Spawner) { | ||
| 68 | // Small delay to allow probe-rs to attach after reset | ||
| 69 | for _ in 0..100_000 { | ||
| 70 | cortex_m::asm::nop(); | ||
| 71 | } | ||
| 72 | |||
| 73 | let mut cfg = hal::config::Config::default(); | ||
| 74 | cfg.clock_cfg.sirc.fro_12m_enabled = true; | ||
| 75 | cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); | ||
| 76 | let p = hal::init(cfg); | ||
| 77 | |||
| 78 | defmt::info!("DMA interleave transfer example starting..."); | ||
| 79 | |||
| 80 | // Enable DMA interrupt (DMA clock/reset/init is handled automatically by HAL) | ||
| 81 | unsafe { | ||
| 82 | cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); | ||
| 83 | } | ||
| 84 | |||
| 85 | let config = Config { | ||
| 86 | baudrate_bps: 115_200, | ||
| 87 | ..Default::default() | ||
| 88 | }; | ||
| 89 | |||
| 90 | let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); | ||
| 91 | let (mut tx, _rx) = lpuart.split(); | ||
| 92 | |||
| 93 | tx.blocking_write(b"EDMA interleave transfer example begin.\r\n\r\n") | ||
| 94 | .unwrap(); | ||
| 95 | |||
| 96 | // Initialize buffers | ||
| 97 | unsafe { | ||
| 98 | SRC_BUFFER = [1, 2, 3, 4, 5, 6, 7, 8]; | ||
| 99 | DEST_BUFFER = [0; BUFFER_LENGTH]; | ||
| 100 | } | ||
| 101 | |||
| 102 | tx.blocking_write(b"Source Buffer: ").unwrap(); | ||
| 103 | print_buffer(&mut tx, core::ptr::addr_of!(SRC_BUFFER) as *const u32, HALF_BUFF_LENGTH); | ||
| 104 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 105 | |||
| 106 | tx.blocking_write(b"Destination Buffer (before): ").unwrap(); | ||
| 107 | print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER) as *const u32, BUFFER_LENGTH); | ||
| 108 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 109 | |||
| 110 | tx.blocking_write(b"Configuring DMA with Embassy-style API...\r\n") | ||
| 111 | .unwrap(); | ||
| 112 | |||
| 113 | // Create DMA channel using Embassy-style API | ||
| 114 | let dma_ch0 = DmaChannel::new(p.DMA_CH0); | ||
| 115 | |||
| 116 | // Configure interleaved transfer using direct TCD access: | ||
| 117 | // - src_offset = 4: advance source by 4 bytes after each read | ||
| 118 | // - dst_offset = 8: advance dest by 8 bytes after each write | ||
| 119 | // This spreads source data across every other word in destination | ||
| 120 | unsafe { | ||
| 121 | let t = dma_ch0.tcd(); | ||
| 122 | |||
| 123 | // Reset channel state | ||
| 124 | t.ch_csr().write(|w| { | ||
| 125 | w.erq() | ||
| 126 | .disable() | ||
| 127 | .earq() | ||
| 128 | .disable() | ||
| 129 | .eei() | ||
| 130 | .no_error() | ||
| 131 | .ebw() | ||
| 132 | .disable() | ||
| 133 | .done() | ||
| 134 | .clear_bit_by_one() | ||
| 135 | }); | ||
| 136 | t.ch_es().write(|w| w.bits(0)); | ||
| 137 | t.ch_int().write(|w| w.int().clear_bit_by_one()); | ||
| 138 | |||
| 139 | // Source/destination addresses | ||
| 140 | t.tcd_saddr() | ||
| 141 | .write(|w| w.saddr().bits(core::ptr::addr_of_mut!(SRC_BUFFER) as u32)); | ||
| 142 | t.tcd_daddr() | ||
| 143 | .write(|w| w.daddr().bits(core::ptr::addr_of_mut!(DEST_BUFFER) as u32)); | ||
| 144 | |||
| 145 | // Custom offsets for interleaving | ||
| 146 | t.tcd_soff().write(|w| w.soff().bits(4)); // src: +4 bytes per read | ||
| 147 | t.tcd_doff().write(|w| w.doff().bits(8)); // dst: +8 bytes per write | ||
| 148 | |||
| 149 | // Attributes: 32-bit transfers (size = 2) | ||
| 150 | t.tcd_attr().write(|w| w.ssize().bits(2).dsize().bits(2)); | ||
| 151 | |||
| 152 | // Transfer entire source buffer in one minor loop | ||
| 153 | let nbytes = (HALF_BUFF_LENGTH * 4) as u32; | ||
| 154 | t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(nbytes)); | ||
| 155 | |||
| 156 | // Reset source address after major loop | ||
| 157 | t.tcd_slast_sda().write(|w| w.slast_sda().bits(-(nbytes as i32) as u32)); | ||
| 158 | // Destination uses 2x offset, so adjust accordingly | ||
| 159 | let dst_total = (HALF_BUFF_LENGTH * 8) as u32; | ||
| 160 | t.tcd_dlast_sga() | ||
| 161 | .write(|w| w.dlast_sga().bits(-(dst_total as i32) as u32)); | ||
| 162 | |||
| 163 | // Major loop count = 1 | ||
| 164 | t.tcd_biter_elinkno().write(|w| w.biter().bits(1)); | ||
| 165 | t.tcd_citer_elinkno().write(|w| w.citer().bits(1)); | ||
| 166 | |||
| 167 | // Enable interrupt on major loop completion | ||
| 168 | t.tcd_csr().write(|w| w.intmajor().set_bit()); | ||
| 169 | |||
| 170 | cortex_m::asm::dsb(); | ||
| 171 | |||
| 172 | tx.blocking_write(b"Triggering transfer...\r\n").unwrap(); | ||
| 173 | dma_ch0.trigger_start(); | ||
| 174 | } | ||
| 175 | |||
| 176 | // Wait for completion using channel helper method | ||
| 177 | while !dma_ch0.is_done() { | ||
| 178 | cortex_m::asm::nop(); | ||
| 179 | } | ||
| 180 | unsafe { | ||
| 181 | dma_ch0.clear_done(); | ||
| 182 | } | ||
| 183 | |||
| 184 | tx.blocking_write(b"\r\nEDMA interleave transfer example finish.\r\n\r\n") | ||
| 185 | .unwrap(); | ||
| 186 | tx.blocking_write(b"Destination Buffer (after): ").unwrap(); | ||
| 187 | print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER) as *const u32, BUFFER_LENGTH); | ||
| 188 | tx.blocking_write(b"\r\n\r\n").unwrap(); | ||
| 189 | |||
| 190 | // Verify: Even indices should match SRC_BUFFER[i/2], odd indices should be 0 | ||
| 191 | let mut mismatch = false; | ||
| 192 | unsafe { | ||
| 193 | for i in 0..BUFFER_LENGTH { | ||
| 194 | if i % 2 == 0 { | ||
| 195 | if DEST_BUFFER[i] != SRC_BUFFER[i / 2] { | ||
| 196 | mismatch = true; | ||
| 197 | } | ||
| 198 | } else if DEST_BUFFER[i] != 0 { | ||
| 199 | mismatch = true; | ||
| 200 | } | ||
| 201 | } | ||
| 202 | } | ||
| 203 | |||
| 204 | if mismatch { | ||
| 205 | tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); | ||
| 206 | defmt::error!("FAIL: Mismatch detected!"); | ||
| 207 | } else { | ||
| 208 | tx.blocking_write(b"PASS: Data verified.\r\n").unwrap(); | ||
| 209 | defmt::info!("PASS: Data verified."); | ||
| 210 | } | ||
| 211 | |||
| 212 | loop { | ||
| 213 | cortex_m::asm::wfe(); | ||
| 214 | } | ||
| 215 | } | ||
diff --git a/examples/mcxa/src/bin/dma_mem_to_mem.rs b/examples/mcxa/src/bin/dma_mem_to_mem.rs new file mode 100644 index 000000000..68f70e742 --- /dev/null +++ b/examples/mcxa/src/bin/dma_mem_to_mem.rs | |||
| @@ -0,0 +1,229 @@ | |||
| 1 | //! DMA memory-to-memory transfer example for MCXA276. | ||
| 2 | //! | ||
| 3 | //! This example demonstrates using DMA to copy data between memory buffers | ||
| 4 | //! using the Embassy-style async API with type-safe transfers. | ||
| 5 | //! | ||
| 6 | //! # Embassy-style features demonstrated: | ||
| 7 | //! - `TransferOptions` for configuration | ||
| 8 | //! - Type-safe `mem_to_mem<u32>()` method with async `.await` | ||
| 9 | //! - `Transfer` Future that can be `.await`ed | ||
| 10 | //! - `Word` trait for automatic transfer width detection | ||
| 11 | //! - `memset()` method for filling memory with a pattern | ||
| 12 | |||
| 13 | #![no_std] | ||
| 14 | #![no_main] | ||
| 15 | |||
| 16 | use embassy_executor::Spawner; | ||
| 17 | use embassy_mcxa::clocks::config::Div8; | ||
| 18 | use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaChannel, TransferOptions}; | ||
| 19 | use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; | ||
| 20 | use embassy_mcxa::{bind_interrupts, pac}; | ||
| 21 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 22 | |||
| 23 | // Bind DMA channel 0 interrupt using Embassy-style macro | ||
| 24 | bind_interrupts!(struct Irqs { | ||
| 25 | DMA_CH0 => DmaCh0InterruptHandler; | ||
| 26 | }); | ||
| 27 | |||
| 28 | const BUFFER_LENGTH: usize = 4; | ||
| 29 | |||
| 30 | // Buffers in RAM (static mut is automatically placed in .bss/.data) | ||
| 31 | static mut SRC_BUFFER: [u32; BUFFER_LENGTH] = [0; BUFFER_LENGTH]; | ||
| 32 | static mut DEST_BUFFER: [u32; BUFFER_LENGTH] = [0; BUFFER_LENGTH]; | ||
| 33 | static mut MEMSET_BUFFER: [u32; BUFFER_LENGTH] = [0; BUFFER_LENGTH]; | ||
| 34 | |||
| 35 | /// Helper to write a u32 as decimal ASCII to UART | ||
| 36 | fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { | ||
| 37 | let mut buf = [0u8; 10]; // u32 max is 4294967295 (10 digits) | ||
| 38 | let mut n = val; | ||
| 39 | let mut i = buf.len(); | ||
| 40 | |||
| 41 | if n == 0 { | ||
| 42 | tx.blocking_write(b"0").ok(); | ||
| 43 | return; | ||
| 44 | } | ||
| 45 | |||
| 46 | while n > 0 { | ||
| 47 | i -= 1; | ||
| 48 | buf[i] = b'0' + (n % 10) as u8; | ||
| 49 | n /= 10; | ||
| 50 | } | ||
| 51 | |||
| 52 | tx.blocking_write(&buf[i..]).ok(); | ||
| 53 | } | ||
| 54 | |||
| 55 | /// Helper to print a buffer as [v1, v2, v3, v4] to UART | ||
| 56 | /// Takes a raw pointer to avoid warnings about shared references to mutable statics | ||
| 57 | fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const [u32; BUFFER_LENGTH]) { | ||
| 58 | tx.blocking_write(b"[").ok(); | ||
| 59 | unsafe { | ||
| 60 | let buf = &*buf_ptr; | ||
| 61 | for (i, val) in buf.iter().enumerate() { | ||
| 62 | write_u32(tx, *val); | ||
| 63 | if i < buf.len() - 1 { | ||
| 64 | tx.blocking_write(b", ").ok(); | ||
| 65 | } | ||
| 66 | } | ||
| 67 | } | ||
| 68 | tx.blocking_write(b"]").ok(); | ||
| 69 | } | ||
| 70 | |||
| 71 | #[embassy_executor::main] | ||
| 72 | async fn main(_spawner: Spawner) { | ||
| 73 | // Small delay to allow probe-rs to attach after reset | ||
| 74 | for _ in 0..100_000 { | ||
| 75 | cortex_m::asm::nop(); | ||
| 76 | } | ||
| 77 | |||
| 78 | let mut cfg = hal::config::Config::default(); | ||
| 79 | cfg.clock_cfg.sirc.fro_12m_enabled = true; | ||
| 80 | cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); | ||
| 81 | let p = hal::init(cfg); | ||
| 82 | |||
| 83 | defmt::info!("DMA memory-to-memory example starting..."); | ||
| 84 | |||
| 85 | // Enable DMA interrupt (DMA clock/reset/init is handled automatically by HAL) | ||
| 86 | unsafe { | ||
| 87 | cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); | ||
| 88 | } | ||
| 89 | |||
| 90 | // Create UART for debug output | ||
| 91 | let config = Config { | ||
| 92 | baudrate_bps: 115_200, | ||
| 93 | ..Default::default() | ||
| 94 | }; | ||
| 95 | |||
| 96 | let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); | ||
| 97 | let (mut tx, _rx) = lpuart.split(); | ||
| 98 | |||
| 99 | tx.blocking_write(b"EDMA memory to memory example begin.\r\n\r\n") | ||
| 100 | .unwrap(); | ||
| 101 | |||
| 102 | // Initialize buffers | ||
| 103 | unsafe { | ||
| 104 | SRC_BUFFER = [1, 2, 3, 4]; | ||
| 105 | DEST_BUFFER = [0; BUFFER_LENGTH]; | ||
| 106 | } | ||
| 107 | |||
| 108 | tx.blocking_write(b"Source Buffer: ").unwrap(); | ||
| 109 | print_buffer(&mut tx, &raw const SRC_BUFFER); | ||
| 110 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 111 | |||
| 112 | tx.blocking_write(b"Destination Buffer (before): ").unwrap(); | ||
| 113 | print_buffer(&mut tx, &raw const DEST_BUFFER); | ||
| 114 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 115 | |||
| 116 | tx.blocking_write(b"Configuring DMA with Embassy-style API...\r\n") | ||
| 117 | .unwrap(); | ||
| 118 | |||
| 119 | // Create DMA channel | ||
| 120 | let dma_ch0 = DmaChannel::new(p.DMA_CH0); | ||
| 121 | |||
| 122 | // Configure transfer options (Embassy-style) | ||
| 123 | // TransferOptions defaults to: complete_transfer_interrupt = true | ||
| 124 | let options = TransferOptions::default(); | ||
| 125 | |||
| 126 | // ========================================================================= | ||
| 127 | // Part 1: Embassy-style async API demonstration (mem_to_mem) | ||
| 128 | // ========================================================================= | ||
| 129 | // | ||
| 130 | // Use the new type-safe `mem_to_mem<u32>()` method: | ||
| 131 | // - Automatically determines transfer width from buffer element type (u32) | ||
| 132 | // - Returns a `Transfer` future that can be `.await`ed | ||
| 133 | // - Uses TransferOptions for consistent configuration | ||
| 134 | // | ||
| 135 | // Using async `.await` - the executor can run other tasks while waiting! | ||
| 136 | |||
| 137 | // Perform type-safe memory-to-memory transfer using Embassy-style async API | ||
| 138 | unsafe { | ||
| 139 | let src = &*core::ptr::addr_of!(SRC_BUFFER); | ||
| 140 | let dst = &mut *core::ptr::addr_of_mut!(DEST_BUFFER); | ||
| 141 | |||
| 142 | // Using async `.await` - the executor can run other tasks while waiting! | ||
| 143 | let transfer = dma_ch0.mem_to_mem(src, dst, options); | ||
| 144 | transfer.await; | ||
| 145 | } | ||
| 146 | |||
| 147 | tx.blocking_write(b"DMA mem-to-mem transfer complete!\r\n\r\n").unwrap(); | ||
| 148 | tx.blocking_write(b"Destination Buffer (after): ").unwrap(); | ||
| 149 | print_buffer(&mut tx, &raw const DEST_BUFFER); | ||
| 150 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 151 | |||
| 152 | // Verify data | ||
| 153 | let mut mismatch = false; | ||
| 154 | unsafe { | ||
| 155 | for i in 0..BUFFER_LENGTH { | ||
| 156 | if SRC_BUFFER[i] != DEST_BUFFER[i] { | ||
| 157 | mismatch = true; | ||
| 158 | break; | ||
| 159 | } | ||
| 160 | } | ||
| 161 | } | ||
| 162 | |||
| 163 | if mismatch { | ||
| 164 | tx.blocking_write(b"FAIL: mem_to_mem mismatch!\r\n").unwrap(); | ||
| 165 | defmt::error!("FAIL: mem_to_mem mismatch!"); | ||
| 166 | } else { | ||
| 167 | tx.blocking_write(b"PASS: mem_to_mem verified.\r\n\r\n").unwrap(); | ||
| 168 | defmt::info!("PASS: mem_to_mem verified."); | ||
| 169 | } | ||
| 170 | |||
| 171 | // ========================================================================= | ||
| 172 | // Part 2: memset() demonstration | ||
| 173 | // ========================================================================= | ||
| 174 | // | ||
| 175 | // The `memset()` method fills a buffer with a pattern value: | ||
| 176 | // - Fixed source address (pattern is read repeatedly) | ||
| 177 | // - Incrementing destination address | ||
| 178 | // - Uses the same Transfer future pattern | ||
| 179 | |||
| 180 | tx.blocking_write(b"--- Demonstrating memset() feature ---\r\n\r\n") | ||
| 181 | .unwrap(); | ||
| 182 | |||
| 183 | tx.blocking_write(b"Memset Buffer (before): ").unwrap(); | ||
| 184 | print_buffer(&mut tx, &raw const MEMSET_BUFFER); | ||
| 185 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 186 | |||
| 187 | // Fill buffer with a pattern value using DMA memset | ||
| 188 | let pattern: u32 = 0xDEADBEEF; | ||
| 189 | tx.blocking_write(b"Filling with pattern 0xDEADBEEF...\r\n").unwrap(); | ||
| 190 | |||
| 191 | unsafe { | ||
| 192 | let dst = &mut *core::ptr::addr_of_mut!(MEMSET_BUFFER); | ||
| 193 | |||
| 194 | // Using blocking_wait() for demonstration - also shows non-async usage | ||
| 195 | let transfer = dma_ch0.memset(&pattern, dst, options); | ||
| 196 | transfer.blocking_wait(); | ||
| 197 | } | ||
| 198 | |||
| 199 | tx.blocking_write(b"DMA memset complete!\r\n\r\n").unwrap(); | ||
| 200 | tx.blocking_write(b"Memset Buffer (after): ").unwrap(); | ||
| 201 | print_buffer(&mut tx, &raw const MEMSET_BUFFER); | ||
| 202 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 203 | |||
| 204 | // Verify memset result | ||
| 205 | let mut memset_ok = true; | ||
| 206 | unsafe { | ||
| 207 | #[allow(clippy::needless_range_loop)] | ||
| 208 | for i in 0..BUFFER_LENGTH { | ||
| 209 | if MEMSET_BUFFER[i] != pattern { | ||
| 210 | memset_ok = false; | ||
| 211 | break; | ||
| 212 | } | ||
| 213 | } | ||
| 214 | } | ||
| 215 | |||
| 216 | if !memset_ok { | ||
| 217 | tx.blocking_write(b"FAIL: memset mismatch!\r\n").unwrap(); | ||
| 218 | defmt::error!("FAIL: memset mismatch!"); | ||
| 219 | } else { | ||
| 220 | tx.blocking_write(b"PASS: memset verified.\r\n\r\n").unwrap(); | ||
| 221 | defmt::info!("PASS: memset verified."); | ||
| 222 | } | ||
| 223 | |||
| 224 | tx.blocking_write(b"=== All DMA tests complete ===\r\n").unwrap(); | ||
| 225 | |||
| 226 | loop { | ||
| 227 | cortex_m::asm::wfe(); | ||
| 228 | } | ||
| 229 | } | ||
diff --git a/examples/mcxa/src/bin/dma_memset.rs b/examples/mcxa/src/bin/dma_memset.rs new file mode 100644 index 000000000..95e365e47 --- /dev/null +++ b/examples/mcxa/src/bin/dma_memset.rs | |||
| @@ -0,0 +1,218 @@ | |||
| 1 | //! DMA memset example for MCXA276. | ||
| 2 | //! | ||
| 3 | //! This example demonstrates using DMA to fill a buffer with a repeated pattern. | ||
| 4 | //! The source address stays fixed while the destination increments. | ||
| 5 | //! | ||
| 6 | //! # Embassy-style features demonstrated: | ||
| 7 | //! - `DmaChannel::is_done()` and `clear_done()` helper methods | ||
| 8 | //! - No need to pass register block around | ||
| 9 | |||
| 10 | #![no_std] | ||
| 11 | #![no_main] | ||
| 12 | |||
| 13 | use embassy_executor::Spawner; | ||
| 14 | use embassy_mcxa::clocks::config::Div8; | ||
| 15 | use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaChannel}; | ||
| 16 | use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; | ||
| 17 | use embassy_mcxa::{bind_interrupts, pac}; | ||
| 18 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 19 | |||
| 20 | // Bind DMA channel 0 interrupt using Embassy-style macro | ||
| 21 | bind_interrupts!(struct Irqs { | ||
| 22 | DMA_CH0 => DmaCh0InterruptHandler; | ||
| 23 | }); | ||
| 24 | |||
| 25 | const BUFFER_LENGTH: usize = 4; | ||
| 26 | |||
| 27 | // Buffers in RAM | ||
| 28 | static mut PATTERN: u32 = 0; | ||
| 29 | static mut DEST_BUFFER: [u32; BUFFER_LENGTH] = [0; BUFFER_LENGTH]; | ||
| 30 | |||
| 31 | /// Helper to write a u32 as decimal ASCII to UART | ||
| 32 | fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { | ||
| 33 | let mut buf = [0u8; 10]; | ||
| 34 | let mut n = val; | ||
| 35 | let mut i = buf.len(); | ||
| 36 | |||
| 37 | if n == 0 { | ||
| 38 | tx.blocking_write(b"0").ok(); | ||
| 39 | return; | ||
| 40 | } | ||
| 41 | |||
| 42 | while n > 0 { | ||
| 43 | i -= 1; | ||
| 44 | buf[i] = b'0' + (n % 10) as u8; | ||
| 45 | n /= 10; | ||
| 46 | } | ||
| 47 | |||
| 48 | tx.blocking_write(&buf[i..]).ok(); | ||
| 49 | } | ||
| 50 | |||
| 51 | /// Helper to print a buffer to UART | ||
| 52 | fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { | ||
| 53 | tx.blocking_write(b"[").ok(); | ||
| 54 | unsafe { | ||
| 55 | for i in 0..len { | ||
| 56 | write_u32(tx, *buf_ptr.add(i)); | ||
| 57 | if i < len - 1 { | ||
| 58 | tx.blocking_write(b", ").ok(); | ||
| 59 | } | ||
| 60 | } | ||
| 61 | } | ||
| 62 | tx.blocking_write(b"]").ok(); | ||
| 63 | } | ||
| 64 | |||
| 65 | #[embassy_executor::main] | ||
| 66 | async fn main(_spawner: Spawner) { | ||
| 67 | // Small delay to allow probe-rs to attach after reset | ||
| 68 | for _ in 0..100_000 { | ||
| 69 | cortex_m::asm::nop(); | ||
| 70 | } | ||
| 71 | |||
| 72 | let mut cfg = hal::config::Config::default(); | ||
| 73 | cfg.clock_cfg.sirc.fro_12m_enabled = true; | ||
| 74 | cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); | ||
| 75 | let p = hal::init(cfg); | ||
| 76 | |||
| 77 | defmt::info!("DMA memset example starting..."); | ||
| 78 | |||
| 79 | // Enable DMA interrupt (DMA clock/reset/init is handled automatically by HAL) | ||
| 80 | unsafe { | ||
| 81 | cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); | ||
| 82 | } | ||
| 83 | |||
| 84 | let config = Config { | ||
| 85 | baudrate_bps: 115_200, | ||
| 86 | ..Default::default() | ||
| 87 | }; | ||
| 88 | |||
| 89 | let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); | ||
| 90 | let (mut tx, _rx) = lpuart.split(); | ||
| 91 | |||
| 92 | tx.blocking_write(b"EDMA memset example begin.\r\n\r\n").unwrap(); | ||
| 93 | |||
| 94 | // Initialize buffers | ||
| 95 | unsafe { | ||
| 96 | PATTERN = 0xDEADBEEF; | ||
| 97 | DEST_BUFFER = [0; BUFFER_LENGTH]; | ||
| 98 | } | ||
| 99 | |||
| 100 | tx.blocking_write(b"Pattern value: 0x").unwrap(); | ||
| 101 | // Print pattern in hex | ||
| 102 | unsafe { | ||
| 103 | let hex_chars = b"0123456789ABCDEF"; | ||
| 104 | let mut hex_buf = [0u8; 8]; | ||
| 105 | let mut val = PATTERN; | ||
| 106 | for i in (0..8).rev() { | ||
| 107 | hex_buf[i] = hex_chars[(val & 0xF) as usize]; | ||
| 108 | val >>= 4; | ||
| 109 | } | ||
| 110 | tx.blocking_write(&hex_buf).ok(); | ||
| 111 | } | ||
| 112 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 113 | |||
| 114 | tx.blocking_write(b"Destination Buffer (before): ").unwrap(); | ||
| 115 | print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER) as *const u32, BUFFER_LENGTH); | ||
| 116 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 117 | |||
| 118 | tx.blocking_write(b"Configuring DMA with Embassy-style API...\r\n") | ||
| 119 | .unwrap(); | ||
| 120 | |||
| 121 | // Create DMA channel using Embassy-style API | ||
| 122 | let dma_ch0 = DmaChannel::new(p.DMA_CH0); | ||
| 123 | |||
| 124 | // Configure memset transfer using direct TCD access: | ||
| 125 | // Source stays fixed (soff = 0, reads same pattern repeatedly) | ||
| 126 | // Destination increments (doff = 4) | ||
| 127 | unsafe { | ||
| 128 | let t = dma_ch0.tcd(); | ||
| 129 | |||
| 130 | // Reset channel state | ||
| 131 | t.ch_csr().write(|w| { | ||
| 132 | w.erq() | ||
| 133 | .disable() | ||
| 134 | .earq() | ||
| 135 | .disable() | ||
| 136 | .eei() | ||
| 137 | .no_error() | ||
| 138 | .ebw() | ||
| 139 | .disable() | ||
| 140 | .done() | ||
| 141 | .clear_bit_by_one() | ||
| 142 | }); | ||
| 143 | t.ch_es().write(|w| w.bits(0)); | ||
| 144 | t.ch_int().write(|w| w.int().clear_bit_by_one()); | ||
| 145 | |||
| 146 | // Source address (pattern) - fixed | ||
| 147 | t.tcd_saddr() | ||
| 148 | .write(|w| w.saddr().bits(core::ptr::addr_of_mut!(PATTERN) as u32)); | ||
| 149 | // Destination address - increments | ||
| 150 | t.tcd_daddr() | ||
| 151 | .write(|w| w.daddr().bits(core::ptr::addr_of_mut!(DEST_BUFFER) as u32)); | ||
| 152 | |||
| 153 | // Source offset = 0 (stays fixed), Dest offset = 4 (increments) | ||
| 154 | t.tcd_soff().write(|w| w.soff().bits(0)); | ||
| 155 | t.tcd_doff().write(|w| w.doff().bits(4)); | ||
| 156 | |||
| 157 | // Attributes: 32-bit transfers (size = 2) | ||
| 158 | t.tcd_attr().write(|w| w.ssize().bits(2).dsize().bits(2)); | ||
| 159 | |||
| 160 | // Transfer entire buffer in one minor loop | ||
| 161 | let nbytes = (BUFFER_LENGTH * 4) as u32; | ||
| 162 | t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(nbytes)); | ||
| 163 | |||
| 164 | // Source doesn't need adjustment (stays fixed) | ||
| 165 | t.tcd_slast_sda().write(|w| w.slast_sda().bits(0)); | ||
| 166 | // Reset dest address after major loop | ||
| 167 | t.tcd_dlast_sga().write(|w| w.dlast_sga().bits(-(nbytes as i32) as u32)); | ||
| 168 | |||
| 169 | // Major loop count = 1 | ||
| 170 | t.tcd_biter_elinkno().write(|w| w.biter().bits(1)); | ||
| 171 | t.tcd_citer_elinkno().write(|w| w.citer().bits(1)); | ||
| 172 | |||
| 173 | // Enable interrupt on major loop completion | ||
| 174 | t.tcd_csr().write(|w| w.intmajor().set_bit()); | ||
| 175 | |||
| 176 | cortex_m::asm::dsb(); | ||
| 177 | |||
| 178 | tx.blocking_write(b"Triggering transfer...\r\n").unwrap(); | ||
| 179 | dma_ch0.trigger_start(); | ||
| 180 | } | ||
| 181 | |||
| 182 | // Wait for completion using channel helper method | ||
| 183 | while !dma_ch0.is_done() { | ||
| 184 | cortex_m::asm::nop(); | ||
| 185 | } | ||
| 186 | unsafe { | ||
| 187 | dma_ch0.clear_done(); | ||
| 188 | } | ||
| 189 | |||
| 190 | tx.blocking_write(b"\r\nEDMA memset example finish.\r\n\r\n").unwrap(); | ||
| 191 | tx.blocking_write(b"Destination Buffer (after): ").unwrap(); | ||
| 192 | print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER) as *const u32, BUFFER_LENGTH); | ||
| 193 | tx.blocking_write(b"\r\n\r\n").unwrap(); | ||
| 194 | |||
| 195 | // Verify: All elements should equal PATTERN | ||
| 196 | let mut mismatch = false; | ||
| 197 | unsafe { | ||
| 198 | #[allow(clippy::needless_range_loop)] | ||
| 199 | for i in 0..BUFFER_LENGTH { | ||
| 200 | if DEST_BUFFER[i] != PATTERN { | ||
| 201 | mismatch = true; | ||
| 202 | break; | ||
| 203 | } | ||
| 204 | } | ||
| 205 | } | ||
| 206 | |||
| 207 | if mismatch { | ||
| 208 | tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); | ||
| 209 | defmt::error!("FAIL: Mismatch detected!"); | ||
| 210 | } else { | ||
| 211 | tx.blocking_write(b"PASS: Data verified.\r\n").unwrap(); | ||
| 212 | defmt::info!("PASS: Data verified."); | ||
| 213 | } | ||
| 214 | |||
| 215 | loop { | ||
| 216 | cortex_m::asm::wfe(); | ||
| 217 | } | ||
| 218 | } | ||
diff --git a/examples/mcxa/src/bin/dma_ping_pong_transfer.rs b/examples/mcxa/src/bin/dma_ping_pong_transfer.rs new file mode 100644 index 000000000..f8f543382 --- /dev/null +++ b/examples/mcxa/src/bin/dma_ping_pong_transfer.rs | |||
| @@ -0,0 +1,376 @@ | |||
| 1 | //! DMA ping-pong/double-buffer transfer example for MCXA276. | ||
| 2 | //! | ||
| 3 | //! This example demonstrates two approaches for ping-pong/double-buffering: | ||
| 4 | //! | ||
| 5 | //! ## Approach 1: Scatter/Gather with linked TCDs (manual) | ||
| 6 | //! - Two TCDs link to each other for alternating transfers | ||
| 7 | //! - Uses custom handler that delegates to on_interrupt() then signals completion | ||
| 8 | //! - Note: With ESG=1, DONE bit is cleared by hardware when next TCD loads, | ||
| 9 | //! so we need an AtomicBool to track completion | ||
| 10 | //! | ||
| 11 | //! ## Approach 2: Half-transfer interrupt with wait_half() (NEW!) | ||
| 12 | //! - Single continuous transfer over entire buffer | ||
| 13 | //! - Uses half-transfer interrupt to know when first half is ready | ||
| 14 | //! - Application can process first half while second half is being filled | ||
| 15 | //! | ||
| 16 | //! # Embassy-style features demonstrated: | ||
| 17 | //! - `DmaChannel::new()` for channel creation | ||
| 18 | //! - Scatter/gather with linked TCDs | ||
| 19 | //! - Custom handler that delegates to HAL's `on_interrupt()` (best practice) | ||
| 20 | //! - Standard `DmaCh1InterruptHandler` with `bind_interrupts!` macro | ||
| 21 | //! - NEW: `wait_half()` for half-transfer interrupt handling | ||
| 22 | |||
| 23 | #![no_std] | ||
| 24 | #![no_main] | ||
| 25 | |||
| 26 | use core::sync::atomic::{AtomicBool, Ordering}; | ||
| 27 | |||
| 28 | use embassy_executor::Spawner; | ||
| 29 | use embassy_mcxa::clocks::config::Div8; | ||
| 30 | use embassy_mcxa::dma::{self, DmaCh1InterruptHandler, DmaChannel, Tcd, TransferOptions}; | ||
| 31 | use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; | ||
| 32 | use embassy_mcxa::{bind_interrupts, pac}; | ||
| 33 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 34 | |||
| 35 | // Source and destination buffers for Approach 1 (scatter/gather) | ||
| 36 | static mut SRC: [u32; 8] = [1, 2, 3, 4, 5, 6, 7, 8]; | ||
| 37 | static mut DST: [u32; 8] = [0; 8]; | ||
| 38 | |||
| 39 | // Source and destination buffers for Approach 2 (wait_half) | ||
| 40 | static mut SRC2: [u32; 8] = [0xA1, 0xA2, 0xA3, 0xA4, 0xB1, 0xB2, 0xB3, 0xB4]; | ||
| 41 | static mut DST2: [u32; 8] = [0; 8]; | ||
| 42 | |||
| 43 | // TCD pool for scatter/gather - must be 32-byte aligned | ||
| 44 | #[repr(C, align(32))] | ||
| 45 | struct TcdPool([Tcd; 2]); | ||
| 46 | |||
| 47 | static mut TCD_POOL: TcdPool = TcdPool( | ||
| 48 | [Tcd { | ||
| 49 | saddr: 0, | ||
| 50 | soff: 0, | ||
| 51 | attr: 0, | ||
| 52 | nbytes: 0, | ||
| 53 | slast: 0, | ||
| 54 | daddr: 0, | ||
| 55 | doff: 0, | ||
| 56 | citer: 0, | ||
| 57 | dlast_sga: 0, | ||
| 58 | csr: 0, | ||
| 59 | biter: 0, | ||
| 60 | }; 2], | ||
| 61 | ); | ||
| 62 | |||
| 63 | // AtomicBool to track scatter/gather completion | ||
| 64 | // Note: With ESG=1, DONE bit is cleared by hardware when next TCD loads, | ||
| 65 | // so we need this flag to detect when each transfer completes | ||
| 66 | static TRANSFER_DONE: AtomicBool = AtomicBool::new(false); | ||
| 67 | |||
| 68 | // Custom handler for scatter/gather that delegates to HAL's on_interrupt() | ||
| 69 | // This follows the "interrupts as threads" pattern - the handler does minimal work | ||
| 70 | // (delegates to HAL + sets a flag) and the main task does the actual processing | ||
| 71 | pub struct PingPongDmaHandler; | ||
| 72 | |||
| 73 | impl embassy_mcxa::interrupt::typelevel::Handler<embassy_mcxa::interrupt::typelevel::DMA_CH0> for PingPongDmaHandler { | ||
| 74 | unsafe fn on_interrupt() { | ||
| 75 | // Delegate to HAL's on_interrupt() which clears INT flag and wakes wakers | ||
| 76 | dma::on_interrupt(0); | ||
| 77 | // Signal completion for polling (needed because ESG clears DONE bit) | ||
| 78 | TRANSFER_DONE.store(true, Ordering::Release); | ||
| 79 | } | ||
| 80 | } | ||
| 81 | |||
| 82 | // Bind DMA channel interrupts | ||
| 83 | // CH0: Custom handler for scatter/gather (delegates to on_interrupt + sets flag) | ||
| 84 | // CH1: Standard handler for wait_half() demo | ||
| 85 | bind_interrupts!(struct Irqs { | ||
| 86 | DMA_CH0 => PingPongDmaHandler; | ||
| 87 | DMA_CH1 => DmaCh1InterruptHandler; | ||
| 88 | }); | ||
| 89 | |||
| 90 | /// Helper to write a u32 as decimal ASCII to UART | ||
| 91 | fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { | ||
| 92 | let mut buf = [0u8; 10]; | ||
| 93 | let mut n = val; | ||
| 94 | let mut i = buf.len(); | ||
| 95 | |||
| 96 | if n == 0 { | ||
| 97 | tx.blocking_write(b"0").ok(); | ||
| 98 | return; | ||
| 99 | } | ||
| 100 | |||
| 101 | while n > 0 { | ||
| 102 | i -= 1; | ||
| 103 | buf[i] = b'0' + (n % 10) as u8; | ||
| 104 | n /= 10; | ||
| 105 | } | ||
| 106 | |||
| 107 | tx.blocking_write(&buf[i..]).ok(); | ||
| 108 | } | ||
| 109 | |||
| 110 | /// Helper to print a buffer to UART | ||
| 111 | fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { | ||
| 112 | tx.blocking_write(b"[").ok(); | ||
| 113 | unsafe { | ||
| 114 | for i in 0..len { | ||
| 115 | write_u32(tx, *buf_ptr.add(i)); | ||
| 116 | if i < len - 1 { | ||
| 117 | tx.blocking_write(b", ").ok(); | ||
| 118 | } | ||
| 119 | } | ||
| 120 | } | ||
| 121 | tx.blocking_write(b"]").ok(); | ||
| 122 | } | ||
| 123 | |||
| 124 | #[embassy_executor::main] | ||
| 125 | async fn main(_spawner: Spawner) { | ||
| 126 | // Small delay to allow probe-rs to attach after reset | ||
| 127 | for _ in 0..100_000 { | ||
| 128 | cortex_m::asm::nop(); | ||
| 129 | } | ||
| 130 | |||
| 131 | let mut cfg = hal::config::Config::default(); | ||
| 132 | cfg.clock_cfg.sirc.fro_12m_enabled = true; | ||
| 133 | cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); | ||
| 134 | let p = hal::init(cfg); | ||
| 135 | |||
| 136 | defmt::info!("DMA ping-pong transfer example starting..."); | ||
| 137 | |||
| 138 | // Enable DMA interrupt (DMA clock/reset/init is handled automatically by HAL) | ||
| 139 | unsafe { | ||
| 140 | cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); | ||
| 141 | } | ||
| 142 | |||
| 143 | let config = Config { | ||
| 144 | baudrate_bps: 115_200, | ||
| 145 | ..Default::default() | ||
| 146 | }; | ||
| 147 | |||
| 148 | let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); | ||
| 149 | let (mut tx, _rx) = lpuart.split(); | ||
| 150 | |||
| 151 | tx.blocking_write(b"EDMA ping-pong transfer example begin.\r\n\r\n") | ||
| 152 | .unwrap(); | ||
| 153 | |||
| 154 | // Initialize buffers | ||
| 155 | unsafe { | ||
| 156 | SRC = [1, 2, 3, 4, 5, 6, 7, 8]; | ||
| 157 | DST = [0; 8]; | ||
| 158 | } | ||
| 159 | |||
| 160 | tx.blocking_write(b"Source Buffer: ").unwrap(); | ||
| 161 | print_buffer(&mut tx, core::ptr::addr_of!(SRC) as *const u32, 8); | ||
| 162 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 163 | |||
| 164 | tx.blocking_write(b"Destination Buffer (before): ").unwrap(); | ||
| 165 | print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); | ||
| 166 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 167 | |||
| 168 | tx.blocking_write(b"Configuring ping-pong DMA with Embassy-style API...\r\n") | ||
| 169 | .unwrap(); | ||
| 170 | |||
| 171 | let dma_ch0 = DmaChannel::new(p.DMA_CH0); | ||
| 172 | |||
| 173 | // Configure ping-pong transfer using direct TCD access: | ||
| 174 | // This sets up TCD0 and TCD1 in RAM, and loads TCD0 into the channel. | ||
| 175 | // TCD0 transfers first half (SRC[0..4] -> DST[0..4]), links to TCD1. | ||
| 176 | // TCD1 transfers second half (SRC[4..8] -> DST[4..8]), links to TCD0. | ||
| 177 | unsafe { | ||
| 178 | let tcds = &mut *core::ptr::addr_of_mut!(TCD_POOL.0); | ||
| 179 | let src_ptr = core::ptr::addr_of!(SRC) as *const u32; | ||
| 180 | let dst_ptr = core::ptr::addr_of_mut!(DST) as *mut u32; | ||
| 181 | |||
| 182 | let half_len = 4usize; | ||
| 183 | let half_bytes = (half_len * 4) as u32; | ||
| 184 | |||
| 185 | let tcd0_addr = &tcds[0] as *const _ as u32; | ||
| 186 | let tcd1_addr = &tcds[1] as *const _ as u32; | ||
| 187 | |||
| 188 | // TCD0: First half -> Links to TCD1 | ||
| 189 | tcds[0] = Tcd { | ||
| 190 | saddr: src_ptr as u32, | ||
| 191 | soff: 4, | ||
| 192 | attr: 0x0202, // 32-bit src/dst | ||
| 193 | nbytes: half_bytes, | ||
| 194 | slast: 0, | ||
| 195 | daddr: dst_ptr as u32, | ||
| 196 | doff: 4, | ||
| 197 | citer: 1, | ||
| 198 | dlast_sga: tcd1_addr as i32, | ||
| 199 | csr: 0x0012, // ESG | INTMAJOR | ||
| 200 | biter: 1, | ||
| 201 | }; | ||
| 202 | |||
| 203 | // TCD1: Second half -> Links to TCD0 | ||
| 204 | tcds[1] = Tcd { | ||
| 205 | saddr: src_ptr.add(half_len) as u32, | ||
| 206 | soff: 4, | ||
| 207 | attr: 0x0202, | ||
| 208 | nbytes: half_bytes, | ||
| 209 | slast: 0, | ||
| 210 | daddr: dst_ptr.add(half_len) as u32, | ||
| 211 | doff: 4, | ||
| 212 | citer: 1, | ||
| 213 | dlast_sga: tcd0_addr as i32, | ||
| 214 | csr: 0x0012, | ||
| 215 | biter: 1, | ||
| 216 | }; | ||
| 217 | |||
| 218 | // Load TCD0 into hardware registers | ||
| 219 | dma_ch0.load_tcd(&tcds[0]); | ||
| 220 | } | ||
| 221 | |||
| 222 | tx.blocking_write(b"Triggering first half transfer...\r\n").unwrap(); | ||
| 223 | |||
| 224 | // Trigger first transfer (first half: SRC[0..4] -> DST[0..4]) | ||
| 225 | unsafe { | ||
| 226 | dma_ch0.trigger_start(); | ||
| 227 | } | ||
| 228 | |||
| 229 | // Wait for first half | ||
| 230 | while !TRANSFER_DONE.load(Ordering::Acquire) { | ||
| 231 | cortex_m::asm::nop(); | ||
| 232 | } | ||
| 233 | TRANSFER_DONE.store(false, Ordering::Release); | ||
| 234 | |||
| 235 | tx.blocking_write(b"First half transferred.\r\n").unwrap(); | ||
| 236 | tx.blocking_write(b"Triggering second half transfer...\r\n").unwrap(); | ||
| 237 | |||
| 238 | // Trigger second transfer (second half: SRC[4..8] -> DST[4..8]) | ||
| 239 | unsafe { | ||
| 240 | dma_ch0.trigger_start(); | ||
| 241 | } | ||
| 242 | |||
| 243 | // Wait for second half | ||
| 244 | while !TRANSFER_DONE.load(Ordering::Acquire) { | ||
| 245 | cortex_m::asm::nop(); | ||
| 246 | } | ||
| 247 | TRANSFER_DONE.store(false, Ordering::Release); | ||
| 248 | |||
| 249 | tx.blocking_write(b"Second half transferred.\r\n\r\n").unwrap(); | ||
| 250 | |||
| 251 | tx.blocking_write(b"EDMA ping-pong transfer example finish.\r\n\r\n") | ||
| 252 | .unwrap(); | ||
| 253 | tx.blocking_write(b"Destination Buffer (after): ").unwrap(); | ||
| 254 | print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); | ||
| 255 | tx.blocking_write(b"\r\n\r\n").unwrap(); | ||
| 256 | |||
| 257 | // Verify: DST should match SRC | ||
| 258 | let mut mismatch = false; | ||
| 259 | unsafe { | ||
| 260 | let src_ptr = core::ptr::addr_of!(SRC) as *const u32; | ||
| 261 | let dst_ptr = core::ptr::addr_of!(DST) as *const u32; | ||
| 262 | for i in 0..8 { | ||
| 263 | if *src_ptr.add(i) != *dst_ptr.add(i) { | ||
| 264 | mismatch = true; | ||
| 265 | break; | ||
| 266 | } | ||
| 267 | } | ||
| 268 | } | ||
| 269 | |||
| 270 | if mismatch { | ||
| 271 | tx.blocking_write(b"FAIL: Approach 1 mismatch detected!\r\n").unwrap(); | ||
| 272 | defmt::error!("FAIL: Approach 1 mismatch detected!"); | ||
| 273 | } else { | ||
| 274 | tx.blocking_write(b"PASS: Approach 1 data verified.\r\n\r\n").unwrap(); | ||
| 275 | defmt::info!("PASS: Approach 1 data verified."); | ||
| 276 | } | ||
| 277 | |||
| 278 | // ========================================================================= | ||
| 279 | // Approach 2: Half-Transfer Interrupt with wait_half() (NEW!) | ||
| 280 | // ========================================================================= | ||
| 281 | // | ||
| 282 | // This approach uses a single continuous DMA transfer with half-transfer | ||
| 283 | // interrupt enabled. The wait_half() method allows you to be notified | ||
| 284 | // when the first half of the buffer is complete, so you can process it | ||
| 285 | // while the second half is still being filled. | ||
| 286 | // | ||
| 287 | // Benefits: | ||
| 288 | // - Simpler setup (no TCD pool needed) | ||
| 289 | // - True async/await support | ||
| 290 | // - Good for streaming data processing | ||
| 291 | |||
| 292 | tx.blocking_write(b"--- Approach 2: wait_half() demo ---\r\n\r\n") | ||
| 293 | .unwrap(); | ||
| 294 | |||
| 295 | // Enable DMA CH1 interrupt | ||
| 296 | unsafe { | ||
| 297 | cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH1); | ||
| 298 | } | ||
| 299 | |||
| 300 | // Initialize approach 2 buffers | ||
| 301 | unsafe { | ||
| 302 | SRC2 = [0xA1, 0xA2, 0xA3, 0xA4, 0xB1, 0xB2, 0xB3, 0xB4]; | ||
| 303 | DST2 = [0; 8]; | ||
| 304 | } | ||
| 305 | |||
| 306 | tx.blocking_write(b"SRC2: ").unwrap(); | ||
| 307 | print_buffer(&mut tx, core::ptr::addr_of!(SRC2) as *const u32, 8); | ||
| 308 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 309 | |||
| 310 | let dma_ch1 = DmaChannel::new(p.DMA_CH1); | ||
| 311 | |||
| 312 | // Configure transfer with half-transfer interrupt enabled | ||
| 313 | let mut options = TransferOptions::default(); | ||
| 314 | options.half_transfer_interrupt = true; // Enable half-transfer interrupt | ||
| 315 | options.complete_transfer_interrupt = true; | ||
| 316 | |||
| 317 | tx.blocking_write(b"Starting transfer with half_transfer_interrupt...\r\n") | ||
| 318 | .unwrap(); | ||
| 319 | |||
| 320 | unsafe { | ||
| 321 | let src = &*core::ptr::addr_of!(SRC2); | ||
| 322 | let dst = &mut *core::ptr::addr_of_mut!(DST2); | ||
| 323 | |||
| 324 | // Create the transfer | ||
| 325 | let mut transfer = dma_ch1.mem_to_mem(src, dst, options); | ||
| 326 | |||
| 327 | // Wait for half-transfer (first 4 elements) | ||
| 328 | tx.blocking_write(b"Waiting for first half...\r\n").unwrap(); | ||
| 329 | let half_ok = transfer.wait_half().await; | ||
| 330 | |||
| 331 | if half_ok { | ||
| 332 | tx.blocking_write(b"Half-transfer complete! First half of DST2: ") | ||
| 333 | .unwrap(); | ||
| 334 | print_buffer(&mut tx, core::ptr::addr_of!(DST2) as *const u32, 4); | ||
| 335 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 336 | tx.blocking_write(b"(Processing first half while second half transfers...)\r\n") | ||
| 337 | .unwrap(); | ||
| 338 | } | ||
| 339 | |||
| 340 | // Wait for complete transfer | ||
| 341 | tx.blocking_write(b"Waiting for second half...\r\n").unwrap(); | ||
| 342 | transfer.await; | ||
| 343 | } | ||
| 344 | |||
| 345 | tx.blocking_write(b"Transfer complete! Full DST2: ").unwrap(); | ||
| 346 | print_buffer(&mut tx, core::ptr::addr_of!(DST2) as *const u32, 8); | ||
| 347 | tx.blocking_write(b"\r\n\r\n").unwrap(); | ||
| 348 | |||
| 349 | // Verify approach 2 | ||
| 350 | let mut mismatch2 = false; | ||
| 351 | unsafe { | ||
| 352 | let src_ptr = core::ptr::addr_of!(SRC2) as *const u32; | ||
| 353 | let dst_ptr = core::ptr::addr_of!(DST2) as *const u32; | ||
| 354 | for i in 0..8 { | ||
| 355 | if *src_ptr.add(i) != *dst_ptr.add(i) { | ||
| 356 | mismatch2 = true; | ||
| 357 | break; | ||
| 358 | } | ||
| 359 | } | ||
| 360 | } | ||
| 361 | |||
| 362 | if mismatch2 { | ||
| 363 | tx.blocking_write(b"FAIL: Approach 2 mismatch!\r\n").unwrap(); | ||
| 364 | defmt::error!("FAIL: Approach 2 mismatch!"); | ||
| 365 | } else { | ||
| 366 | tx.blocking_write(b"PASS: Approach 2 verified.\r\n").unwrap(); | ||
| 367 | defmt::info!("PASS: Approach 2 verified."); | ||
| 368 | } | ||
| 369 | |||
| 370 | tx.blocking_write(b"\r\n=== All ping-pong demos complete ===\r\n") | ||
| 371 | .unwrap(); | ||
| 372 | |||
| 373 | loop { | ||
| 374 | cortex_m::asm::wfe(); | ||
| 375 | } | ||
| 376 | } | ||
diff --git a/examples/mcxa/src/bin/dma_scatter_gather.rs b/examples/mcxa/src/bin/dma_scatter_gather.rs new file mode 100644 index 000000000..4b26bc2ed --- /dev/null +++ b/examples/mcxa/src/bin/dma_scatter_gather.rs | |||
| @@ -0,0 +1,262 @@ | |||
| 1 | //! DMA scatter-gather transfer example for MCXA276. | ||
| 2 | //! | ||
| 3 | //! This example demonstrates using DMA with scatter/gather to chain multiple | ||
| 4 | //! transfer descriptors. The first TCD transfers the first half of the buffer, | ||
| 5 | //! then automatically loads the second TCD to transfer the second half. | ||
| 6 | //! | ||
| 7 | //! # Embassy-style features demonstrated: | ||
| 8 | //! - `DmaChannel::new()` for channel creation | ||
| 9 | //! - Scatter/gather with chained TCDs | ||
| 10 | //! - Custom handler that delegates to HAL's `on_interrupt()` (best practice) | ||
| 11 | |||
| 12 | #![no_std] | ||
| 13 | #![no_main] | ||
| 14 | |||
| 15 | use core::sync::atomic::{AtomicBool, Ordering}; | ||
| 16 | |||
| 17 | use embassy_executor::Spawner; | ||
| 18 | use embassy_mcxa::clocks::config::Div8; | ||
| 19 | use embassy_mcxa::dma::{self, DmaChannel, Tcd}; | ||
| 20 | use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; | ||
| 21 | use embassy_mcxa::{bind_interrupts, pac}; | ||
| 22 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 23 | |||
| 24 | // Source and destination buffers | ||
| 25 | static mut SRC: [u32; 8] = [1, 2, 3, 4, 5, 6, 7, 8]; | ||
| 26 | static mut DST: [u32; 8] = [0; 8]; | ||
| 27 | |||
| 28 | // TCD pool for scatter/gather - must be 32-byte aligned | ||
| 29 | #[repr(C, align(32))] | ||
| 30 | struct TcdPool([Tcd; 2]); | ||
| 31 | |||
| 32 | static mut TCD_POOL: TcdPool = TcdPool( | ||
| 33 | [Tcd { | ||
| 34 | saddr: 0, | ||
| 35 | soff: 0, | ||
| 36 | attr: 0, | ||
| 37 | nbytes: 0, | ||
| 38 | slast: 0, | ||
| 39 | daddr: 0, | ||
| 40 | doff: 0, | ||
| 41 | citer: 0, | ||
| 42 | dlast_sga: 0, | ||
| 43 | csr: 0, | ||
| 44 | biter: 0, | ||
| 45 | }; 2], | ||
| 46 | ); | ||
| 47 | |||
| 48 | // AtomicBool to track scatter/gather completion | ||
| 49 | // Note: With ESG=1, DONE bit is cleared by hardware when next TCD loads, | ||
| 50 | // so we need this flag to detect when each transfer completes | ||
| 51 | static TRANSFER_DONE: AtomicBool = AtomicBool::new(false); | ||
| 52 | |||
| 53 | // Custom handler for scatter/gather that delegates to HAL's on_interrupt() | ||
| 54 | // This follows the "interrupts as threads" pattern - the handler does minimal work | ||
| 55 | // (delegates to HAL + sets a flag) and the main task does the actual processing | ||
| 56 | pub struct ScatterGatherDmaHandler; | ||
| 57 | |||
| 58 | impl embassy_mcxa::interrupt::typelevel::Handler<embassy_mcxa::interrupt::typelevel::DMA_CH0> | ||
| 59 | for ScatterGatherDmaHandler | ||
| 60 | { | ||
| 61 | unsafe fn on_interrupt() { | ||
| 62 | // Delegate to HAL's on_interrupt() which clears INT flag and wakes wakers | ||
| 63 | dma::on_interrupt(0); | ||
| 64 | // Signal completion for polling (needed because ESG clears DONE bit) | ||
| 65 | TRANSFER_DONE.store(true, Ordering::Release); | ||
| 66 | } | ||
| 67 | } | ||
| 68 | |||
| 69 | // Bind DMA channel interrupt | ||
| 70 | // Custom handler for scatter/gather (delegates to on_interrupt + sets flag) | ||
| 71 | bind_interrupts!(struct Irqs { | ||
| 72 | DMA_CH0 => ScatterGatherDmaHandler; | ||
| 73 | }); | ||
| 74 | |||
| 75 | /// Helper to write a u32 as decimal ASCII to UART | ||
| 76 | fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { | ||
| 77 | let mut buf = [0u8; 10]; | ||
| 78 | let mut n = val; | ||
| 79 | let mut i = buf.len(); | ||
| 80 | |||
| 81 | if n == 0 { | ||
| 82 | tx.blocking_write(b"0").ok(); | ||
| 83 | return; | ||
| 84 | } | ||
| 85 | |||
| 86 | while n > 0 { | ||
| 87 | i -= 1; | ||
| 88 | buf[i] = b'0' + (n % 10) as u8; | ||
| 89 | n /= 10; | ||
| 90 | } | ||
| 91 | |||
| 92 | tx.blocking_write(&buf[i..]).ok(); | ||
| 93 | } | ||
| 94 | |||
| 95 | /// Helper to print a buffer to UART | ||
| 96 | fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { | ||
| 97 | tx.blocking_write(b"[").ok(); | ||
| 98 | unsafe { | ||
| 99 | for i in 0..len { | ||
| 100 | write_u32(tx, *buf_ptr.add(i)); | ||
| 101 | if i < len - 1 { | ||
| 102 | tx.blocking_write(b", ").ok(); | ||
| 103 | } | ||
| 104 | } | ||
| 105 | } | ||
| 106 | tx.blocking_write(b"]").ok(); | ||
| 107 | } | ||
| 108 | |||
| 109 | #[embassy_executor::main] | ||
| 110 | async fn main(_spawner: Spawner) { | ||
| 111 | // Small delay to allow probe-rs to attach after reset | ||
| 112 | for _ in 0..100_000 { | ||
| 113 | cortex_m::asm::nop(); | ||
| 114 | } | ||
| 115 | |||
| 116 | let mut cfg = hal::config::Config::default(); | ||
| 117 | cfg.clock_cfg.sirc.fro_12m_enabled = true; | ||
| 118 | cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); | ||
| 119 | let p = hal::init(cfg); | ||
| 120 | |||
| 121 | defmt::info!("DMA scatter-gather transfer example starting..."); | ||
| 122 | |||
| 123 | // DMA is initialized during hal::init() - no need to call ensure_init() | ||
| 124 | |||
| 125 | // Enable DMA interrupt | ||
| 126 | unsafe { | ||
| 127 | cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); | ||
| 128 | } | ||
| 129 | |||
| 130 | let config = Config { | ||
| 131 | baudrate_bps: 115_200, | ||
| 132 | ..Default::default() | ||
| 133 | }; | ||
| 134 | |||
| 135 | let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); | ||
| 136 | let (mut tx, _rx) = lpuart.split(); | ||
| 137 | |||
| 138 | tx.blocking_write(b"EDMA scatter-gather transfer example begin.\r\n\r\n") | ||
| 139 | .unwrap(); | ||
| 140 | |||
| 141 | // Initialize buffers | ||
| 142 | unsafe { | ||
| 143 | SRC = [1, 2, 3, 4, 5, 6, 7, 8]; | ||
| 144 | DST = [0; 8]; | ||
| 145 | } | ||
| 146 | |||
| 147 | tx.blocking_write(b"Source Buffer: ").unwrap(); | ||
| 148 | print_buffer(&mut tx, core::ptr::addr_of!(SRC) as *const u32, 8); | ||
| 149 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 150 | |||
| 151 | tx.blocking_write(b"Destination Buffer (before): ").unwrap(); | ||
| 152 | print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); | ||
| 153 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 154 | |||
| 155 | tx.blocking_write(b"Configuring scatter-gather DMA with Embassy-style API...\r\n") | ||
| 156 | .unwrap(); | ||
| 157 | |||
| 158 | let dma_ch0 = DmaChannel::new(p.DMA_CH0); | ||
| 159 | |||
| 160 | // Configure scatter-gather transfer using direct TCD access: | ||
| 161 | // This sets up TCD0 and TCD1 in RAM, and loads TCD0 into the channel. | ||
| 162 | // TCD0 transfers first half (SRC[0..4] -> DST[0..4]), then loads TCD1. | ||
| 163 | // TCD1 transfers second half (SRC[4..8] -> DST[4..8]), last TCD. | ||
| 164 | unsafe { | ||
| 165 | let tcds = core::slice::from_raw_parts_mut(core::ptr::addr_of_mut!(TCD_POOL.0) as *mut Tcd, 2); | ||
| 166 | let src_ptr = core::ptr::addr_of!(SRC) as *const u32; | ||
| 167 | let dst_ptr = core::ptr::addr_of_mut!(DST) as *mut u32; | ||
| 168 | |||
| 169 | let num_tcds = 2usize; | ||
| 170 | let chunk_len = 4usize; // 8 / 2 | ||
| 171 | let chunk_bytes = (chunk_len * 4) as u32; | ||
| 172 | |||
| 173 | for i in 0..num_tcds { | ||
| 174 | let is_last = i == num_tcds - 1; | ||
| 175 | let next_tcd_addr = if is_last { | ||
| 176 | 0 // No next TCD | ||
| 177 | } else { | ||
| 178 | &tcds[i + 1] as *const _ as u32 | ||
| 179 | }; | ||
| 180 | |||
| 181 | tcds[i] = Tcd { | ||
| 182 | saddr: src_ptr.add(i * chunk_len) as u32, | ||
| 183 | soff: 4, | ||
| 184 | attr: 0x0202, // 32-bit src/dst | ||
| 185 | nbytes: chunk_bytes, | ||
| 186 | slast: 0, | ||
| 187 | daddr: dst_ptr.add(i * chunk_len) as u32, | ||
| 188 | doff: 4, | ||
| 189 | citer: 1, | ||
| 190 | dlast_sga: next_tcd_addr as i32, | ||
| 191 | // ESG (scatter/gather) for non-last, INTMAJOR for all | ||
| 192 | csr: if is_last { 0x0002 } else { 0x0012 }, | ||
| 193 | biter: 1, | ||
| 194 | }; | ||
| 195 | } | ||
| 196 | |||
| 197 | // Load TCD0 into hardware registers | ||
| 198 | dma_ch0.load_tcd(&tcds[0]); | ||
| 199 | } | ||
| 200 | |||
| 201 | tx.blocking_write(b"Triggering first half transfer...\r\n").unwrap(); | ||
| 202 | |||
| 203 | // Trigger first transfer (first half: SRC[0..4] -> DST[0..4]) | ||
| 204 | // TCD0 is currently loaded. | ||
| 205 | unsafe { | ||
| 206 | dma_ch0.trigger_start(); | ||
| 207 | } | ||
| 208 | |||
| 209 | // Wait for first half | ||
| 210 | while !TRANSFER_DONE.load(Ordering::Acquire) { | ||
| 211 | cortex_m::asm::nop(); | ||
| 212 | } | ||
| 213 | TRANSFER_DONE.store(false, Ordering::Release); | ||
| 214 | |||
| 215 | tx.blocking_write(b"First half transferred.\r\n").unwrap(); | ||
| 216 | tx.blocking_write(b"Triggering second half transfer...\r\n").unwrap(); | ||
| 217 | |||
| 218 | // Trigger second transfer (second half: SRC[4..8] -> DST[4..8]) | ||
| 219 | // TCD1 should have been loaded by the scatter/gather engine. | ||
| 220 | unsafe { | ||
| 221 | dma_ch0.trigger_start(); | ||
| 222 | } | ||
| 223 | |||
| 224 | // Wait for second half | ||
| 225 | while !TRANSFER_DONE.load(Ordering::Acquire) { | ||
| 226 | cortex_m::asm::nop(); | ||
| 227 | } | ||
| 228 | TRANSFER_DONE.store(false, Ordering::Release); | ||
| 229 | |||
| 230 | tx.blocking_write(b"Second half transferred.\r\n\r\n").unwrap(); | ||
| 231 | |||
| 232 | tx.blocking_write(b"EDMA scatter-gather transfer example finish.\r\n\r\n") | ||
| 233 | .unwrap(); | ||
| 234 | tx.blocking_write(b"Destination Buffer (after): ").unwrap(); | ||
| 235 | print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); | ||
| 236 | tx.blocking_write(b"\r\n\r\n").unwrap(); | ||
| 237 | |||
| 238 | // Verify: DST should match SRC | ||
| 239 | let mut mismatch = false; | ||
| 240 | unsafe { | ||
| 241 | let src_ptr = core::ptr::addr_of!(SRC) as *const u32; | ||
| 242 | let dst_ptr = core::ptr::addr_of!(DST) as *const u32; | ||
| 243 | for i in 0..8 { | ||
| 244 | if *src_ptr.add(i) != *dst_ptr.add(i) { | ||
| 245 | mismatch = true; | ||
| 246 | break; | ||
| 247 | } | ||
| 248 | } | ||
| 249 | } | ||
| 250 | |||
| 251 | if mismatch { | ||
| 252 | tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); | ||
| 253 | defmt::error!("FAIL: Mismatch detected!"); | ||
| 254 | } else { | ||
| 255 | tx.blocking_write(b"PASS: Data verified.\r\n").unwrap(); | ||
| 256 | defmt::info!("PASS: Data verified."); | ||
| 257 | } | ||
| 258 | |||
| 259 | loop { | ||
| 260 | cortex_m::asm::wfe(); | ||
| 261 | } | ||
| 262 | } | ||
diff --git a/examples/mcxa/src/bin/dma_scatter_gather_builder.rs b/examples/mcxa/src/bin/dma_scatter_gather_builder.rs new file mode 100644 index 000000000..e483bb81f --- /dev/null +++ b/examples/mcxa/src/bin/dma_scatter_gather_builder.rs | |||
| @@ -0,0 +1,231 @@ | |||
| 1 | //! DMA Scatter-Gather Builder example for MCXA276. | ||
| 2 | //! | ||
| 3 | //! This example demonstrates using the new `ScatterGatherBuilder` API for | ||
| 4 | //! chaining multiple DMA transfers with a type-safe builder pattern. | ||
| 5 | //! | ||
| 6 | //! # Features demonstrated: | ||
| 7 | //! - `ScatterGatherBuilder::new()` for creating a builder | ||
| 8 | //! - `add_transfer()` for adding memory-to-memory segments | ||
| 9 | //! - `build()` to start the chained transfer | ||
| 10 | //! - Automatic TCD linking and ESG bit management | ||
| 11 | //! | ||
| 12 | //! # Comparison with manual scatter-gather: | ||
| 13 | //! The manual approach (see `dma_scatter_gather.rs`) requires: | ||
| 14 | //! - Manual TCD pool allocation and alignment | ||
| 15 | //! - Manual CSR/ESG/INTMAJOR bit manipulation | ||
| 16 | //! - Manual dlast_sga address calculations | ||
| 17 | //! | ||
| 18 | //! The builder approach handles all of this automatically! | ||
| 19 | |||
| 20 | #![no_std] | ||
| 21 | #![no_main] | ||
| 22 | |||
| 23 | use embassy_executor::Spawner; | ||
| 24 | use embassy_mcxa::clocks::config::Div8; | ||
| 25 | use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaChannel, ScatterGatherBuilder}; | ||
| 26 | use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; | ||
| 27 | use embassy_mcxa::{bind_interrupts, pac}; | ||
| 28 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 29 | |||
| 30 | // Bind DMA channel 0 interrupt | ||
| 31 | bind_interrupts!(struct Irqs { | ||
| 32 | DMA_CH0 => DmaCh0InterruptHandler; | ||
| 33 | }); | ||
| 34 | |||
| 35 | // Source buffers (multiple segments) | ||
| 36 | static mut SRC1: [u32; 4] = [0x11111111, 0x22222222, 0x33333333, 0x44444444]; | ||
| 37 | static mut SRC2: [u32; 4] = [0xAAAAAAAA, 0xBBBBBBBB, 0xCCCCCCCC, 0xDDDDDDDD]; | ||
| 38 | static mut SRC3: [u32; 4] = [0x12345678, 0x9ABCDEF0, 0xFEDCBA98, 0x76543210]; | ||
| 39 | |||
| 40 | // Destination buffers (one per segment) | ||
| 41 | static mut DST1: [u32; 4] = [0; 4]; | ||
| 42 | static mut DST2: [u32; 4] = [0; 4]; | ||
| 43 | static mut DST3: [u32; 4] = [0; 4]; | ||
| 44 | |||
| 45 | /// Helper to write a u32 as hex to UART | ||
| 46 | fn write_hex(tx: &mut LpuartTx<'_, Blocking>, val: u32) { | ||
| 47 | const HEX: &[u8; 16] = b"0123456789ABCDEF"; | ||
| 48 | for i in (0..8).rev() { | ||
| 49 | let nibble = ((val >> (i * 4)) & 0xF) as usize; | ||
| 50 | tx.blocking_write(&[HEX[nibble]]).ok(); | ||
| 51 | } | ||
| 52 | } | ||
| 53 | |||
| 54 | /// Helper to print a buffer to UART | ||
| 55 | fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { | ||
| 56 | tx.blocking_write(b"[").ok(); | ||
| 57 | unsafe { | ||
| 58 | for i in 0..len { | ||
| 59 | write_hex(tx, *buf_ptr.add(i)); | ||
| 60 | if i < len - 1 { | ||
| 61 | tx.blocking_write(b", ").ok(); | ||
| 62 | } | ||
| 63 | } | ||
| 64 | } | ||
| 65 | tx.blocking_write(b"]").ok(); | ||
| 66 | } | ||
| 67 | |||
| 68 | #[embassy_executor::main] | ||
| 69 | async fn main(_spawner: Spawner) { | ||
| 70 | // Small delay to allow probe-rs to attach after reset | ||
| 71 | for _ in 0..100_000 { | ||
| 72 | cortex_m::asm::nop(); | ||
| 73 | } | ||
| 74 | |||
| 75 | let mut cfg = hal::config::Config::default(); | ||
| 76 | cfg.clock_cfg.sirc.fro_12m_enabled = true; | ||
| 77 | cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); | ||
| 78 | let p = hal::init(cfg); | ||
| 79 | |||
| 80 | defmt::info!("DMA Scatter-Gather Builder example starting..."); | ||
| 81 | |||
| 82 | // Enable DMA interrupt (DMA clock/reset/init is handled automatically by HAL) | ||
| 83 | unsafe { | ||
| 84 | cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); | ||
| 85 | } | ||
| 86 | |||
| 87 | // Create UART for debug output | ||
| 88 | let config = Config { | ||
| 89 | baudrate_bps: 115_200, | ||
| 90 | ..Default::default() | ||
| 91 | }; | ||
| 92 | |||
| 93 | let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); | ||
| 94 | let (mut tx, _rx) = lpuart.split(); | ||
| 95 | |||
| 96 | tx.blocking_write(b"DMA Scatter-Gather Builder Example\r\n").unwrap(); | ||
| 97 | tx.blocking_write(b"===================================\r\n\r\n") | ||
| 98 | .unwrap(); | ||
| 99 | |||
| 100 | // Show source buffers | ||
| 101 | tx.blocking_write(b"Source buffers:\r\n").unwrap(); | ||
| 102 | tx.blocking_write(b" SRC1: ").unwrap(); | ||
| 103 | print_buffer(&mut tx, core::ptr::addr_of!(SRC1) as *const u32, 4); | ||
| 104 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 105 | tx.blocking_write(b" SRC2: ").unwrap(); | ||
| 106 | print_buffer(&mut tx, core::ptr::addr_of!(SRC2) as *const u32, 4); | ||
| 107 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 108 | tx.blocking_write(b" SRC3: ").unwrap(); | ||
| 109 | print_buffer(&mut tx, core::ptr::addr_of!(SRC3) as *const u32, 4); | ||
| 110 | tx.blocking_write(b"\r\n\r\n").unwrap(); | ||
| 111 | |||
| 112 | tx.blocking_write(b"Destination buffers (before):\r\n").unwrap(); | ||
| 113 | tx.blocking_write(b" DST1: ").unwrap(); | ||
| 114 | print_buffer(&mut tx, core::ptr::addr_of!(DST1) as *const u32, 4); | ||
| 115 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 116 | tx.blocking_write(b" DST2: ").unwrap(); | ||
| 117 | print_buffer(&mut tx, core::ptr::addr_of!(DST2) as *const u32, 4); | ||
| 118 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 119 | tx.blocking_write(b" DST3: ").unwrap(); | ||
| 120 | print_buffer(&mut tx, core::ptr::addr_of!(DST3) as *const u32, 4); | ||
| 121 | tx.blocking_write(b"\r\n\r\n").unwrap(); | ||
| 122 | |||
| 123 | // Create DMA channel | ||
| 124 | let dma_ch0 = DmaChannel::new(p.DMA_CH0); | ||
| 125 | |||
| 126 | tx.blocking_write(b"Building scatter-gather chain with builder API...\r\n") | ||
| 127 | .unwrap(); | ||
| 128 | |||
| 129 | // ========================================================================= | ||
| 130 | // ScatterGatherBuilder API demonstration | ||
| 131 | // ========================================================================= | ||
| 132 | // | ||
| 133 | // The builder pattern makes scatter-gather transfers much easier: | ||
| 134 | // 1. Create a builder | ||
| 135 | // 2. Add transfer segments with add_transfer() | ||
| 136 | // 3. Call build() to start the entire chain | ||
| 137 | // No manual TCD manipulation required! | ||
| 138 | |||
| 139 | let mut builder = ScatterGatherBuilder::<u32>::new(); | ||
| 140 | |||
| 141 | // Add three transfer segments - the builder handles TCD linking automatically | ||
| 142 | unsafe { | ||
| 143 | let src1 = &*core::ptr::addr_of!(SRC1); | ||
| 144 | let dst1 = &mut *core::ptr::addr_of_mut!(DST1); | ||
| 145 | builder.add_transfer(src1, dst1); | ||
| 146 | } | ||
| 147 | |||
| 148 | unsafe { | ||
| 149 | let src2 = &*core::ptr::addr_of!(SRC2); | ||
| 150 | let dst2 = &mut *core::ptr::addr_of_mut!(DST2); | ||
| 151 | builder.add_transfer(src2, dst2); | ||
| 152 | } | ||
| 153 | |||
| 154 | unsafe { | ||
| 155 | let src3 = &*core::ptr::addr_of!(SRC3); | ||
| 156 | let dst3 = &mut *core::ptr::addr_of_mut!(DST3); | ||
| 157 | builder.add_transfer(src3, dst3); | ||
| 158 | } | ||
| 159 | |||
| 160 | tx.blocking_write(b"Added 3 transfer segments to chain.\r\n").unwrap(); | ||
| 161 | tx.blocking_write(b"Starting scatter-gather transfer with .await...\r\n\r\n") | ||
| 162 | .unwrap(); | ||
| 163 | |||
| 164 | // Build and execute the scatter-gather chain | ||
| 165 | // The build() method: | ||
| 166 | // - Links all TCDs together with ESG bit | ||
| 167 | // - Sets INTMAJOR on all TCDs | ||
| 168 | // - Loads the first TCD into hardware | ||
| 169 | // - Returns a Transfer future | ||
| 170 | unsafe { | ||
| 171 | let transfer = builder.build(&dma_ch0).expect("Failed to build scatter-gather"); | ||
| 172 | transfer.blocking_wait(); | ||
| 173 | } | ||
| 174 | |||
| 175 | tx.blocking_write(b"Scatter-gather transfer complete!\r\n\r\n").unwrap(); | ||
| 176 | |||
| 177 | // Show results | ||
| 178 | tx.blocking_write(b"Destination buffers (after):\r\n").unwrap(); | ||
| 179 | tx.blocking_write(b" DST1: ").unwrap(); | ||
| 180 | print_buffer(&mut tx, core::ptr::addr_of!(DST1) as *const u32, 4); | ||
| 181 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 182 | tx.blocking_write(b" DST2: ").unwrap(); | ||
| 183 | print_buffer(&mut tx, core::ptr::addr_of!(DST2) as *const u32, 4); | ||
| 184 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 185 | tx.blocking_write(b" DST3: ").unwrap(); | ||
| 186 | print_buffer(&mut tx, core::ptr::addr_of!(DST3) as *const u32, 4); | ||
| 187 | tx.blocking_write(b"\r\n\r\n").unwrap(); | ||
| 188 | |||
| 189 | // Verify all three segments | ||
| 190 | let mut all_ok = true; | ||
| 191 | unsafe { | ||
| 192 | let src1 = core::ptr::addr_of!(SRC1) as *const u32; | ||
| 193 | let dst1 = core::ptr::addr_of!(DST1) as *const u32; | ||
| 194 | for i in 0..4 { | ||
| 195 | if *src1.add(i) != *dst1.add(i) { | ||
| 196 | all_ok = false; | ||
| 197 | } | ||
| 198 | } | ||
| 199 | |||
| 200 | let src2 = core::ptr::addr_of!(SRC2) as *const u32; | ||
| 201 | let dst2 = core::ptr::addr_of!(DST2) as *const u32; | ||
| 202 | for i in 0..4 { | ||
| 203 | if *src2.add(i) != *dst2.add(i) { | ||
| 204 | all_ok = false; | ||
| 205 | } | ||
| 206 | } | ||
| 207 | |||
| 208 | let src3 = core::ptr::addr_of!(SRC3) as *const u32; | ||
| 209 | let dst3 = core::ptr::addr_of!(DST3) as *const u32; | ||
| 210 | for i in 0..4 { | ||
| 211 | if *src3.add(i) != *dst3.add(i) { | ||
| 212 | all_ok = false; | ||
| 213 | } | ||
| 214 | } | ||
| 215 | } | ||
| 216 | |||
| 217 | if all_ok { | ||
| 218 | tx.blocking_write(b"PASS: All segments verified!\r\n").unwrap(); | ||
| 219 | defmt::info!("PASS: All segments verified!"); | ||
| 220 | } else { | ||
| 221 | tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); | ||
| 222 | defmt::error!("FAIL: Mismatch detected!"); | ||
| 223 | } | ||
| 224 | |||
| 225 | tx.blocking_write(b"\r\n=== Scatter-Gather Builder example complete ===\r\n") | ||
| 226 | .unwrap(); | ||
| 227 | |||
| 228 | loop { | ||
| 229 | cortex_m::asm::wfe(); | ||
| 230 | } | ||
| 231 | } | ||
diff --git a/examples/mcxa/src/bin/dma_wrap_transfer.rs b/examples/mcxa/src/bin/dma_wrap_transfer.rs new file mode 100644 index 000000000..82936d9d0 --- /dev/null +++ b/examples/mcxa/src/bin/dma_wrap_transfer.rs | |||
| @@ -0,0 +1,222 @@ | |||
| 1 | //! DMA wrap transfer example for MCXA276. | ||
| 2 | //! | ||
| 3 | //! This example demonstrates using DMA with modulo addressing to wrap around | ||
| 4 | //! a source buffer, effectively repeating the source data in the destination. | ||
| 5 | //! | ||
| 6 | //! # Embassy-style features demonstrated: | ||
| 7 | //! - `DmaChannel::is_done()` and `clear_done()` helper methods | ||
| 8 | //! - No need to pass register block around | ||
| 9 | |||
| 10 | #![no_std] | ||
| 11 | #![no_main] | ||
| 12 | |||
| 13 | use embassy_executor::Spawner; | ||
| 14 | use embassy_mcxa::clocks::config::Div8; | ||
| 15 | use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaChannel}; | ||
| 16 | use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; | ||
| 17 | use embassy_mcxa::{bind_interrupts, pac}; | ||
| 18 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 19 | |||
| 20 | // Bind DMA channel 0 interrupt using Embassy-style macro | ||
| 21 | bind_interrupts!(struct Irqs { | ||
| 22 | DMA_CH0 => DmaCh0InterruptHandler; | ||
| 23 | }); | ||
| 24 | |||
| 25 | // Source buffer: 4 words (16 bytes), aligned to 16 bytes for modulo | ||
| 26 | #[repr(align(16))] | ||
| 27 | struct AlignedSrc([u32; 4]); | ||
| 28 | |||
| 29 | static mut SRC: AlignedSrc = AlignedSrc([0; 4]); | ||
| 30 | static mut DST: [u32; 8] = [0; 8]; | ||
| 31 | |||
| 32 | /// Helper to write a u32 as decimal ASCII to UART | ||
| 33 | fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { | ||
| 34 | let mut buf = [0u8; 10]; | ||
| 35 | let mut n = val; | ||
| 36 | let mut i = buf.len(); | ||
| 37 | |||
| 38 | if n == 0 { | ||
| 39 | tx.blocking_write(b"0").ok(); | ||
| 40 | return; | ||
| 41 | } | ||
| 42 | |||
| 43 | while n > 0 { | ||
| 44 | i -= 1; | ||
| 45 | buf[i] = b'0' + (n % 10) as u8; | ||
| 46 | n /= 10; | ||
| 47 | } | ||
| 48 | |||
| 49 | tx.blocking_write(&buf[i..]).ok(); | ||
| 50 | } | ||
| 51 | |||
| 52 | /// Helper to print a buffer to UART | ||
| 53 | fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { | ||
| 54 | tx.blocking_write(b"[").ok(); | ||
| 55 | unsafe { | ||
| 56 | for i in 0..len { | ||
| 57 | write_u32(tx, *buf_ptr.add(i)); | ||
| 58 | if i < len - 1 { | ||
| 59 | tx.blocking_write(b", ").ok(); | ||
| 60 | } | ||
| 61 | } | ||
| 62 | } | ||
| 63 | tx.blocking_write(b"]").ok(); | ||
| 64 | } | ||
| 65 | |||
| 66 | #[embassy_executor::main] | ||
| 67 | async fn main(_spawner: Spawner) { | ||
| 68 | // Small delay to allow probe-rs to attach after reset | ||
| 69 | for _ in 0..100_000 { | ||
| 70 | cortex_m::asm::nop(); | ||
| 71 | } | ||
| 72 | |||
| 73 | let mut cfg = hal::config::Config::default(); | ||
| 74 | cfg.clock_cfg.sirc.fro_12m_enabled = true; | ||
| 75 | cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); | ||
| 76 | let p = hal::init(cfg); | ||
| 77 | |||
| 78 | defmt::info!("DMA wrap transfer example starting..."); | ||
| 79 | |||
| 80 | // Enable DMA interrupt (DMA clock/reset/init is handled automatically by HAL) | ||
| 81 | unsafe { | ||
| 82 | cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); | ||
| 83 | } | ||
| 84 | |||
| 85 | let config = Config { | ||
| 86 | baudrate_bps: 115_200, | ||
| 87 | ..Default::default() | ||
| 88 | }; | ||
| 89 | |||
| 90 | let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); | ||
| 91 | let (mut tx, _rx) = lpuart.split(); | ||
| 92 | |||
| 93 | tx.blocking_write(b"EDMA wrap transfer example begin.\r\n\r\n").unwrap(); | ||
| 94 | |||
| 95 | // Initialize buffers | ||
| 96 | unsafe { | ||
| 97 | SRC.0 = [1, 2, 3, 4]; | ||
| 98 | DST = [0; 8]; | ||
| 99 | } | ||
| 100 | |||
| 101 | tx.blocking_write(b"Source Buffer: ").unwrap(); | ||
| 102 | print_buffer(&mut tx, unsafe { core::ptr::addr_of!(SRC.0) } as *const u32, 4); | ||
| 103 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 104 | |||
| 105 | tx.blocking_write(b"Destination Buffer (before): ").unwrap(); | ||
| 106 | print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); | ||
| 107 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 108 | |||
| 109 | tx.blocking_write(b"Configuring DMA with Embassy-style API...\r\n") | ||
| 110 | .unwrap(); | ||
| 111 | |||
| 112 | // Create DMA channel using Embassy-style API | ||
| 113 | let dma_ch0 = DmaChannel::new(p.DMA_CH0); | ||
| 114 | |||
| 115 | // Configure wrap transfer using direct TCD access: | ||
| 116 | // SRC is 16 bytes (4 * u32). We want to transfer 32 bytes (8 * u32). | ||
| 117 | // SRC modulo is 16 bytes (2^4 = 16) - wraps source address. | ||
| 118 | // DST modulo is 0 (disabled). | ||
| 119 | // This causes the source address to wrap around after 16 bytes, | ||
| 120 | // effectively repeating the source data. | ||
| 121 | unsafe { | ||
| 122 | let t = dma_ch0.tcd(); | ||
| 123 | |||
| 124 | // Reset channel state | ||
| 125 | t.ch_csr().write(|w| { | ||
| 126 | w.erq() | ||
| 127 | .disable() | ||
| 128 | .earq() | ||
| 129 | .disable() | ||
| 130 | .eei() | ||
| 131 | .no_error() | ||
| 132 | .ebw() | ||
| 133 | .disable() | ||
| 134 | .done() | ||
| 135 | .clear_bit_by_one() | ||
| 136 | }); | ||
| 137 | t.ch_es().write(|w| w.bits(0)); | ||
| 138 | t.ch_int().write(|w| w.int().clear_bit_by_one()); | ||
| 139 | |||
| 140 | // Source/destination addresses | ||
| 141 | t.tcd_saddr() | ||
| 142 | .write(|w| w.saddr().bits(core::ptr::addr_of!(SRC.0) as u32)); | ||
| 143 | t.tcd_daddr() | ||
| 144 | .write(|w| w.daddr().bits(core::ptr::addr_of_mut!(DST) as u32)); | ||
| 145 | |||
| 146 | // Offsets: both increment by 4 bytes | ||
| 147 | t.tcd_soff().write(|w| w.soff().bits(4)); | ||
| 148 | t.tcd_doff().write(|w| w.doff().bits(4)); | ||
| 149 | |||
| 150 | // Attributes: 32-bit transfers (size = 2) | ||
| 151 | // SMOD = 4 (2^4 = 16 byte modulo for source), DMOD = 0 (disabled) | ||
| 152 | t.tcd_attr().write(|w| { | ||
| 153 | w.ssize() | ||
| 154 | .bits(2) | ||
| 155 | .dsize() | ||
| 156 | .bits(2) | ||
| 157 | .smod() | ||
| 158 | .bits(4) // Source modulo: 2^4 = 16 bytes | ||
| 159 | .dmod() | ||
| 160 | .bits(0) // Dest modulo: disabled | ||
| 161 | }); | ||
| 162 | |||
| 163 | // Transfer 32 bytes total in one minor loop | ||
| 164 | let nbytes = 32u32; | ||
| 165 | t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(nbytes)); | ||
| 166 | |||
| 167 | // Source wraps via modulo, no adjustment needed | ||
| 168 | t.tcd_slast_sda().write(|w| w.slast_sda().bits(0)); | ||
| 169 | // Reset dest address after major loop | ||
| 170 | t.tcd_dlast_sga().write(|w| w.dlast_sga().bits(-(nbytes as i32) as u32)); | ||
| 171 | |||
| 172 | // Major loop count = 1 | ||
| 173 | t.tcd_biter_elinkno().write(|w| w.biter().bits(1)); | ||
| 174 | t.tcd_citer_elinkno().write(|w| w.citer().bits(1)); | ||
| 175 | |||
| 176 | // Enable interrupt on major loop completion | ||
| 177 | t.tcd_csr().write(|w| w.intmajor().set_bit()); | ||
| 178 | |||
| 179 | cortex_m::asm::dsb(); | ||
| 180 | |||
| 181 | tx.blocking_write(b"Triggering transfer...\r\n").unwrap(); | ||
| 182 | dma_ch0.trigger_start(); | ||
| 183 | } | ||
| 184 | |||
| 185 | // Wait for completion using channel helper method | ||
| 186 | while !dma_ch0.is_done() { | ||
| 187 | cortex_m::asm::nop(); | ||
| 188 | } | ||
| 189 | unsafe { | ||
| 190 | dma_ch0.clear_done(); | ||
| 191 | } | ||
| 192 | |||
| 193 | tx.blocking_write(b"\r\nEDMA wrap transfer example finish.\r\n\r\n") | ||
| 194 | .unwrap(); | ||
| 195 | tx.blocking_write(b"Destination Buffer (after): ").unwrap(); | ||
| 196 | print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); | ||
| 197 | tx.blocking_write(b"\r\n\r\n").unwrap(); | ||
| 198 | |||
| 199 | // Verify: DST should be [1, 2, 3, 4, 1, 2, 3, 4] | ||
| 200 | let expected = [1u32, 2, 3, 4, 1, 2, 3, 4]; | ||
| 201 | let mut mismatch = false; | ||
| 202 | unsafe { | ||
| 203 | for i in 0..8 { | ||
| 204 | if DST[i] != expected[i] { | ||
| 205 | mismatch = true; | ||
| 206 | break; | ||
| 207 | } | ||
| 208 | } | ||
| 209 | } | ||
| 210 | |||
| 211 | if mismatch { | ||
| 212 | tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); | ||
| 213 | defmt::error!("FAIL: Mismatch detected!"); | ||
| 214 | } else { | ||
| 215 | tx.blocking_write(b"PASS: Data verified.\r\n").unwrap(); | ||
| 216 | defmt::info!("PASS: Data verified."); | ||
| 217 | } | ||
| 218 | |||
| 219 | loop { | ||
| 220 | cortex_m::asm::wfe(); | ||
| 221 | } | ||
| 222 | } | ||
diff --git a/examples/mcxa/src/bin/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 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | use embassy_executor::Spawner; | ||
| 5 | use embassy_mcxa::clocks::config::Div8; | ||
| 6 | use hal::lpuart::{Blocking, Config, Lpuart}; | ||
| 7 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 8 | |||
| 9 | /// Simple helper to write a byte as hex to UART | ||
| 10 | fn write_hex_byte(uart: &mut Lpuart<'_, Blocking>, byte: u8) { | ||
| 11 | const HEX_DIGITS: &[u8] = b"0123456789ABCDEF"; | ||
| 12 | let _ = uart.write_byte(HEX_DIGITS[(byte >> 4) as usize]); | ||
| 13 | let _ = uart.write_byte(HEX_DIGITS[(byte & 0xF) as usize]); | ||
| 14 | } | ||
| 15 | |||
| 16 | #[embassy_executor::main] | ||
| 17 | async fn main(_spawner: Spawner) { | ||
| 18 | let mut cfg = hal::config::Config::default(); | ||
| 19 | cfg.clock_cfg.sirc.fro_12m_enabled = true; | ||
| 20 | cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); | ||
| 21 | let p = hal::init(cfg); | ||
| 22 | |||
| 23 | defmt::info!("boot"); | ||
| 24 | |||
| 25 | // Create UART configuration | ||
| 26 | let config = Config { | ||
| 27 | baudrate_bps: 115_200, | ||
| 28 | ..Default::default() | ||
| 29 | }; | ||
| 30 | |||
| 31 | // Create UART instance using LPUART2 with P2_2 as TX and P2_3 as RX | ||
| 32 | let mut uart = Lpuart::new_blocking( | ||
| 33 | p.LPUART2, // Peripheral | ||
| 34 | p.P2_2, // TX pin | ||
| 35 | p.P2_3, // RX pin | ||
| 36 | config, | ||
| 37 | ) | ||
| 38 | .unwrap(); | ||
| 39 | |||
| 40 | // Print welcome message before any async delays to guarantee early console output | ||
| 41 | uart.write_str_blocking("\r\n=== MCXA276 UART Echo Demo ===\r\n"); | ||
| 42 | uart.write_str_blocking("Available commands:\r\n"); | ||
| 43 | uart.write_str_blocking(" help - Show this help\r\n"); | ||
| 44 | uart.write_str_blocking(" echo <text> - Echo back the text\r\n"); | ||
| 45 | uart.write_str_blocking(" hex <byte> - Display byte in hex (0-255)\r\n"); | ||
| 46 | uart.write_str_blocking("Type a command: "); | ||
| 47 | |||
| 48 | let mut buffer = [0u8; 64]; | ||
| 49 | let mut buf_idx = 0; | ||
| 50 | |||
| 51 | loop { | ||
| 52 | // Read a byte from UART | ||
| 53 | let byte = uart.read_byte_blocking(); | ||
| 54 | |||
| 55 | // Echo the character back | ||
| 56 | if byte == b'\r' || byte == b'\n' { | ||
| 57 | // Enter pressed - process command | ||
| 58 | uart.write_str_blocking("\r\n"); | ||
| 59 | |||
| 60 | if buf_idx > 0 { | ||
| 61 | let command = &buffer[0..buf_idx]; | ||
| 62 | |||
| 63 | if command == b"help" { | ||
| 64 | uart.write_str_blocking("Available commands:\r\n"); | ||
| 65 | uart.write_str_blocking(" help - Show this help\r\n"); | ||
| 66 | uart.write_str_blocking(" echo <text> - Echo back the text\r\n"); | ||
| 67 | uart.write_str_blocking(" hex <byte> - Display byte in hex (0-255)\r\n"); | ||
| 68 | } else if command.starts_with(b"echo ") && command.len() > 5 { | ||
| 69 | uart.write_str_blocking("Echo: "); | ||
| 70 | uart.write_str_blocking(core::str::from_utf8(&command[5..]).unwrap_or("")); | ||
| 71 | uart.write_str_blocking("\r\n"); | ||
| 72 | } else if command.starts_with(b"hex ") && command.len() > 4 { | ||
| 73 | // Parse the byte value | ||
| 74 | let num_str = &command[4..]; | ||
| 75 | if let Ok(num) = parse_u8(num_str) { | ||
| 76 | uart.write_str_blocking("Hex: 0x"); | ||
| 77 | write_hex_byte(&mut uart, num); | ||
| 78 | uart.write_str_blocking("\r\n"); | ||
| 79 | } else { | ||
| 80 | uart.write_str_blocking("Invalid number for hex command\r\n"); | ||
| 81 | } | ||
| 82 | } else if !command.is_empty() { | ||
| 83 | uart.write_str_blocking("Unknown command: "); | ||
| 84 | uart.write_str_blocking(core::str::from_utf8(command).unwrap_or("")); | ||
| 85 | uart.write_str_blocking("\r\n"); | ||
| 86 | } | ||
| 87 | } | ||
| 88 | |||
| 89 | // Reset buffer and prompt | ||
| 90 | buf_idx = 0; | ||
| 91 | uart.write_str_blocking("Type a command: "); | ||
| 92 | } else if byte == 8 || byte == 127 { | ||
| 93 | // Backspace | ||
| 94 | if buf_idx > 0 { | ||
| 95 | buf_idx -= 1; | ||
| 96 | uart.write_str_blocking("\x08 \x08"); // Erase character | ||
| 97 | } | ||
| 98 | } else if buf_idx < buffer.len() - 1 { | ||
| 99 | // Regular character | ||
| 100 | buffer[buf_idx] = byte; | ||
| 101 | buf_idx += 1; | ||
| 102 | let _ = uart.write_byte(byte); | ||
| 103 | } | ||
| 104 | } | ||
| 105 | } | ||
| 106 | |||
| 107 | /// Simple parser for u8 from ASCII bytes | ||
| 108 | fn parse_u8(bytes: &[u8]) -> Result<u8, ()> { | ||
| 109 | let mut result = 0u8; | ||
| 110 | for &b in bytes { | ||
| 111 | if b.is_ascii_digit() { | ||
| 112 | result = result.checked_mul(10).ok_or(())?; | ||
| 113 | result = result.checked_add(b - b'0').ok_or(())?; | ||
| 114 | } else { | ||
| 115 | return Err(()); | ||
| 116 | } | ||
| 117 | } | ||
| 118 | Ok(result) | ||
| 119 | } | ||
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 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | use embassy_executor::Spawner; | ||
| 5 | use embassy_time::Timer; | ||
| 6 | use hal::clocks::config::Div8; | ||
| 7 | use hal::config::Config; | ||
| 8 | use hal::i2c::controller::{self, I2c, Speed}; | ||
| 9 | use tmp108::Tmp108; | ||
| 10 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 11 | |||
| 12 | #[embassy_executor::main] | ||
| 13 | async fn main(_spawner: Spawner) { | ||
| 14 | let mut config = Config::default(); | ||
| 15 | config.clock_cfg.sirc.fro_lf_div = Div8::from_divisor(1); | ||
| 16 | |||
| 17 | let p = hal::init(config); | ||
| 18 | |||
| 19 | defmt::info!("I2C example"); | ||
| 20 | |||
| 21 | let mut config = controller::Config::default(); | ||
| 22 | config.speed = Speed::Standard; | ||
| 23 | let i2c = I2c::new_blocking(p.LPI2C3, p.P3_27, p.P3_28, config).unwrap(); | ||
| 24 | let mut tmp = Tmp108::new_with_a0_gnd(i2c); | ||
| 25 | |||
| 26 | loop { | ||
| 27 | let temperature = tmp.temperature().unwrap(); | ||
| 28 | defmt::info!("Temperature: {}C", temperature); | ||
| 29 | Timer::after_secs(1).await; | ||
| 30 | } | ||
| 31 | } | ||
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 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | use embassy_executor::Spawner; | ||
| 5 | use embassy_mcxa::gpio::Pull; | ||
| 6 | use embassy_mcxa::Input; | ||
| 7 | use embassy_time::Timer; | ||
| 8 | use hal::clocks::config::Div8; | ||
| 9 | use hal::config::Config; | ||
| 10 | use hal::i2c::controller::{self, I2c, Speed}; | ||
| 11 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 12 | |||
| 13 | #[embassy_executor::main] | ||
| 14 | async fn main(_spawner: Spawner) { | ||
| 15 | let mut config = Config::default(); | ||
| 16 | config.clock_cfg.sirc.fro_lf_div = Div8::from_divisor(1); | ||
| 17 | |||
| 18 | let p = hal::init(config); | ||
| 19 | |||
| 20 | defmt::info!("I2C example"); | ||
| 21 | |||
| 22 | let mut config = controller::Config::default(); | ||
| 23 | config.speed = Speed::Standard; | ||
| 24 | |||
| 25 | // Note: P0_2 is connected to P1_8 on the FRDM_MCXA276 via a resistor, and | ||
| 26 | // defaults to SWO on the debug peripheral. Explicitly make it a high-z | ||
| 27 | // input. | ||
| 28 | let _pin = Input::new(p.P0_2, Pull::Disabled); | ||
| 29 | let mut i2c = I2c::new_blocking(p.LPI2C2, p.P1_9, p.P1_8, config).unwrap(); | ||
| 30 | |||
| 31 | for addr in 0x01..=0x7f { | ||
| 32 | let result = i2c.blocking_write(addr, &[]); | ||
| 33 | if result.is_ok() { | ||
| 34 | defmt::info!("Device found at addr {:02x}", addr); | ||
| 35 | } | ||
| 36 | } | ||
| 37 | |||
| 38 | loop { | ||
| 39 | Timer::after_secs(10).await; | ||
| 40 | } | ||
| 41 | } | ||
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 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | use embassy_executor::Spawner; | ||
| 5 | use embassy_mcxa::clocks::config::Div8; | ||
| 6 | use embassy_mcxa::lpuart::buffered::BufferedLpuart; | ||
| 7 | use embassy_mcxa::lpuart::Config; | ||
| 8 | use embassy_mcxa::{bind_interrupts, lpuart}; | ||
| 9 | use embedded_io_async::Write; | ||
| 10 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 11 | |||
| 12 | // Bind OS_EVENT for timers plus LPUART2 IRQ for the buffered driver | ||
| 13 | bind_interrupts!(struct Irqs { | ||
| 14 | LPUART2 => lpuart::buffered::BufferedInterruptHandler::<hal::peripherals::LPUART2>; | ||
| 15 | }); | ||
| 16 | |||
| 17 | #[embassy_executor::main] | ||
| 18 | async fn main(_spawner: Spawner) { | ||
| 19 | let mut cfg = hal::config::Config::default(); | ||
| 20 | cfg.clock_cfg.sirc.fro_12m_enabled = true; | ||
| 21 | cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); | ||
| 22 | let p = hal::init(cfg); | ||
| 23 | |||
| 24 | // Configure NVIC for LPUART2 | ||
| 25 | hal::interrupt::LPUART2.configure_for_uart(hal::interrupt::Priority::P3); | ||
| 26 | |||
| 27 | // UART configuration (enable both TX and RX) | ||
| 28 | let config = Config { | ||
| 29 | baudrate_bps: 115_200, | ||
| 30 | rx_fifo_watermark: 0, | ||
| 31 | tx_fifo_watermark: 0, | ||
| 32 | ..Default::default() | ||
| 33 | }; | ||
| 34 | |||
| 35 | let mut tx_buf = [0u8; 256]; | ||
| 36 | let mut rx_buf = [0u8; 256]; | ||
| 37 | |||
| 38 | // Create a buffered LPUART2 instance with both TX and RX | ||
| 39 | let mut uart = BufferedLpuart::new( | ||
| 40 | p.LPUART2, | ||
| 41 | p.P2_2, // TX pin | ||
| 42 | p.P2_3, // RX pin | ||
| 43 | Irqs, | ||
| 44 | &mut tx_buf, | ||
| 45 | &mut rx_buf, | ||
| 46 | config, | ||
| 47 | ) | ||
| 48 | .unwrap(); | ||
| 49 | |||
| 50 | // Split into TX and RX parts | ||
| 51 | let (tx, rx) = uart.split_ref(); | ||
| 52 | |||
| 53 | tx.write(b"Hello buffered LPUART.\r\n").await.unwrap(); | ||
| 54 | tx.write(b"Type characters to echo them back.\r\n").await.unwrap(); | ||
| 55 | |||
| 56 | // Echo loop | ||
| 57 | let mut buf = [0u8; 4]; | ||
| 58 | loop { | ||
| 59 | let used = rx.read(&mut buf).await.unwrap(); | ||
| 60 | tx.write_all(&buf[..used]).await.unwrap(); | ||
| 61 | } | ||
| 62 | } | ||
diff --git a/examples/mcxa/src/bin/lpuart_dma.rs b/examples/mcxa/src/bin/lpuart_dma.rs new file mode 100644 index 000000000..5497f8646 --- /dev/null +++ b/examples/mcxa/src/bin/lpuart_dma.rs | |||
| @@ -0,0 +1,81 @@ | |||
| 1 | //! LPUART DMA example for MCXA276. | ||
| 2 | //! | ||
| 3 | //! This example demonstrates using DMA for UART TX and RX operations. | ||
| 4 | //! It sends a message using DMA, then waits for 16 characters to be received | ||
| 5 | //! via DMA and echoes them back. | ||
| 6 | //! | ||
| 7 | //! The DMA request sources are automatically derived from the LPUART instance type. | ||
| 8 | //! DMA clock/reset/init is handled automatically by the HAL. | ||
| 9 | |||
| 10 | #![no_std] | ||
| 11 | #![no_main] | ||
| 12 | |||
| 13 | use embassy_executor::Spawner; | ||
| 14 | use embassy_mcxa::clocks::config::Div8; | ||
| 15 | use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaCh1InterruptHandler}; | ||
| 16 | use embassy_mcxa::lpuart::{Config, LpuartDma}; | ||
| 17 | use embassy_mcxa::{bind_interrupts, pac}; | ||
| 18 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 19 | |||
| 20 | // Bind DMA channel interrupts using Embassy-style macro | ||
| 21 | bind_interrupts!(struct Irqs { | ||
| 22 | DMA_CH0 => DmaCh0InterruptHandler; | ||
| 23 | DMA_CH1 => DmaCh1InterruptHandler; | ||
| 24 | }); | ||
| 25 | |||
| 26 | #[embassy_executor::main] | ||
| 27 | async fn main(_spawner: Spawner) { | ||
| 28 | let mut cfg = hal::config::Config::default(); | ||
| 29 | cfg.clock_cfg.sirc.fro_12m_enabled = true; | ||
| 30 | cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); | ||
| 31 | let p = hal::init(cfg); | ||
| 32 | |||
| 33 | defmt::info!("LPUART DMA example starting..."); | ||
| 34 | |||
| 35 | // Enable DMA interrupts (per-channel, as needed) | ||
| 36 | unsafe { | ||
| 37 | cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); | ||
| 38 | cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH1); | ||
| 39 | } | ||
| 40 | |||
| 41 | // Create UART configuration | ||
| 42 | let config = Config { | ||
| 43 | baudrate_bps: 115_200, | ||
| 44 | ..Default::default() | ||
| 45 | }; | ||
| 46 | |||
| 47 | // Create UART instance with DMA channels | ||
| 48 | let mut lpuart = LpuartDma::new( | ||
| 49 | p.LPUART2, p.P2_2, // TX pin | ||
| 50 | p.P2_3, // RX pin | ||
| 51 | p.DMA_CH0, // TX DMA channel | ||
| 52 | p.DMA_CH1, // RX DMA channel | ||
| 53 | config, | ||
| 54 | ) | ||
| 55 | .unwrap(); | ||
| 56 | |||
| 57 | // Send a message using DMA (DMA request source is automatically derived from LPUART2) | ||
| 58 | let tx_msg = b"Hello from LPUART2 DMA TX!\r\n"; | ||
| 59 | lpuart.write_dma(tx_msg).await.unwrap(); | ||
| 60 | |||
| 61 | defmt::info!("TX DMA complete"); | ||
| 62 | |||
| 63 | // Send prompt | ||
| 64 | let prompt = b"Type 16 characters to echo via DMA:\r\n"; | ||
| 65 | lpuart.write_dma(prompt).await.unwrap(); | ||
| 66 | |||
| 67 | // Receive 16 characters using DMA | ||
| 68 | let mut rx_buf = [0u8; 16]; | ||
| 69 | lpuart.read_dma(&mut rx_buf).await.unwrap(); | ||
| 70 | |||
| 71 | defmt::info!("RX DMA complete"); | ||
| 72 | |||
| 73 | // Echo back the received data | ||
| 74 | let echo_prefix = b"\r\nReceived: "; | ||
| 75 | lpuart.write_dma(echo_prefix).await.unwrap(); | ||
| 76 | lpuart.write_dma(&rx_buf).await.unwrap(); | ||
| 77 | let done_msg = b"\r\nDone!\r\n"; | ||
| 78 | lpuart.write_dma(done_msg).await.unwrap(); | ||
| 79 | |||
| 80 | defmt::info!("Example complete"); | ||
| 81 | } | ||
diff --git a/examples/mcxa/src/bin/lpuart_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 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | use embassy_executor::Spawner; | ||
| 5 | use embassy_mcxa::clocks::config::Div8; | ||
| 6 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 7 | |||
| 8 | use crate::hal::lpuart::{Config, Lpuart}; | ||
| 9 | |||
| 10 | #[embassy_executor::main] | ||
| 11 | async fn main(_spawner: Spawner) { | ||
| 12 | let mut cfg = hal::config::Config::default(); | ||
| 13 | cfg.clock_cfg.sirc.fro_12m_enabled = true; | ||
| 14 | cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); | ||
| 15 | let p = hal::init(cfg); | ||
| 16 | |||
| 17 | defmt::info!("boot"); | ||
| 18 | |||
| 19 | // Create UART configuration | ||
| 20 | let config = Config { | ||
| 21 | baudrate_bps: 115_200, | ||
| 22 | ..Default::default() | ||
| 23 | }; | ||
| 24 | |||
| 25 | // Create UART instance using LPUART2 with P2_2 as TX and P2_3 as RX | ||
| 26 | let lpuart = Lpuart::new_blocking( | ||
| 27 | p.LPUART2, // Peripheral | ||
| 28 | p.P2_2, // TX pin | ||
| 29 | p.P2_3, // RX pin | ||
| 30 | config, | ||
| 31 | ) | ||
| 32 | .unwrap(); | ||
| 33 | |||
| 34 | // Split into separate TX and RX parts | ||
| 35 | let (mut tx, mut rx) = lpuart.split(); | ||
| 36 | |||
| 37 | // Write hello messages | ||
| 38 | tx.blocking_write(b"Hello world.\r\n").unwrap(); | ||
| 39 | tx.blocking_write(b"Echoing. Type characters...\r\n").unwrap(); | ||
| 40 | |||
| 41 | // Echo loop | ||
| 42 | loop { | ||
| 43 | let mut buf = [0u8; 1]; | ||
| 44 | rx.blocking_read(&mut buf).unwrap(); | ||
| 45 | tx.blocking_write(&buf).unwrap(); | ||
| 46 | } | ||
| 47 | } | ||
diff --git a/examples/mcxa/src/bin/lpuart_ring_buffer.rs b/examples/mcxa/src/bin/lpuart_ring_buffer.rs new file mode 100644 index 000000000..1d1a51970 --- /dev/null +++ b/examples/mcxa/src/bin/lpuart_ring_buffer.rs | |||
| @@ -0,0 +1,130 @@ | |||
| 1 | //! LPUART Ring Buffer DMA example for MCXA276. | ||
| 2 | //! | ||
| 3 | //! This example demonstrates using the high-level `LpuartRxDma::setup_ring_buffer()` | ||
| 4 | //! API for continuous circular DMA reception from a UART peripheral. | ||
| 5 | //! | ||
| 6 | //! # Features demonstrated: | ||
| 7 | //! - `LpuartRxDma::setup_ring_buffer()` for continuous peripheral-to-memory DMA | ||
| 8 | //! - `RingBuffer` for async reading of received data | ||
| 9 | //! - Handling of potential overrun conditions | ||
| 10 | //! - Half-transfer and complete-transfer interrupts for timely wakeups | ||
| 11 | //! | ||
| 12 | //! # How it works: | ||
| 13 | //! 1. Create an `LpuartRxDma` driver with a DMA channel | ||
| 14 | //! 2. Call `setup_ring_buffer()` which handles all low-level DMA configuration | ||
| 15 | //! 3. Application asynchronously reads data as it arrives via `ring_buf.read()` | ||
| 16 | //! 4. Both half-transfer and complete-transfer interrupts wake the reader | ||
| 17 | |||
| 18 | #![no_std] | ||
| 19 | #![no_main] | ||
| 20 | |||
| 21 | use embassy_executor::Spawner; | ||
| 22 | use embassy_mcxa::bind_interrupts; | ||
| 23 | use embassy_mcxa::clocks::config::Div8; | ||
| 24 | use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaCh1InterruptHandler}; | ||
| 25 | use embassy_mcxa::lpuart::{Config, LpuartDma, LpuartTxDma}; | ||
| 26 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 27 | |||
| 28 | // Bind DMA channel interrupts | ||
| 29 | bind_interrupts!(struct Irqs { | ||
| 30 | DMA_CH0 => DmaCh0InterruptHandler; | ||
| 31 | DMA_CH1 => DmaCh1InterruptHandler; | ||
| 32 | }); | ||
| 33 | |||
| 34 | // Ring buffer for RX - power of 2 is ideal for modulo efficiency | ||
| 35 | static mut RX_RING_BUFFER: [u8; 64] = [0; 64]; | ||
| 36 | |||
| 37 | /// Helper to write a byte as hex to UART | ||
| 38 | fn write_hex<T: embassy_mcxa::lpuart::Instance, C: embassy_mcxa::dma::Channel>( | ||
| 39 | tx: &mut LpuartTxDma<'_, T, C>, | ||
| 40 | byte: u8, | ||
| 41 | ) { | ||
| 42 | const HEX: &[u8; 16] = b"0123456789ABCDEF"; | ||
| 43 | let buf = [HEX[(byte >> 4) as usize], HEX[(byte & 0x0F) as usize]]; | ||
| 44 | tx.blocking_write(&buf).ok(); | ||
| 45 | } | ||
| 46 | |||
| 47 | #[embassy_executor::main] | ||
| 48 | async fn main(_spawner: Spawner) { | ||
| 49 | // Small delay to allow probe-rs to attach after reset | ||
| 50 | for _ in 0..100_000 { | ||
| 51 | cortex_m::asm::nop(); | ||
| 52 | } | ||
| 53 | |||
| 54 | let mut cfg = hal::config::Config::default(); | ||
| 55 | cfg.clock_cfg.sirc.fro_12m_enabled = true; | ||
| 56 | cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); | ||
| 57 | let p = hal::init(cfg); | ||
| 58 | |||
| 59 | defmt::info!("LPUART Ring Buffer DMA example starting..."); | ||
| 60 | |||
| 61 | // Create UART configuration | ||
| 62 | let config = Config { | ||
| 63 | baudrate_bps: 115_200, | ||
| 64 | ..Default::default() | ||
| 65 | }; | ||
| 66 | |||
| 67 | // Create LPUART with DMA support for both TX and RX, then split | ||
| 68 | // This is the proper Embassy pattern - create once, split into TX and RX | ||
| 69 | let lpuart = LpuartDma::new(p.LPUART2, p.P2_2, p.P2_3, p.DMA_CH1, p.DMA_CH0, config).unwrap(); | ||
| 70 | let (mut tx, rx) = lpuart.split(); | ||
| 71 | |||
| 72 | tx.blocking_write(b"LPUART Ring Buffer DMA Example\r\n").unwrap(); | ||
| 73 | tx.blocking_write(b"==============================\r\n\r\n").unwrap(); | ||
| 74 | |||
| 75 | tx.blocking_write(b"Setting up circular DMA for UART RX...\r\n") | ||
| 76 | .unwrap(); | ||
| 77 | |||
| 78 | // Set up the ring buffer with circular DMA | ||
| 79 | // The HAL handles: DMA request source, RDMAE enable, circular transfer config, NVIC enable | ||
| 80 | let ring_buf = unsafe { | ||
| 81 | let buf = &mut *core::ptr::addr_of_mut!(RX_RING_BUFFER); | ||
| 82 | rx.setup_ring_buffer(buf) | ||
| 83 | }; | ||
| 84 | |||
| 85 | // Enable DMA requests to start continuous reception | ||
| 86 | unsafe { | ||
| 87 | rx.enable_dma_request(); | ||
| 88 | } | ||
| 89 | |||
| 90 | tx.blocking_write(b"Ring buffer ready! Type characters to see them echoed.\r\n") | ||
| 91 | .unwrap(); | ||
| 92 | tx.blocking_write(b"The DMA continuously receives in the background.\r\n\r\n") | ||
| 93 | .unwrap(); | ||
| 94 | |||
| 95 | // Main loop: read from ring buffer and echo back | ||
| 96 | let mut read_buf = [0u8; 16]; | ||
| 97 | let mut total_received: usize = 0; | ||
| 98 | |||
| 99 | loop { | ||
| 100 | // Async read - waits until data is available | ||
| 101 | match ring_buf.read(&mut read_buf).await { | ||
| 102 | Ok(n) if n > 0 => { | ||
| 103 | total_received += n; | ||
| 104 | |||
| 105 | // Echo back what we received | ||
| 106 | tx.blocking_write(b"RX[").unwrap(); | ||
| 107 | for (i, &byte) in read_buf.iter().enumerate().take(n) { | ||
| 108 | write_hex(&mut tx, byte); | ||
| 109 | if i < n - 1 { | ||
| 110 | tx.blocking_write(b" ").unwrap(); | ||
| 111 | } | ||
| 112 | } | ||
| 113 | tx.blocking_write(b"]: ").unwrap(); | ||
| 114 | tx.blocking_write(&read_buf[..n]).unwrap(); | ||
| 115 | tx.blocking_write(b"\r\n").unwrap(); | ||
| 116 | |||
| 117 | defmt::info!("Received {} bytes, total: {}", n, total_received); | ||
| 118 | } | ||
| 119 | Ok(_) => { | ||
| 120 | // No data, shouldn't happen with async read | ||
| 121 | } | ||
| 122 | Err(_) => { | ||
| 123 | // Overrun detected | ||
| 124 | tx.blocking_write(b"ERROR: Ring buffer overrun!\r\n").unwrap(); | ||
| 125 | defmt::error!("Ring buffer overrun!"); | ||
| 126 | ring_buf.clear(); | ||
| 127 | } | ||
| 128 | } | ||
| 129 | } | ||
| 130 | } | ||
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 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | use embassy_executor::Spawner; | ||
| 5 | use embassy_mcxa as hal; | ||
| 6 | use hal::rtc::{RtcDateTime, RtcInterruptEnable}; | ||
| 7 | use hal::InterruptExt; | ||
| 8 | |||
| 9 | type MyRtc = hal::rtc::Rtc<'static, hal::rtc::Rtc0>; | ||
| 10 | |||
| 11 | use embassy_mcxa::bind_interrupts; | ||
| 12 | use {defmt_rtt as _, panic_probe as _}; | ||
| 13 | |||
| 14 | bind_interrupts!(struct Irqs { | ||
| 15 | RTC => hal::rtc::RtcHandler; | ||
| 16 | }); | ||
| 17 | |||
| 18 | #[used] | ||
| 19 | #[no_mangle] | ||
| 20 | static KEEP_RTC: unsafe extern "C" fn() = RTC; | ||
| 21 | |||
| 22 | #[embassy_executor::main] | ||
| 23 | async fn main(_spawner: Spawner) { | ||
| 24 | let p = hal::init(hal::config::Config::default()); | ||
| 25 | |||
| 26 | defmt::info!("=== RTC Alarm Example ==="); | ||
| 27 | |||
| 28 | let rtc_config = hal::rtc::get_default_config(); | ||
| 29 | |||
| 30 | let rtc = MyRtc::new(p.RTC0, rtc_config); | ||
| 31 | |||
| 32 | let now = RtcDateTime { | ||
| 33 | year: 2025, | ||
| 34 | month: 10, | ||
| 35 | day: 15, | ||
| 36 | hour: 14, | ||
| 37 | minute: 30, | ||
| 38 | second: 0, | ||
| 39 | }; | ||
| 40 | |||
| 41 | rtc.stop(); | ||
| 42 | |||
| 43 | defmt::info!("Time set to: 2025-10-15 14:30:00"); | ||
| 44 | rtc.set_datetime(now); | ||
| 45 | |||
| 46 | let mut alarm = now; | ||
| 47 | alarm.second += 10; | ||
| 48 | |||
| 49 | rtc.set_alarm(alarm); | ||
| 50 | defmt::info!("Alarm set for: 2025-10-15 14:30:10 (+10 seconds)"); | ||
| 51 | |||
| 52 | rtc.set_interrupt(RtcInterruptEnable::RTC_ALARM_INTERRUPT_ENABLE); | ||
| 53 | |||
| 54 | unsafe { | ||
| 55 | hal::interrupt::RTC.enable(); | ||
| 56 | } | ||
| 57 | |||
| 58 | unsafe { | ||
| 59 | cortex_m::interrupt::enable(); | ||
| 60 | } | ||
| 61 | |||
| 62 | rtc.start(); | ||
| 63 | |||
| 64 | defmt::info!("RTC started, waiting for alarm..."); | ||
| 65 | |||
| 66 | loop { | ||
| 67 | if rtc.is_alarm_triggered() { | ||
| 68 | defmt::info!("*** ALARM TRIGGERED! ***"); | ||
| 69 | break; | ||
| 70 | } | ||
| 71 | } | ||
| 72 | |||
| 73 | defmt::info!("Example complete - Test PASSED!"); | ||
| 74 | } | ||
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 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![allow(clippy::missing_safety_doc)] | ||
| 3 | |||
| 4 | //! Shared board-specific helpers for the FRDM-MCXA276 examples. | ||
| 5 | //! These live with the examples so the HAL stays generic. | ||
| 6 | |||
| 7 | use hal::{clocks, pins}; | ||
| 8 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | ||
| 9 | |||
| 10 | /// Initialize clocks and pin muxing for ADC. | ||
| 11 | pub unsafe fn init_adc_pins() { | ||
| 12 | // NOTE: Lpuart has been updated to properly enable + reset its own clocks. | ||
| 13 | // GPIO has not. | ||
| 14 | _ = clocks::enable_and_reset::<hal::peripherals::PORT1>(&clocks::periph_helpers::NoConfig); | ||
| 15 | pins::configure_adc_pins(); | ||
| 16 | } | ||
