From 23d74db1d6113914f2c4b80f0992bfeed235a89d Mon Sep 17 00:00:00 2001 From: Jakob Date: Sat, 15 Nov 2025 13:36:23 +0100 Subject: Avoid generating update events when chaning timer period. Set frequency update methods to return applied ARR values which then can be used for calcualting new CCR values. --- embassy-stm32/src/timer/complementary_pwm.rs | 10 ++++++---- embassy-stm32/src/timer/low_level.rs | 24 ++++++++---------------- embassy-stm32/src/timer/simple_pwm.rs | 10 ++++++---- 3 files changed, 20 insertions(+), 24 deletions(-) diff --git a/embassy-stm32/src/timer/complementary_pwm.rs b/embassy-stm32/src/timer/complementary_pwm.rs index 9a56a41fb..90ba196fc 100644 --- a/embassy-stm32/src/timer/complementary_pwm.rs +++ b/embassy-stm32/src/timer/complementary_pwm.rs @@ -160,15 +160,17 @@ impl<'d, T: AdvancedInstance4Channel> ComplementaryPwm<'d, T> { /// Set PWM frequency. /// - /// Note: when you call this, the max duty value changes, so you will have to - /// call `set_duty` on all channels with the duty calculated based on the new max duty. - pub fn set_frequency(&mut self, freq: Hertz) { + /// Returns the applied ARR value which can be used to calculate CCR values. + /// + /// Note: that the frequency will not be applied in the timer until an update event + /// occurs. Reading the `max_duty` before the update event will return the old value + pub fn set_frequency(&mut self, freq: Hertz) -> u32 { let multiplier = if self.inner.get_counting_mode().is_center_aligned() { 2u8 } else { 1u8 }; - self.inner.set_frequency_internal(freq * multiplier, 16); + self.inner.set_frequency_internal(freq * multiplier, 16) } /// Get max duty value. diff --git a/embassy-stm32/src/timer/low_level.rs b/embassy-stm32/src/timer/low_level.rs index 0122fe4f7..f6af8be8c 100644 --- a/embassy-stm32/src/timer/low_level.rs +++ b/embassy-stm32/src/timer/low_level.rs @@ -293,19 +293,17 @@ impl<'d, T: CoreInstance> Timer<'d, T> { /// the timer counter will wrap around at the same frequency as is being set. /// In center-aligned mode (which not all timers support), the wrap-around frequency is effectively halved /// because it needs to count up and down. - pub fn set_frequency(&self, frequency: Hertz) { + pub fn set_frequency(&self, frequency: Hertz) -> u32 { match T::BITS { - TimerBits::Bits16 => { - self.set_frequency_internal(frequency, 16); - } + TimerBits::Bits16 => self.set_frequency_internal(frequency, 16), #[cfg(not(stm32l0))] - TimerBits::Bits32 => { - self.set_frequency_internal(frequency, 32); - } + TimerBits::Bits32 => self.set_frequency_internal(frequency, 32), } } - pub(crate) fn set_frequency_internal(&self, frequency: Hertz, max_divide_by_bits: u8) { + /// Calculate ARR based on desired frequency + /// Returns actual value written to the register as u32 + pub(crate) fn set_frequency_internal(&self, frequency: Hertz, max_divide_by_bits: u8) -> u32 { let f = frequency.0; assert!(f > 0); let timer_f = T::frequency().0; @@ -322,10 +320,7 @@ impl<'d, T: CoreInstance> Timer<'d, T> { let regs = self.regs_core(); regs.psc().write_value(psc); regs.arr().write(|r| r.set_arr(arr)); - - regs.cr1().modify(|r| r.set_urs(vals::Urs::COUNTER_ONLY)); - regs.egr().write(|r| r.set_ug(true)); - regs.cr1().modify(|r| r.set_urs(vals::Urs::ANY_EVENT)); + arr as u32 } #[cfg(not(stm32l0))] TimerBits::Bits32 => { @@ -335,10 +330,7 @@ impl<'d, T: CoreInstance> Timer<'d, T> { let regs = self.regs_gp32_unchecked(); regs.psc().write_value(psc); regs.arr().write_value(arr); - - regs.cr1().modify(|r| r.set_urs(vals::Urs::COUNTER_ONLY)); - regs.egr().write(|r| r.set_ug(true)); - regs.cr1().modify(|r| r.set_urs(vals::Urs::ANY_EVENT)); + arr } } } diff --git a/embassy-stm32/src/timer/simple_pwm.rs b/embassy-stm32/src/timer/simple_pwm.rs index c338b0fd4..01996c969 100644 --- a/embassy-stm32/src/timer/simple_pwm.rs +++ b/embassy-stm32/src/timer/simple_pwm.rs @@ -285,16 +285,18 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { /// Set PWM frequency. /// - /// Note: when you call this, the max duty value changes, so you will have to - /// call `set_duty` on all channels with the duty calculated based on the new max duty. - pub fn set_frequency(&mut self, freq: Hertz) { + /// Returns the applied ARR value which can be used to calculate CCR values. + /// + /// Note: that the frequency will not be applied in the timer until an update event + /// occurs. Reading the `max_duty` before the update event will return the old value + pub fn set_frequency(&mut self, freq: Hertz) -> u32 { // TODO: prevent ARR = u16::MAX? let multiplier = if self.inner.get_counting_mode().is_center_aligned() { 2u8 } else { 1u8 }; - self.inner.set_frequency_internal(freq * multiplier, 16); + self.inner.set_frequency_internal(freq * multiplier, 16) } /// Get max duty value. -- cgit From 67af86d664cd84122824d0a039ce366f2dcdae03 Mon Sep 17 00:00:00 2001 From: Jakob Date: Sat, 15 Nov 2025 13:47:07 +0100 Subject: Add changelog entry --- embassy-stm32/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/embassy-stm32/CHANGELOG.md b/embassy-stm32/CHANGELOG.md index 9153e15b9..b0287f73a 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 ## Unreleased - ReleaseDate +- fix: Avoid generating timer update events when updating the frequency, add ARR as return value ([#4890](https://github.com/embassy-rs/embassy/pull/4890)) - fix: flash erase on dual-bank STM32Gxxx - feat: Add support for STM32N657X0 - feat: timer: Add 32-bit timer support to SimplePwm waveform_up method following waveform pattern ([#4717](https://github.com/embassy-rs/embassy/pull/4717)) -- cgit From 4793f59cde20203b33dca7222d12cbd9f95d5e1c Mon Sep 17 00:00:00 2001 From: Jakob Date: Sat, 15 Nov 2025 20:19:06 +0100 Subject: Add separate method for generating update event. Make sure values are loaded into shadow registers before starting the timer. --- embassy-stm32/CHANGELOG.md | 2 +- embassy-stm32/src/timer/complementary_pwm.rs | 14 +++++++------- embassy-stm32/src/timer/input_capture.rs | 1 + embassy-stm32/src/timer/low_level.rs | 26 ++++++++++++++++++-------- embassy-stm32/src/timer/pwm_input.rs | 1 + embassy-stm32/src/timer/simple_pwm.rs | 14 ++++++++------ 6 files changed, 36 insertions(+), 22 deletions(-) diff --git a/embassy-stm32/CHANGELOG.md b/embassy-stm32/CHANGELOG.md index b0287f73a..71b8cdafa 100644 --- a/embassy-stm32/CHANGELOG.md +++ b/embassy-stm32/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - ReleaseDate -- fix: Avoid generating timer update events when updating the frequency, add ARR as return value ([#4890](https://github.com/embassy-rs/embassy/pull/4890)) +- fix: Avoid generating timer update events when updating the frequency ([#4890](https://github.com/embassy-rs/embassy/pull/4890)) - fix: flash erase on dual-bank STM32Gxxx - feat: Add support for STM32N657X0 - feat: timer: Add 32-bit timer support to SimplePwm waveform_up method following waveform pattern ([#4717](https://github.com/embassy-rs/embassy/pull/4717)) diff --git a/embassy-stm32/src/timer/complementary_pwm.rs b/embassy-stm32/src/timer/complementary_pwm.rs index 90ba196fc..3331e5b6b 100644 --- a/embassy-stm32/src/timer/complementary_pwm.rs +++ b/embassy-stm32/src/timer/complementary_pwm.rs @@ -77,8 +77,6 @@ impl<'d, T: AdvancedInstance4Channel> ComplementaryPwm<'d, T> { this.inner.set_counting_mode(counting_mode); this.set_frequency(freq); - this.inner.start(); - this.inner.enable_outputs(); [Channel::Ch1, Channel::Ch2, Channel::Ch3, Channel::Ch4] @@ -89,6 +87,10 @@ impl<'d, T: AdvancedInstance4Channel> ComplementaryPwm<'d, T> { }); this.inner.set_autoreload_preload(true); + // Generate update event so pre-load registers are written to the shadow registers + this.inner.generate_update_event(); + this.inner.start(); + this } @@ -160,17 +162,15 @@ impl<'d, T: AdvancedInstance4Channel> ComplementaryPwm<'d, T> { /// Set PWM frequency. /// - /// Returns the applied ARR value which can be used to calculate CCR values. - /// /// Note: that the frequency will not be applied in the timer until an update event - /// occurs. Reading the `max_duty` before the update event will return the old value - pub fn set_frequency(&mut self, freq: Hertz) -> u32 { + /// occurs. + pub fn set_frequency(&mut self, freq: Hertz) { let multiplier = if self.inner.get_counting_mode().is_center_aligned() { 2u8 } else { 1u8 }; - self.inner.set_frequency_internal(freq * multiplier, 16) + self.inner.set_frequency_internal(freq * multiplier, 16); } /// Get max duty value. diff --git a/embassy-stm32/src/timer/input_capture.rs b/embassy-stm32/src/timer/input_capture.rs index 2a4ec2db0..9cf0f8c34 100644 --- a/embassy-stm32/src/timer/input_capture.rs +++ b/embassy-stm32/src/timer/input_capture.rs @@ -60,6 +60,7 @@ impl<'d, T: GeneralInstance4Channel> InputCapture<'d, T> { this.inner.set_counting_mode(counting_mode); this.inner.set_tick_freq(freq); this.inner.enable_outputs(); // Required for advanced timers, see GeneralInstance4Channel for details + this.inner.generate_update_event(); this.inner.start(); // enable NVIC interrupt diff --git a/embassy-stm32/src/timer/low_level.rs b/embassy-stm32/src/timer/low_level.rs index f6af8be8c..55e1160ef 100644 --- a/embassy-stm32/src/timer/low_level.rs +++ b/embassy-stm32/src/timer/low_level.rs @@ -272,6 +272,16 @@ impl<'d, T: CoreInstance> Timer<'d, T> { self.regs_core().cr1().modify(|r| r.set_cen(true)); } + /// Generate timer update event from software. + /// + /// Set URS to avoid generating interrupt or DMA request. This update event is only + /// used to load value from pre-load registers. + pub fn generate_update_event(&self) { + self.regs_core().cr1().modify(|r| r.set_urs(vals::Urs::COUNTER_ONLY)); + self.regs_core().egr().write(|r| r.set_ug(true)); + self.regs_core().cr1().modify(|r| r.set_urs(vals::Urs::ANY_EVENT)); + } + /// Stop the timer. pub fn stop(&self) { self.regs_core().cr1().modify(|r| r.set_cen(false)); @@ -293,17 +303,19 @@ impl<'d, T: CoreInstance> Timer<'d, T> { /// the timer counter will wrap around at the same frequency as is being set. /// In center-aligned mode (which not all timers support), the wrap-around frequency is effectively halved /// because it needs to count up and down. - pub fn set_frequency(&self, frequency: Hertz) -> u32 { + pub fn set_frequency(&self, frequency: Hertz) { match T::BITS { - TimerBits::Bits16 => self.set_frequency_internal(frequency, 16), + TimerBits::Bits16 => { + self.set_frequency_internal(frequency, 16); + } #[cfg(not(stm32l0))] - TimerBits::Bits32 => self.set_frequency_internal(frequency, 32), + TimerBits::Bits32 => { + self.set_frequency_internal(frequency, 32); + } } } - /// Calculate ARR based on desired frequency - /// Returns actual value written to the register as u32 - pub(crate) fn set_frequency_internal(&self, frequency: Hertz, max_divide_by_bits: u8) -> u32 { + pub(crate) fn set_frequency_internal(&self, frequency: Hertz, max_divide_by_bits: u8) { let f = frequency.0; assert!(f > 0); let timer_f = T::frequency().0; @@ -320,7 +332,6 @@ impl<'d, T: CoreInstance> Timer<'d, T> { let regs = self.regs_core(); regs.psc().write_value(psc); regs.arr().write(|r| r.set_arr(arr)); - arr as u32 } #[cfg(not(stm32l0))] TimerBits::Bits32 => { @@ -330,7 +341,6 @@ impl<'d, T: CoreInstance> Timer<'d, T> { let regs = self.regs_gp32_unchecked(); regs.psc().write_value(psc); regs.arr().write_value(arr); - arr } } } diff --git a/embassy-stm32/src/timer/pwm_input.rs b/embassy-stm32/src/timer/pwm_input.rs index da8a79b09..057ab011a 100644 --- a/embassy-stm32/src/timer/pwm_input.rs +++ b/embassy-stm32/src/timer/pwm_input.rs @@ -47,6 +47,7 @@ impl<'d, T: GeneralInstance4Channel> PwmInput<'d, T> { inner.set_counting_mode(CountingMode::EdgeAlignedUp); inner.set_tick_freq(freq); inner.enable_outputs(); // Required for advanced timers, see GeneralInstance4Channel for details + inner.generate_update_event(); inner.start(); // Configuration steps from ST RM0390 (STM32F446) chapter 17.3.6 diff --git a/embassy-stm32/src/timer/simple_pwm.rs b/embassy-stm32/src/timer/simple_pwm.rs index 01996c969..58a2e2685 100644 --- a/embassy-stm32/src/timer/simple_pwm.rs +++ b/embassy-stm32/src/timer/simple_pwm.rs @@ -198,7 +198,6 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { this.inner.set_counting_mode(counting_mode); this.set_frequency(freq); this.inner.enable_outputs(); // Required for advanced timers, see GeneralInstance4Channel for details - this.inner.start(); [Channel::Ch1, Channel::Ch2, Channel::Ch3, Channel::Ch4] .iter() @@ -207,6 +206,11 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { this.inner.set_output_compare_preload(channel, true); }); + this.inner.set_autoreload_preload(true); + + // Generate update event so pre-load registers are written to the shadow registers + this.inner.generate_update_event(); + this.inner.start(); this } @@ -285,18 +289,16 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { /// Set PWM frequency. /// - /// Returns the applied ARR value which can be used to calculate CCR values. - /// /// Note: that the frequency will not be applied in the timer until an update event - /// occurs. Reading the `max_duty` before the update event will return the old value - pub fn set_frequency(&mut self, freq: Hertz) -> u32 { + /// occurs. + pub fn set_frequency(&mut self, freq: Hertz) { // TODO: prevent ARR = u16::MAX? let multiplier = if self.inner.get_counting_mode().is_center_aligned() { 2u8 } else { 1u8 }; - self.inner.set_frequency_internal(freq * multiplier, 16) + self.inner.set_frequency_internal(freq * multiplier, 16); } /// Get max duty value. -- cgit