From 3402e76f9876d58d271501102e2d80c9b2c389b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Jacobs?= Date: Tue, 19 Nov 2024 17:26:45 +0100 Subject: [PATCH] stm32/timer: avoid max_compare_value >= u16::MAX With STM32 32 bits timers, the max_compare_value (AKA, ARR register) can currently be set greater than u16::MAX, which leads to the following assert!(max < u16::MAX as u32) in max_duty_cycle() when setting up a 1 kHz SimplePwm on 84 MHz MCU. The issue is fixed by forcing a max_compare_value that fits into 16 bits when setting the frequency for a PWM. --- embassy-stm32/src/timer/complementary_pwm.rs | 2 +- embassy-stm32/src/timer/low_level.rs | 24 +++++++++++++------- embassy-stm32/src/timer/simple_pwm.rs | 2 +- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/embassy-stm32/src/timer/complementary_pwm.rs b/embassy-stm32/src/timer/complementary_pwm.rs index 46ccbf3df..02c01e900 100644 --- a/embassy-stm32/src/timer/complementary_pwm.rs +++ b/embassy-stm32/src/timer/complementary_pwm.rs @@ -116,7 +116,7 @@ impl<'d, T: AdvancedInstance4Channel> ComplementaryPwm<'d, T> { } else { 1u8 }; - self.inner.set_frequency(freq * multiplier); + 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 3136ea4e9..7360d6aef 100644 --- a/embassy-stm32/src/timer/low_level.rs +++ b/embassy-stm32/src/timer/low_level.rs @@ -242,16 +242,28 @@ impl<'d, T: CoreInstance> Timer<'d, T> { /// 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) { + match T::BITS { + TimerBits::Bits16 => { + self.set_frequency_internal(frequency, 16); + } + #[cfg(not(stm32l0))] + TimerBits::Bits32 => { + self.set_frequency_internal(frequency, 32); + } + } + } + + 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; + let pclk_ticks_per_timer_period = (timer_f / f) as u64; + let psc: u16 = unwrap!(((pclk_ticks_per_timer_period - 1) / (1 << max_divide_by_bits)).try_into()); + let divide_by = pclk_ticks_per_timer_period / (u64::from(psc) + 1); + match T::BITS { TimerBits::Bits16 => { - let pclk_ticks_per_timer_period = timer_f / f; - let psc: u16 = unwrap!(((pclk_ticks_per_timer_period - 1) / (1 << 16)).try_into()); - let divide_by = pclk_ticks_per_timer_period / (u32::from(psc) + 1); - // the timer counts `0..=arr`, we want it to count `0..divide_by` let arr = unwrap!(u16::try_from(divide_by - 1)); @@ -265,10 +277,6 @@ impl<'d, T: CoreInstance> Timer<'d, T> { } #[cfg(not(stm32l0))] TimerBits::Bits32 => { - let pclk_ticks_per_timer_period = (timer_f / f) as u64; - let psc: u16 = unwrap!(((pclk_ticks_per_timer_period - 1) / (1 << 32)).try_into()); - let divide_by = pclk_ticks_per_timer_period / (u64::from(psc) + 1); - // the timer counts `0..=arr`, we want it to count `0..divide_by` let arr: u32 = unwrap!(u32::try_from(divide_by - 1)); diff --git a/embassy-stm32/src/timer/simple_pwm.rs b/embassy-stm32/src/timer/simple_pwm.rs index 9e4a09095..56fb1871e 100644 --- a/embassy-stm32/src/timer/simple_pwm.rs +++ b/embassy-stm32/src/timer/simple_pwm.rs @@ -278,7 +278,7 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { } else { 1u8 }; - self.inner.set_frequency(freq * multiplier); + self.inner.set_frequency_internal(freq * multiplier, 16); } /// Get max duty value.