diff options
| author | Raul Alimbekov <[email protected]> | 2025-12-16 09:05:22 +0300 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-12-16 09:05:22 +0300 |
| commit | c9a04b4b732b7a3b696eb8223664c1a7942b1875 (patch) | |
| tree | 6dbe5c02e66eed8d8762f13f95afd24f8db2b38c /embassy-stm32/src/timer/complementary_pwm.rs | |
| parent | cde24a3ef1117653ba5ed4184102b33f745782fb (diff) | |
| parent | 5ae6e060ec1c90561719aabdc29d5b6e7b8b0a82 (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.rs | 210 |
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 | ||
| 3 | use core::marker::PhantomData; | 3 | use core::marker::PhantomData; |
| 4 | 4 | ||
| 5 | pub use stm32_metapac::timer::vals::{Ckd, Ossi, Ossr}; | ||
| 6 | |||
| 7 | use super::low_level::{CountingMode, OutputPolarity, Timer}; | 5 | use super::low_level::{CountingMode, OutputPolarity, Timer}; |
| 8 | use super::simple_pwm::PwmPin; | 6 | use super::simple_pwm::PwmPin; |
| 9 | use super::{AdvancedInstance4Channel, Ch1, Ch2, Ch3, Ch4, Channel, TimerComplementaryPin}; | 7 | use super::{AdvancedInstance4Channel, Ch1, Ch2, Ch3, Ch4, Channel, TimerComplementaryPin}; |
| 10 | use crate::gpio::{AnyPin, OutputType}; | 8 | use crate::Peri; |
| 9 | use crate::dma::word::Word; | ||
| 10 | use crate::gpio::{AfType, AnyPin, OutputType}; | ||
| 11 | pub use crate::pac::timer::vals::{Ccds, Ckd, Mms2, Ossi, Ossr}; | ||
| 11 | use crate::time::Hertz; | 12 | use crate::time::Hertz; |
| 12 | use crate::timer::low_level::OutputCompareMode; | ||
| 13 | use crate::timer::TimerChannel; | 13 | use crate::timer::TimerChannel; |
| 14 | use crate::Peri; | 14 | use crate::timer::low_level::OutputCompareMode; |
| 15 | use 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)] |
| 390 | mod tests { | 448 | mod 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() { |
