aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--embassy-stm32/CHANGELOG.md1
-rw-r--r--embassy-stm32/src/timer/low_level.rs35
-rw-r--r--embassy-stm32/src/timer/mod.rs1
-rw-r--r--embassy-stm32/src/timer/ringbuffered.rs169
-rw-r--r--embassy-stm32/src/timer/simple_pwm.rs28
-rw-r--r--examples/stm32f7/src/bin/pwm.rs61
-rw-r--r--examples/stm32f7/src/bin/pwm_ringbuffer.rs153
7 files changed, 447 insertions, 1 deletions
diff --git a/embassy-stm32/CHANGELOG.md b/embassy-stm32/CHANGELOG.md
index 949ea03b5..d3e5ba48d 100644
--- a/embassy-stm32/CHANGELOG.md
+++ b/embassy-stm32/CHANGELOG.md
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7 7
8## Unreleased - ReleaseDate 8## Unreleased - ReleaseDate
9 9
10- feat: Add continuous waveform method to SimplePWM
10- change: remove waveform timer method 11- change: remove waveform timer method
11- change: low power: store stop mode for dma channels 12- change: low power: store stop mode for dma channels
12- fix: Fixed ADC4 enable() for WBA 13- fix: Fixed ADC4 enable() for WBA
diff --git a/embassy-stm32/src/timer/low_level.rs b/embassy-stm32/src/timer/low_level.rs
index f986c8dab..aba08081f 100644
--- a/embassy-stm32/src/timer/low_level.rs
+++ b/embassy-stm32/src/timer/low_level.rs
@@ -13,7 +13,7 @@ use embassy_hal_internal::Peri;
13pub use stm32_metapac::timer::vals::{FilterValue, Mms as MasterMode, Sms as SlaveMode, Ts as TriggerSource}; 13pub use stm32_metapac::timer::vals::{FilterValue, Mms as MasterMode, Sms as SlaveMode, Ts as TriggerSource};
14 14
15use super::*; 15use super::*;
16use crate::dma::Transfer; 16use crate::dma::{Transfer, WritableRingBuffer};
17use crate::pac::timer::vals; 17use crate::pac::timer::vals;
18use crate::rcc; 18use crate::rcc;
19use crate::time::Hertz; 19use crate::time::Hertz;
@@ -660,6 +660,39 @@ impl<'d, T: GeneralInstance4Channel> Timer<'d, T> {
660 } 660 }
661 } 661 }
662 662
663 /// Setup a ring buffer for the channel
664 pub fn setup_ring_buffer<'a>(
665 &mut self,
666 dma: Peri<'a, impl super::UpDma<T>>,
667 channel: Channel,
668 dma_buf: &'a mut [u16],
669 ) -> WritableRingBuffer<'a, u16> {
670 #[allow(clippy::let_unit_value)] // eg. stm32f334
671 let req = dma.request();
672
673 unsafe {
674 use crate::dma::TransferOptions;
675 #[cfg(not(any(bdma, gpdma)))]
676 use crate::dma::{Burst, FifoThreshold};
677
678 let dma_transfer_option = TransferOptions {
679 #[cfg(not(any(bdma, gpdma)))]
680 fifo_threshold: Some(FifoThreshold::Full),
681 #[cfg(not(any(bdma, gpdma)))]
682 mburst: Burst::Incr8,
683 ..Default::default()
684 };
685
686 WritableRingBuffer::new(
687 dma,
688 req,
689 self.regs_1ch().ccr(channel.index()).as_ptr() as *mut u16,
690 dma_buf,
691 dma_transfer_option,
692 )
693 }
694 }
695
663 /// Generate a sequence of PWM waveform 696 /// Generate a sequence of PWM waveform
664 /// 697 ///
665 /// Note: 698 /// Note:
diff --git a/embassy-stm32/src/timer/mod.rs b/embassy-stm32/src/timer/mod.rs
index 804d1ef37..3fa363881 100644
--- a/embassy-stm32/src/timer/mod.rs
+++ b/embassy-stm32/src/timer/mod.rs
@@ -12,6 +12,7 @@ pub mod low_level;
12pub mod one_pulse; 12pub mod one_pulse;
13pub mod pwm_input; 13pub mod pwm_input;
14pub mod qei; 14pub mod qei;
15pub mod ringbuffered;
15pub mod simple_pwm; 16pub mod simple_pwm;
16 17
17use crate::interrupt; 18use crate::interrupt;
diff --git a/embassy-stm32/src/timer/ringbuffered.rs b/embassy-stm32/src/timer/ringbuffered.rs
new file mode 100644
index 000000000..e8f97bf59
--- /dev/null
+++ b/embassy-stm32/src/timer/ringbuffered.rs
@@ -0,0 +1,169 @@
1//! RingBuffered PWM driver.
2
3use core::mem::ManuallyDrop;
4use core::task::Waker;
5
6use super::low_level::Timer;
7use super::{Channel, GeneralInstance4Channel};
8use crate::dma::WritableRingBuffer;
9use crate::dma::ringbuffer::Error;
10
11/// A PWM channel that uses a DMA ring buffer for continuous waveform generation.
12///
13/// This allows you to continuously update PWM duty cycles via DMA without blocking the CPU.
14/// The ring buffer enables smooth, uninterrupted waveform generation by automatically cycling
15/// through duty cycle values stored in memory.
16///
17/// You can write new duty cycle values to the ring buffer while it's running, enabling
18/// dynamic waveform generation for applications like motor control, LED dimming, or audio output.
19///
20/// # Example
21/// ```ignore
22/// let mut channel = pwm.ch1().into_ring_buffered_channel(dma_ch, &mut buffer);
23/// channel.start(); // Start DMA transfer
24/// channel.write(&[100, 200, 300]).ok(); // Update duty cycles
25/// ```
26pub struct RingBufferedPwmChannel<'d, T: GeneralInstance4Channel> {
27 timer: ManuallyDrop<Timer<'d, T>>,
28 ring_buf: WritableRingBuffer<'d, u16>,
29 channel: Channel,
30}
31
32impl<'d, T: GeneralInstance4Channel> RingBufferedPwmChannel<'d, T> {
33 pub(crate) fn new(
34 timer: ManuallyDrop<Timer<'d, T>>,
35 channel: Channel,
36 ring_buf: WritableRingBuffer<'d, u16>,
37 ) -> Self {
38 Self {
39 timer,
40 ring_buf,
41 channel,
42 }
43 }
44
45 /// Start the ring buffer operation.
46 ///
47 /// You must call this after creating it for it to work.
48 pub fn start(&mut self) {
49 self.ring_buf.start()
50 }
51
52 /// Clear all data in the ring buffer.
53 pub fn clear(&mut self) {
54 self.ring_buf.clear()
55 }
56
57 /// Write elements directly to the raw buffer. This can be used to fill the buffer before starting the DMA transfer.
58 pub fn write_immediate(&mut self, buf: &[u16]) -> Result<(usize, usize), Error> {
59 self.ring_buf.write_immediate(buf)
60 }
61
62 /// Write elements from the ring buffer
63 /// Return a tuple of the length written and the length remaining in the buffer
64 pub fn write(&mut self, buf: &[u16]) -> Result<(usize, usize), Error> {
65 self.ring_buf.write(buf)
66 }
67
68 /// Write an exact number of elements to the ringbuffer.
69 pub async fn write_exact(&mut self, buffer: &[u16]) -> Result<usize, Error> {
70 self.ring_buf.write_exact(buffer).await
71 }
72
73 /// Wait for any ring buffer write error.
74 pub async fn wait_write_error(&mut self) -> Result<usize, Error> {
75 self.ring_buf.wait_write_error().await
76 }
77
78 /// The current length of the ringbuffer
79 pub fn len(&mut self) -> Result<usize, Error> {
80 self.ring_buf.len()
81 }
82
83 /// The capacity of the ringbuffer
84 pub const fn capacity(&self) -> usize {
85 self.ring_buf.capacity()
86 }
87
88 /// Set a waker to be woken when at least one byte is send.
89 pub fn set_waker(&mut self, waker: &Waker) {
90 self.ring_buf.set_waker(waker)
91 }
92
93 /// Request the DMA to reset. The configuration for this channel will not be preserved.
94 ///
95 /// This doesn't immediately stop the transfer, you have to wait until is_running returns false.
96 pub fn request_reset(&mut self) {
97 self.ring_buf.request_reset()
98 }
99
100 /// Request the transfer to pause, keeping the existing configuration for this channel.
101 /// To restart the transfer, call [`start`](Self::start) again.
102 ///
103 /// This doesn't immediately stop the transfer, you have to wait until is_running returns false.
104 pub fn request_pause(&mut self) {
105 self.ring_buf.request_pause()
106 }
107
108 /// Return whether DMA is still running.
109 ///
110 /// If this returns false, it can be because either the transfer finished, or it was requested to stop early with request_stop.
111 pub fn is_running(&mut self) -> bool {
112 self.ring_buf.is_running()
113 }
114
115 /// Stop the DMA transfer and await until the buffer is empty.
116 ///
117 /// This disables the DMA transfer's circular mode so that the transfer stops when all available data has been written.
118 ///
119 /// This is designed to be used with streaming output data such as the I2S/SAI or DAC.
120 pub async fn stop(&mut self) {
121 self.ring_buf.stop().await
122 }
123
124 /// Enable the given channel.
125 pub fn enable(&mut self) {
126 self.timer.enable_channel(self.channel, true);
127 }
128
129 /// Disable the given channel.
130 pub fn disable(&mut self) {
131 self.timer.enable_channel(self.channel, false);
132 }
133
134 /// Check whether given channel is enabled
135 pub fn is_enabled(&self) -> bool {
136 self.timer.get_channel_enable_state(self.channel)
137 }
138
139 /// Get max duty value.
140 ///
141 /// This value depends on the configured frequency and the timer's clock rate from RCC.
142 pub fn max_duty_cycle(&self) -> u16 {
143 let max = self.timer.get_max_compare_value();
144 assert!(max < u16::MAX as u32);
145 max as u16 + 1
146 }
147
148 /// Set the output polarity for a given channel.
149 pub fn set_polarity(&mut self, polarity: super::low_level::OutputPolarity) {
150 self.timer.set_output_polarity(self.channel, polarity);
151 }
152
153 /// Set the output compare mode for a given channel.
154 pub fn set_output_compare_mode(&mut self, mode: super::low_level::OutputCompareMode) {
155 self.timer.set_output_compare_mode(self.channel, mode);
156 }
157}
158
159/// A group of four [`SimplePwmChannel`]s, obtained from [`SimplePwm::split`].
160pub struct RingBufferedPwmChannels<'d, T: GeneralInstance4Channel> {
161 /// Channel 1
162 pub ch1: RingBufferedPwmChannel<'d, T>,
163 /// Channel 2
164 pub ch2: RingBufferedPwmChannel<'d, T>,
165 /// Channel 3
166 pub ch3: RingBufferedPwmChannel<'d, T>,
167 /// Channel 4
168 pub ch4: RingBufferedPwmChannel<'d, T>,
169}
diff --git a/embassy-stm32/src/timer/simple_pwm.rs b/embassy-stm32/src/timer/simple_pwm.rs
index eb1b66358..484e9fd81 100644
--- a/embassy-stm32/src/timer/simple_pwm.rs
+++ b/embassy-stm32/src/timer/simple_pwm.rs
@@ -4,6 +4,7 @@ use core::marker::PhantomData;
4use core::mem::ManuallyDrop; 4use core::mem::ManuallyDrop;
5 5
6use super::low_level::{CountingMode, OutputCompareMode, OutputPolarity, Timer}; 6use super::low_level::{CountingMode, OutputCompareMode, OutputPolarity, Timer};
7use super::ringbuffered::RingBufferedPwmChannel;
7use super::{Ch1, Ch2, Ch3, Ch4, Channel, GeneralInstance4Channel, TimerChannel, TimerPin}; 8use super::{Ch1, Ch2, Ch3, Ch4, Channel, GeneralInstance4Channel, TimerChannel, TimerPin};
8use crate::Peri; 9use crate::Peri;
9#[cfg(gpio_v2)] 10#[cfg(gpio_v2)]
@@ -158,6 +159,33 @@ impl<'d, T: GeneralInstance4Channel> SimplePwmChannel<'d, T> {
158 pub fn set_output_compare_mode(&mut self, mode: OutputCompareMode) { 159 pub fn set_output_compare_mode(&mut self, mode: OutputCompareMode) {
159 self.timer.set_output_compare_mode(self.channel, mode); 160 self.timer.set_output_compare_mode(self.channel, mode);
160 } 161 }
162
163 /// Convert this PWM channel into a ring-buffered PWM channel.
164 ///
165 /// This allows continuous PWM waveform generation using a DMA ring buffer.
166 /// The ring buffer enables dynamic updates to the PWM duty cycle without blocking.
167 ///
168 /// # Arguments
169 /// * `tx_dma` - The DMA channel to use for transferring duty cycle values
170 /// * `dma_buf` - The buffer to use as a ring buffer (must be non-empty and <= 65535 elements)
171 ///
172 /// # Panics
173 /// Panics if `dma_buf` is empty or longer than 65535 elements.
174 pub fn into_ring_buffered_channel(
175 mut self,
176 tx_dma: Peri<'d, impl super::UpDma<T>>,
177 dma_buf: &'d mut [u16],
178 ) -> RingBufferedPwmChannel<'d, T> {
179 assert!(!dma_buf.is_empty() && dma_buf.len() <= 0xFFFF);
180
181 self.timer.enable_update_dma(true);
182
183 RingBufferedPwmChannel::new(
184 unsafe { self.timer.clone_unchecked() },
185 self.channel,
186 self.timer.setup_ring_buffer(tx_dma, self.channel, dma_buf),
187 )
188 }
161} 189}
162 190
163/// A group of four [`SimplePwmChannel`]s, obtained from [`SimplePwm::split`]. 191/// A group of four [`SimplePwmChannel`]s, obtained from [`SimplePwm::split`].
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 @@
1#![no_std]
2#![no_main]
3
4use defmt::*;
5use embassy_executor::Spawner;
6use embassy_stm32::Config;
7use embassy_stm32::gpio::OutputType;
8use embassy_stm32::time::{Hertz, mhz};
9use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm};
10use embassy_time::Timer;
11use {defmt_rtt as _, panic_probe as _};
12
13// If you are trying this and your USB device doesn't connect, the most
14// common issues are the RCC config and vbus_detection
15//
16// See https://embassy.dev/book/#_the_usb_examples_are_not_working_on_my_board_is_there_anything_else_i_need_to_configure
17// for more information.
18#[embassy_executor::main]
19async fn main(_spawner: Spawner) {
20 info!("Hello World!");
21
22 let mut config = Config::default();
23 {
24 use embassy_stm32::rcc::*;
25 config.rcc.hse = Some(Hse {
26 freq: Hertz(8_000_000),
27 mode: HseMode::Bypass,
28 });
29 config.rcc.pll_src = PllSource::HSE;
30 config.rcc.pll = Some(Pll {
31 prediv: PllPreDiv::DIV4,
32 mul: PllMul::MUL200,
33 divp: Some(PllPDiv::DIV2), // 8mhz / 4 * 200 / 2 = 200Mhz
34 divq: Some(PllQDiv::DIV4), // 8mhz / 4 * 200 / 4 = 100Mhz
35 divr: None,
36 });
37 config.rcc.ahb_pre = AHBPrescaler::DIV1;
38 config.rcc.apb1_pre = APBPrescaler::DIV4;
39 config.rcc.apb2_pre = APBPrescaler::DIV2;
40 config.rcc.sys = Sysclk::PLL1_P;
41 }
42 let p = embassy_stm32::init(config);
43 let ch1_pin = PwmPin::new(p.PE9, OutputType::PushPull);
44 let mut pwm = SimplePwm::new(p.TIM1, Some(ch1_pin), None, None, None, mhz(1), Default::default());
45 let mut ch1 = pwm.ch1();
46 ch1.enable();
47
48 info!("PWM initialized");
49 info!("PWM max duty {}", ch1.max_duty_cycle());
50
51 loop {
52 ch1.set_duty_cycle_fully_off();
53 Timer::after_millis(300).await;
54 ch1.set_duty_cycle_fraction(1, 4);
55 Timer::after_millis(300).await;
56 ch1.set_duty_cycle_fraction(1, 2);
57 Timer::after_millis(300).await;
58 ch1.set_duty_cycle(ch1.max_duty_cycle() - 1);
59 Timer::after_millis(300).await;
60 }
61}
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 @@
1#![no_std]
2#![no_main]
3
4use defmt::*;
5use embassy_executor::Spawner;
6use embassy_stm32::Config;
7use embassy_stm32::gpio::OutputType;
8use embassy_stm32::time::mhz;
9use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm};
10use embassy_time::Timer;
11use {defmt_rtt as _, panic_probe as _};
12
13// If you are trying this and your USB device doesn't connect, the most
14// common issues are the RCC config and vbus_detection
15//
16// See https://embassy.dev/book/#_the_usb_examples_are_not_working_on_my_board_is_there_anything_else_i_need_to_configure
17// for more information.
18#[embassy_executor::main]
19async fn main(_spawner: Spawner) {
20 info!("PWM Ring Buffer Example");
21
22 let mut config = Config::default();
23 {
24 use embassy_stm32::rcc::*;
25 use embassy_stm32::time::Hertz;
26 config.rcc.hse = Some(Hse {
27 freq: Hertz(8_000_000),
28 mode: HseMode::Bypass,
29 });
30 config.rcc.pll_src = PllSource::HSE;
31 config.rcc.pll = Some(Pll {
32 prediv: PllPreDiv::DIV4,
33 mul: PllMul::MUL200,
34 divp: Some(PllPDiv::DIV2), // 8mhz / 4 * 200 / 2 = 200Mhz
35 divq: Some(PllQDiv::DIV4), // 8mhz / 4 * 200 / 4 = 100Mhz
36 divr: None,
37 });
38 config.rcc.ahb_pre = AHBPrescaler::DIV1;
39 config.rcc.apb1_pre = APBPrescaler::DIV4;
40 config.rcc.apb2_pre = APBPrescaler::DIV2;
41 config.rcc.sys = Sysclk::PLL1_P;
42 }
43 let p = embassy_stm32::init(config);
44
45 // Initialize PWM on TIM1
46 let ch1_pin = PwmPin::new(p.PE9, OutputType::PushPull);
47 let ch2_pin = PwmPin::new(p.PE11, OutputType::PushPull);
48 let mut pwm = SimplePwm::new(
49 p.TIM1,
50 Some(ch1_pin),
51 Some(ch2_pin),
52 None,
53 None,
54 mhz(1),
55 Default::default(),
56 );
57
58 // Use channel 1 for static PWM at 50%
59 let mut ch1 = pwm.ch1();
60 ch1.enable();
61 ch1.set_duty_cycle_fraction(1, 2);
62 info!("Channel 1 (PE9/D6): Static 50% duty cycle");
63
64 // Get max duty from channel 1 before converting channel 2
65 let max_duty = ch1.max_duty_cycle();
66 info!("PWM max duty: {}", max_duty);
67
68 // Create a DMA ring buffer for channel 2
69 const BUFFER_SIZE: usize = 128;
70 static mut DMA_BUFFER: [u16; BUFFER_SIZE] = [0u16; BUFFER_SIZE];
71 let dma_buffer = unsafe { &mut *core::ptr::addr_of_mut!(DMA_BUFFER) };
72
73 // Pre-fill buffer with initial sine wave using lookup table approach
74 for i in 0..BUFFER_SIZE {
75 // Simple sine approximation using triangle wave
76 let phase = (i * 256) / BUFFER_SIZE;
77 let sine_approx = if phase < 128 {
78 phase as u16 * 2
79 } else {
80 (255 - phase) as u16 * 2
81 };
82 dma_buffer[i] = (sine_approx as u32 * max_duty as u32 / 256) as u16;
83 }
84
85 // Convert channel 2 to ring-buffered PWM
86 let mut ring_pwm = pwm.ch1().into_ring_buffered_channel(p.DMA2_CH5, dma_buffer);
87
88 info!("Ring buffer capacity: {}", ring_pwm.capacity());
89
90 // Pre-write some initial data to the buffer before starting
91 info!("Pre-writing initial waveform data...");
92
93 ring_pwm.write(&[0; BUFFER_SIZE]).unwrap();
94
95 // Enable the PWM channel output
96 ring_pwm.enable();
97
98 // Start the DMA ring buffer
99 ring_pwm.start();
100 info!("Channel 2 (PE11/D5): Ring buffered sine wave started");
101
102 // Give DMA time to start consuming
103 Timer::after_millis(10).await;
104
105 // Continuously update the waveform
106 let mut phase: f32 = 0.0;
107 let mut amplitude: f32 = 1.0;
108 let mut amplitude_direction = -0.05;
109
110 loop {
111 // Generate new waveform data with varying amplitude
112 let mut new_data = [0u16; 32];
113 for i in 0..new_data.len() {
114 // Triangle wave approximation for sine
115 let pos = ((i as u32 + phase as u32) * 4) % 256;
116 let sine_approx = if pos < 128 {
117 pos as u16 * 2
118 } else {
119 (255 - pos) as u16 * 2
120 };
121 let scaled = (sine_approx as u32 * (amplitude * 256.0) as u32) / (256 * 256);
122 new_data[i] = ((scaled * max_duty as u32) / 256) as u16;
123 }
124
125 // Write new data to the ring buffer
126 match ring_pwm.write_exact(&new_data).await {
127 Ok(_remaining) => {}
128 Err(e) => {
129 info!("Write error: {:?}", e);
130 }
131 }
132
133 // Update phase for animation effect
134 phase += 2.0;
135 if phase >= 64.0 {
136 phase = 0.0;
137 }
138
139 // Vary amplitude for breathing effect
140 amplitude += amplitude_direction;
141 if amplitude <= 0.2 || amplitude >= 1.0 {
142 amplitude_direction = -amplitude_direction;
143 }
144
145 // Log buffer status periodically
146 if (phase as u32) % 10 == 0 {
147 match ring_pwm.len() {
148 Ok(len) => info!("Ring buffer fill: {}/{}", len, ring_pwm.capacity()),
149 Err(_) => info!("Error reading buffer length"),
150 }
151 }
152 }
153}