From 2e70c376c884a64fc931406350fecb6c0314dcf0 Mon Sep 17 00:00:00 2001 From: xoviat Date: Tue, 25 Nov 2025 10:14:55 -0600 Subject: timer: add writable ring buffer --- examples/stm32f7/src/bin/pwm.rs | 61 ++++++++++++ examples/stm32f7/src/bin/pwm_ringbuffer.rs | 153 +++++++++++++++++++++++++++++ 2 files changed, 214 insertions(+) create mode 100644 examples/stm32f7/src/bin/pwm.rs create mode 100644 examples/stm32f7/src/bin/pwm_ringbuffer.rs (limited to 'examples') diff --git a/examples/stm32f7/src/bin/pwm.rs b/examples/stm32f7/src/bin/pwm.rs new file mode 100644 index 000000000..b071eb597 --- /dev/null +++ b/examples/stm32f7/src/bin/pwm.rs @@ -0,0 +1,61 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::Config; +use embassy_stm32::gpio::OutputType; +use embassy_stm32::time::{Hertz, mhz}; +use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +// If you are trying this and your USB device doesn't connect, the most +// common issues are the RCC config and vbus_detection +// +// See https://embassy.dev/book/#_the_usb_examples_are_not_working_on_my_board_is_there_anything_else_i_need_to_configure +// for more information. +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello World!"); + + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::Bypass, + }); + config.rcc.pll_src = PllSource::HSE; + config.rcc.pll = Some(Pll { + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL200, + divp: Some(PllPDiv::DIV2), // 8mhz / 4 * 200 / 2 = 200Mhz + divq: Some(PllQDiv::DIV4), // 8mhz / 4 * 200 / 4 = 100Mhz + divr: None, + }); + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV4; + config.rcc.apb2_pre = APBPrescaler::DIV2; + config.rcc.sys = Sysclk::PLL1_P; + } + let p = embassy_stm32::init(config); + let ch1_pin = PwmPin::new(p.PE9, OutputType::PushPull); + let mut pwm = SimplePwm::new(p.TIM1, Some(ch1_pin), None, None, None, mhz(1), Default::default()); + let mut ch1 = pwm.ch1(); + ch1.enable(); + + info!("PWM initialized"); + info!("PWM max duty {}", ch1.max_duty_cycle()); + + loop { + ch1.set_duty_cycle_fully_off(); + Timer::after_millis(300).await; + ch1.set_duty_cycle_fraction(1, 4); + Timer::after_millis(300).await; + ch1.set_duty_cycle_fraction(1, 2); + Timer::after_millis(300).await; + ch1.set_duty_cycle(ch1.max_duty_cycle() - 1); + Timer::after_millis(300).await; + } +} diff --git a/examples/stm32f7/src/bin/pwm_ringbuffer.rs b/examples/stm32f7/src/bin/pwm_ringbuffer.rs new file mode 100644 index 000000000..4d191ac13 --- /dev/null +++ b/examples/stm32f7/src/bin/pwm_ringbuffer.rs @@ -0,0 +1,153 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::Config; +use embassy_stm32::gpio::OutputType; +use embassy_stm32::time::mhz; +use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +// If you are trying this and your USB device doesn't connect, the most +// common issues are the RCC config and vbus_detection +// +// See https://embassy.dev/book/#_the_usb_examples_are_not_working_on_my_board_is_there_anything_else_i_need_to_configure +// for more information. +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("PWM Ring Buffer Example"); + + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + use embassy_stm32::time::Hertz; + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::Bypass, + }); + config.rcc.pll_src = PllSource::HSE; + config.rcc.pll = Some(Pll { + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL200, + divp: Some(PllPDiv::DIV2), // 8mhz / 4 * 200 / 2 = 200Mhz + divq: Some(PllQDiv::DIV4), // 8mhz / 4 * 200 / 4 = 100Mhz + divr: None, + }); + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV4; + config.rcc.apb2_pre = APBPrescaler::DIV2; + config.rcc.sys = Sysclk::PLL1_P; + } + let p = embassy_stm32::init(config); + + // Initialize PWM on TIM1 + let ch1_pin = PwmPin::new(p.PE9, OutputType::PushPull); + let ch2_pin = PwmPin::new(p.PE11, OutputType::PushPull); + let mut pwm = SimplePwm::new( + p.TIM1, + Some(ch1_pin), + Some(ch2_pin), + None, + None, + mhz(1), + Default::default(), + ); + + // Use channel 1 for static PWM at 50% + let mut ch1 = pwm.ch1(); + ch1.enable(); + ch1.set_duty_cycle_fraction(1, 2); + info!("Channel 1 (PE9/D6): Static 50% duty cycle"); + + // Get max duty from channel 1 before converting channel 2 + let max_duty = ch1.max_duty_cycle(); + info!("PWM max duty: {}", max_duty); + + // Create a DMA ring buffer for channel 2 + const BUFFER_SIZE: usize = 128; + static mut DMA_BUFFER: [u16; BUFFER_SIZE] = [0u16; BUFFER_SIZE]; + let dma_buffer = unsafe { &mut *core::ptr::addr_of_mut!(DMA_BUFFER) }; + + // Pre-fill buffer with initial sine wave using lookup table approach + for i in 0..BUFFER_SIZE { + // Simple sine approximation using triangle wave + let phase = (i * 256) / BUFFER_SIZE; + let sine_approx = if phase < 128 { + phase as u16 * 2 + } else { + (255 - phase) as u16 * 2 + }; + dma_buffer[i] = (sine_approx as u32 * max_duty as u32 / 256) as u16; + } + + // Convert channel 2 to ring-buffered PWM + let mut ring_pwm = pwm.ch1().into_ring_buffered_channel(p.DMA2_CH5, dma_buffer); + + info!("Ring buffer capacity: {}", ring_pwm.capacity()); + + // Pre-write some initial data to the buffer before starting + info!("Pre-writing initial waveform data..."); + + ring_pwm.write(&[0; BUFFER_SIZE]).unwrap(); + + // Enable the PWM channel output + ring_pwm.enable(); + + // Start the DMA ring buffer + ring_pwm.start(); + info!("Channel 2 (PE11/D5): Ring buffered sine wave started"); + + // Give DMA time to start consuming + Timer::after_millis(10).await; + + // Continuously update the waveform + let mut phase: f32 = 0.0; + let mut amplitude: f32 = 1.0; + let mut amplitude_direction = -0.05; + + loop { + // Generate new waveform data with varying amplitude + let mut new_data = [0u16; 32]; + for i in 0..new_data.len() { + // Triangle wave approximation for sine + let pos = ((i as u32 + phase as u32) * 4) % 256; + let sine_approx = if pos < 128 { + pos as u16 * 2 + } else { + (255 - pos) as u16 * 2 + }; + let scaled = (sine_approx as u32 * (amplitude * 256.0) as u32) / (256 * 256); + new_data[i] = ((scaled * max_duty as u32) / 256) as u16; + } + + // Write new data to the ring buffer + match ring_pwm.write_exact(&new_data).await { + Ok(_remaining) => {} + Err(e) => { + info!("Write error: {:?}", e); + } + } + + // Update phase for animation effect + phase += 2.0; + if phase >= 64.0 { + phase = 0.0; + } + + // Vary amplitude for breathing effect + amplitude += amplitude_direction; + if amplitude <= 0.2 || amplitude >= 1.0 { + amplitude_direction = -amplitude_direction; + } + + // Log buffer status periodically + if (phase as u32) % 10 == 0 { + match ring_pwm.len() { + Ok(len) => info!("Ring buffer fill: {}/{}", len, ring_pwm.capacity()), + Err(_) => info!("Error reading buffer length"), + } + } + } +} -- cgit