aboutsummaryrefslogtreecommitdiff
path: root/embassy-stm32/src/timer/complementary_pwm.rs
diff options
context:
space:
mode:
authorRaul Alimbekov <[email protected]>2025-12-16 09:05:22 +0300
committerGitHub <[email protected]>2025-12-16 09:05:22 +0300
commitc9a04b4b732b7a3b696eb8223664c1a7942b1875 (patch)
tree6dbe5c02e66eed8d8762f13f95afd24f8db2b38c /embassy-stm32/src/timer/complementary_pwm.rs
parentcde24a3ef1117653ba5ed4184102b33f745782fb (diff)
parent5ae6e060ec1c90561719aabdc29d5b6e7b8b0a82 (diff)
Merge branch 'main' into main
Diffstat (limited to 'embassy-stm32/src/timer/complementary_pwm.rs')
-rw-r--r--embassy-stm32/src/timer/complementary_pwm.rs210
1 files changed, 134 insertions, 76 deletions
diff --git a/embassy-stm32/src/timer/complementary_pwm.rs b/embassy-stm32/src/timer/complementary_pwm.rs
index 484aae1d0..620d7858e 100644
--- a/embassy-stm32/src/timer/complementary_pwm.rs
+++ b/embassy-stm32/src/timer/complementary_pwm.rs
@@ -2,16 +2,17 @@
2 2
3use core::marker::PhantomData; 3use core::marker::PhantomData;
4 4
5pub use stm32_metapac::timer::vals::{Ckd, Ossi, Ossr};
6
7use super::low_level::{CountingMode, OutputPolarity, Timer}; 5use super::low_level::{CountingMode, OutputPolarity, Timer};
8use super::simple_pwm::PwmPin; 6use super::simple_pwm::PwmPin;
9use super::{AdvancedInstance4Channel, Ch1, Ch2, Ch3, Ch4, Channel, TimerComplementaryPin}; 7use super::{AdvancedInstance4Channel, Ch1, Ch2, Ch3, Ch4, Channel, TimerComplementaryPin};
10use crate::gpio::{AnyPin, OutputType}; 8use crate::Peri;
9use crate::dma::word::Word;
10use crate::gpio::{AfType, AnyPin, OutputType};
11pub use crate::pac::timer::vals::{Ccds, Ckd, Mms2, Ossi, Ossr};
11use crate::time::Hertz; 12use crate::time::Hertz;
12use crate::timer::low_level::OutputCompareMode;
13use crate::timer::TimerChannel; 13use crate::timer::TimerChannel;
14use crate::Peri; 14use crate::timer::low_level::OutputCompareMode;
15use crate::timer::simple_pwm::PwmPinConfig;
15 16
16/// Complementary PWM pin wrapper. 17/// Complementary PWM pin wrapper.
17/// 18///
@@ -27,9 +28,27 @@ impl<'d, T: AdvancedInstance4Channel, C: TimerChannel, #[cfg(afio)] A> if_afio!(
27 pub fn new(pin: Peri<'d, if_afio!(impl TimerComplementaryPin<T, C, A>)>, output_type: OutputType) -> Self { 28 pub fn new(pin: Peri<'d, if_afio!(impl TimerComplementaryPin<T, C, A>)>, output_type: OutputType) -> Self {
28 critical_section::with(|_| { 29 critical_section::with(|_| {
29 pin.set_low(); 30 pin.set_low();
30 set_as_af!( 31 set_as_af!(pin, AfType::output(output_type, crate::gpio::Speed::VeryHigh));
31 pin, 32 });
32 crate::gpio::AfType::output(output_type, crate::gpio::Speed::VeryHigh) 33 ComplementaryPwmPin {
34 pin: pin.into(),
35 phantom: PhantomData,
36 }
37 }
38
39 /// Create a new PWM pin instance with config.
40 pub fn new_with_config(
41 pin: Peri<'d, if_afio!(impl TimerComplementaryPin<T, C, A>)>,
42 pin_config: PwmPinConfig,
43 ) -> Self {
44 critical_section::with(|_| {
45 pin.set_low();
46 #[cfg(gpio_v1)]
47 set_as_af!(pin, AfType::output(pin_config.output_type, pin_config.speed));
48 #[cfg(gpio_v2)]
49 pin.set_as_af(
50 pin.af_num(),
51 AfType::output_pull(pin_config.output_type, pin_config.speed, pin_config.pull),
33 ); 52 );
34 }); 53 });
35 ComplementaryPwmPin { 54 ComplementaryPwmPin {
@@ -77,8 +96,6 @@ impl<'d, T: AdvancedInstance4Channel> ComplementaryPwm<'d, T> {
77 96
78 this.inner.set_counting_mode(counting_mode); 97 this.inner.set_counting_mode(counting_mode);
79 this.set_frequency(freq); 98 this.set_frequency(freq);
80 this.inner.start();
81
82 this.inner.enable_outputs(); 99 this.inner.enable_outputs();
83 100
84 [Channel::Ch1, Channel::Ch2, Channel::Ch3, Channel::Ch4] 101 [Channel::Ch1, Channel::Ch2, Channel::Ch3, Channel::Ch4]
@@ -89,6 +106,10 @@ impl<'d, T: AdvancedInstance4Channel> ComplementaryPwm<'d, T> {
89 }); 106 });
90 this.inner.set_autoreload_preload(true); 107 this.inner.set_autoreload_preload(true);
91 108
109 // Generate update event so pre-load registers are written to the shadow registers
110 this.inner.generate_update_event();
111 this.inner.start();
112
92 this 113 this
93 } 114 }
94 115
@@ -136,6 +157,16 @@ impl<'d, T: AdvancedInstance4Channel> ComplementaryPwm<'d, T> {
136 self.inner.get_moe() 157 self.inner.get_moe()
137 } 158 }
138 159
160 /// Set Master Slave Mode 2
161 pub fn set_mms2(&mut self, mms2: Mms2) {
162 self.inner.set_mms2_selection(mms2);
163 }
164
165 /// Set Repetition Counter
166 pub fn set_repetition_counter(&mut self, val: u16) {
167 self.inner.set_repetition_counter(val);
168 }
169
139 /// Enable the given channel. 170 /// Enable the given channel.
140 pub fn enable(&mut self, channel: Channel) { 171 pub fn enable(&mut self, channel: Channel) {
141 self.inner.enable_channel(channel, true); 172 self.inner.enable_channel(channel, true);
@@ -150,8 +181,8 @@ impl<'d, T: AdvancedInstance4Channel> ComplementaryPwm<'d, T> {
150 181
151 /// Set PWM frequency. 182 /// Set PWM frequency.
152 /// 183 ///
153 /// Note: when you call this, the max duty value changes, so you will have to 184 /// Note: that the frequency will not be applied in the timer until an update event
154 /// call `set_duty` on all channels with the duty calculated based on the new max duty. 185 /// occurs.
155 pub fn set_frequency(&mut self, freq: Hertz) { 186 pub fn set_frequency(&mut self, freq: Hertz) {
156 let multiplier = if self.inner.get_counting_mode().is_center_aligned() { 187 let multiplier = if self.inner.get_counting_mode().is_center_aligned() {
157 2u8 188 2u8
@@ -164,20 +195,20 @@ impl<'d, T: AdvancedInstance4Channel> ComplementaryPwm<'d, T> {
164 /// Get max duty value. 195 /// Get max duty value.
165 /// 196 ///
166 /// This value depends on the configured frequency and the timer's clock rate from RCC. 197 /// This value depends on the configured frequency and the timer's clock rate from RCC.
167 pub fn get_max_duty(&self) -> u16 { 198 pub fn get_max_duty(&self) -> u32 {
168 if self.inner.get_counting_mode().is_center_aligned() { 199 if self.inner.get_counting_mode().is_center_aligned() {
169 self.inner.get_max_compare_value() as u16 200 self.inner.get_max_compare_value().into()
170 } else { 201 } else {
171 self.inner.get_max_compare_value() as u16 + 1 202 self.inner.get_max_compare_value().into() + 1
172 } 203 }
173 } 204 }
174 205
175 /// Set the duty for a given channel. 206 /// Set the duty for a given channel.
176 /// 207 ///
177 /// The value ranges from 0 for 0% duty, to [`get_max_duty`](Self::get_max_duty) for 100% duty, both included. 208 /// The value ranges from 0 for 0% duty, to [`get_max_duty`](Self::get_max_duty) for 100% duty, both included.
178 pub fn set_duty(&mut self, channel: Channel, duty: u16) { 209 pub fn set_duty(&mut self, channel: Channel, duty: u32) {
179 assert!(duty <= self.get_max_duty()); 210 assert!(duty <= self.get_max_duty());
180 self.inner.set_compare_value(channel, duty as _) 211 self.inner.set_compare_value(channel, unwrap!(duty.try_into()))
181 } 212 }
182 213
183 /// Set the output polarity for a given channel. 214 /// Set the output polarity for a given channel.
@@ -207,61 +238,88 @@ impl<'d, T: AdvancedInstance4Channel> ComplementaryPwm<'d, T> {
207 /// Generate a sequence of PWM waveform 238 /// Generate a sequence of PWM waveform
208 /// 239 ///
209 /// Note: 240 /// Note:
210 /// you will need to provide corresponding TIMx_UP DMA channel to use this method. 241 /// The DMA channel provided does not need to correspond to the requested channel.
211 pub async fn waveform_up(&mut self, dma: Peri<'_, impl super::UpDma<T>>, channel: Channel, duty: &[u16]) { 242 pub async fn waveform<C: TimerChannel, W: Word + Into<T::Word>>(
212 #[allow(clippy::let_unit_value)] // eg. stm32f334 243 &mut self,
213 let req = dma.request(); 244 dma: Peri<'_, impl super::Dma<T, C>>,
214 245 channel: Channel,
215 let original_duty_state = self.inner.get_compare_value(channel); 246 duty: &[W],
216 let original_enable_state = self.inner.get_channel_enable_state(channel); 247 ) {
217 let original_update_dma_state = self.inner.get_update_dma_state(); 248 self.inner.enable_channel(channel, true);
218 249 self.inner.enable_channel(C::CHANNEL, true);
219 if !original_update_dma_state { 250 self.inner.clamp_compare_value::<W>(channel);
220 self.inner.enable_update_dma(true); 251 self.inner.set_cc_dma_selection(Ccds::ON_UPDATE);
221 } 252 self.inner.set_cc_dma_enable_state(C::CHANNEL, true);
222 253 self.inner.setup_channel_update_dma(dma, channel, duty).await;
223 if !original_enable_state { 254 self.inner.set_cc_dma_enable_state(C::CHANNEL, false);
224 self.inner.enable_channel(channel, true); 255 }
225 }
226
227 unsafe {
228 #[cfg(not(any(bdma, gpdma)))]
229 use crate::dma::{Burst, FifoThreshold};
230 use crate::dma::{Transfer, TransferOptions};
231
232 let dma_transfer_option = TransferOptions {
233 #[cfg(not(any(bdma, gpdma)))]
234 fifo_threshold: Some(FifoThreshold::Full),
235 #[cfg(not(any(bdma, gpdma)))]
236 mburst: Burst::Incr8,
237 ..Default::default()
238 };
239
240 Transfer::new_write(
241 dma,
242 req,
243 duty,
244 self.inner.regs_gp16().ccr(channel.index()).as_ptr() as *mut u16,
245 dma_transfer_option,
246 )
247 .await
248 };
249
250 // restore output compare state
251 if !original_enable_state {
252 self.inner.enable_channel(channel, false);
253 }
254 256
255 self.inner.set_compare_value(channel, original_duty_state); 257 /// Generate a sequence of PWM waveform
258 ///
259 /// Note:
260 /// you will need to provide corresponding TIMx_UP DMA channel to use this method.
261 pub async fn waveform_up<W: Word + Into<T::Word>>(
262 &mut self,
263 dma: Peri<'_, impl super::UpDma<T>>,
264 channel: Channel,
265 duty: &[W],
266 ) {
267 self.inner.enable_channel(channel, true);
268 self.inner.clamp_compare_value::<W>(channel);
269 self.inner.enable_update_dma(true);
270 self.inner.setup_update_dma(dma, channel, duty).await;
271 self.inner.enable_update_dma(false);
272 }
256 273
257 // Since DMA is closed before timer update event trigger DMA is turn off, 274 /// Generate a multichannel sequence of PWM waveforms using DMA triggered by timer update events.
258 // this can almost always trigger a DMA FIFO error. 275 ///
259 // 276 /// This method utilizes the timer's DMA burst transfer capability to update multiple CCRx registers
260 // optional TODO: 277 /// in sequence on each update event (UEV). The data is written via the DMAR register using the
261 // clean FEIF after disable UDE 278 /// DMA base address (DBA) and burst length (DBL) configured in the DCR register.
262 if !original_update_dma_state { 279 ///
263 self.inner.enable_update_dma(false); 280 /// The `duty` buffer must be structured as a flattened 2D array in row-major order, where each row
264 } 281 /// represents a single update event and each column corresponds to a specific timer channel (starting
282 /// from `starting_channel` up to and including `ending_channel`).
283 ///
284 /// For example, if using channels 1 through 4, a buffer of 4 update steps might look like:
285 ///
286 /// ```rust,ignore
287 /// let dma_buf: [u16; 16] = [
288 /// ch1_duty_1, ch2_duty_1, ch3_duty_1, ch4_duty_1, // update 1
289 /// ch1_duty_2, ch2_duty_2, ch3_duty_2, ch4_duty_2, // update 2
290 /// ch1_duty_3, ch2_duty_3, ch3_duty_3, ch4_duty_3, // update 3
291 /// ch1_duty_4, ch2_duty_4, ch3_duty_4, ch4_duty_4, // update 4
292 /// ];
293 /// ```
294 ///
295 /// Each group of `N` values (where `N` is number of channels) is transferred on one update event,
296 /// updating the duty cycles of all selected channels simultaneously.
297 ///
298 /// Note:
299 /// You will need to provide corresponding `TIMx_UP` DMA channel to use this method.
300 /// Also be aware that embassy timers use one of timers internally. It is possible to
301 /// switch this timer by using `time-driver-timX` feature.
302 ///
303 pub async fn waveform_up_multi_channel<W: Word + Into<T::Word>>(
304 &mut self,
305 dma: Peri<'_, impl super::UpDma<T>>,
306 starting_channel: Channel,
307 ending_channel: Channel,
308 duty: &[W],
309 ) {
310 [Channel::Ch1, Channel::Ch2, Channel::Ch3, Channel::Ch4]
311 .iter()
312 .filter(|ch| ch.index() >= starting_channel.index())
313 .filter(|ch| ch.index() <= ending_channel.index())
314 .for_each(|ch| {
315 self.inner.enable_channel(*ch, true);
316 self.inner.clamp_compare_value::<W>(*ch);
317 });
318 self.inner.enable_update_dma(true);
319 self.inner
320 .setup_update_dma_burst(dma, starting_channel, ending_channel, duty)
321 .await;
322 self.inner.enable_update_dma(false);
265 } 323 }
266} 324}
267 325
@@ -285,20 +343,20 @@ impl<'d, T: AdvancedInstance4Channel> embedded_hal_02::Pwm for ComplementaryPwm<
285 } 343 }
286 344
287 fn get_duty(&self, channel: Self::Channel) -> Self::Duty { 345 fn get_duty(&self, channel: Self::Channel) -> Self::Duty {
288 self.inner.get_compare_value(channel) as u16 346 unwrap!(self.inner.get_compare_value(channel).try_into())
289 } 347 }
290 348
291 fn get_max_duty(&self) -> Self::Duty { 349 fn get_max_duty(&self) -> Self::Duty {
292 if self.inner.get_counting_mode().is_center_aligned() { 350 if self.inner.get_counting_mode().is_center_aligned() {
293 self.inner.get_max_compare_value() as u16 351 unwrap!(self.inner.get_max_compare_value().try_into())
294 } else { 352 } else {
295 self.inner.get_max_compare_value() as u16 + 1 353 unwrap!(self.inner.get_max_compare_value().try_into()) + 1
296 } 354 }
297 } 355 }
298 356
299 fn set_duty(&mut self, channel: Self::Channel, duty: Self::Duty) { 357 fn set_duty(&mut self, channel: Self::Channel, duty: Self::Duty) {
300 assert!(duty <= self.get_max_duty()); 358 assert!(duty <= unwrap!(self.get_max_duty().try_into()));
301 self.inner.set_compare_value(channel, duty as u32) 359 self.inner.set_compare_value(channel, unwrap!(duty.try_into()))
302 } 360 }
303 361
304 fn set_period<P>(&mut self, period: P) 362 fn set_period<P>(&mut self, period: P)
@@ -388,7 +446,7 @@ fn compute_dead_time_value(value: u16) -> (Ckd, u8) {
388 446
389#[cfg(test)] 447#[cfg(test)]
390mod tests { 448mod tests {
391 use super::{compute_dead_time_value, Ckd}; 449 use super::{Ckd, compute_dead_time_value};
392 450
393 #[test] 451 #[test]
394 fn test_compute_dead_time_value() { 452 fn test_compute_dead_time_value() {