From 3c44e67adfd761ec37f149b25b0085fbb27bbaf9 Mon Sep 17 00:00:00 2001 From: Hatim Thayyil Date: Tue, 10 Feb 2026 14:11:15 +0000 Subject: [PATCH 01/17] stm32/hrtim: split off BridgeConverter and ResonantConverter converter implementations to separate modules --- embassy-stm32/src/hrtim/bridge_converter.rs | 149 ++++++++++++ embassy-stm32/src/hrtim/mod.rs | 218 +----------------- embassy-stm32/src/hrtim/resonant_converter.rs | 78 +++++++ examples/stm32f334/src/bin/pwm.rs | 2 +- 4 files changed, 233 insertions(+), 214 deletions(-) create mode 100644 embassy-stm32/src/hrtim/bridge_converter.rs create mode 100644 embassy-stm32/src/hrtim/resonant_converter.rs diff --git a/embassy-stm32/src/hrtim/bridge_converter.rs b/embassy-stm32/src/hrtim/bridge_converter.rs new file mode 100644 index 0000000000..c129c942ab --- /dev/null +++ b/embassy-stm32/src/hrtim/bridge_converter.rs @@ -0,0 +1,149 @@ +//! Fixed-frequency bridge converter driver. + +use core::marker::PhantomData; + +use super::traits::Instance; +use super::AdvancedChannel; +use crate::time::Hertz; + +/// Fixed-frequency bridge converter driver. +/// +/// Our implementation of the bridge converter uses a single channel and three compare registers, +/// allowing implementation of a synchronous buck or boost converter in continuous or discontinuous +/// conduction mode. +/// +/// It is important to remember that in synchronous topologies, energy can flow in reverse during +/// light loading conditions, and that the low-side switch must be active for a short time to drive +/// a bootstrapped high-side switch. +pub struct BridgeConverter> { + timer: PhantomData, + channel: PhantomData, + dead_time: u16, + primary_duty: u16, + min_secondary_duty: u16, + max_secondary_duty: u16, +} + +impl> BridgeConverter { + /// Create a new HRTIM bridge converter driver. + pub fn new(_channel: C, frequency: Hertz) -> Self { + T::set_channel_frequency(C::raw(), frequency); + + // Always enable preload + T::regs().tim(C::raw()).cr().modify(|w| { + w.set_preen(true); + w.set_repu(true); + w.set_cont(true); + }); + + // Enable timer outputs + T::regs().oenr().modify(|w| { + w.set_t1oen(C::raw(), true); + w.set_t2oen(C::raw(), true); + }); + + // The dead-time generation unit cannot be used because it forces the other output + // to be completely complementary to the first output, which restricts certain waveforms + // Therefore, software-implemented dead time must be used when setting the duty cycles + + // Set output 1 to active on a period event + T::regs().tim(C::raw()).setr(0).modify(|w| w.set_per(true)); + + // Set output 1 to inactive on a compare 1 event + T::regs().tim(C::raw()).rstr(0).modify(|w| w.set_cmp(0, true)); + + // Set output 2 to active on a compare 2 event + T::regs().tim(C::raw()).setr(1).modify(|w| w.set_cmp(1, true)); + + // Set output 2 to inactive on a compare 3 event + T::regs().tim(C::raw()).rstr(1).modify(|w| w.set_cmp(2, true)); + + Self { + timer: PhantomData, + channel: PhantomData, + dead_time: 0, + primary_duty: 0, + min_secondary_duty: 0, + max_secondary_duty: 0, + } + } + + /// Start HRTIM. + pub fn start(&mut self) { + T::regs().mcr().modify(|w| w.set_tcen(C::raw(), true)); + } + + /// Stop HRTIM. + pub fn stop(&mut self) { + T::regs().mcr().modify(|w| w.set_tcen(C::raw(), false)); + } + + /// Enable burst mode. + pub fn enable_burst_mode(&mut self) { + T::regs().tim(C::raw()).outr().modify(|w| { + // Enable Burst Mode + w.set_idlem(0, true); + w.set_idlem(1, true); + + // Set output to active during the burst + w.set_idles(0, true); + w.set_idles(1, true); + }) + } + + /// Disable burst mode. + pub fn disable_burst_mode(&mut self) { + T::regs().tim(C::raw()).outr().modify(|w| { + // Disable Burst Mode + w.set_idlem(0, false); + w.set_idlem(1, false); + }) + } + + fn update_primary_duty_or_dead_time(&mut self) { + self.min_secondary_duty = self.primary_duty + self.dead_time; + + T::regs().tim(C::raw()).cmp(0).modify(|w| w.set_cmp(self.primary_duty)); + T::regs() + .tim(C::raw()) + .cmp(1) + .modify(|w| w.set_cmp(self.min_secondary_duty)); + } + + /// Set the dead time as a proportion of the maximum compare value + pub fn set_dead_time(&mut self, dead_time: u16) { + self.dead_time = dead_time; + self.max_secondary_duty = self.get_max_compare_value() - dead_time; + self.update_primary_duty_or_dead_time(); + } + + /// Get the maximum compare value of a duty cycle + pub fn get_max_compare_value(&mut self) -> u16 { + T::regs().tim(C::raw()).per().read().per() + } + + /// The primary duty is the period in which the primary switch is active + /// + /// In the case of a buck converter, this is the high-side switch + /// In the case of a boost converter, this is the low-side switch + pub fn set_primary_duty(&mut self, primary_duty: u16) { + self.primary_duty = primary_duty; + self.update_primary_duty_or_dead_time(); + } + + /// The secondary duty is the period in any switch is active + /// + /// If less than or equal to the primary duty, the secondary switch will be active for one tick + /// If a fully complementary output is desired, the secondary duty can be set to the max compare + pub fn set_secondary_duty(&mut self, secondary_duty: u16) { + let secondary_duty = if secondary_duty > self.max_secondary_duty { + self.max_secondary_duty + } else if secondary_duty <= self.min_secondary_duty { + self.min_secondary_duty + 1 + } else { + secondary_duty + }; + + T::regs().tim(C::raw()).cmp(2).modify(|w| w.set_cmp(secondary_duty)); + } +} diff --git a/embassy-stm32/src/hrtim/mod.rs b/embassy-stm32/src/hrtim/mod.rs index ae8ee22f8f..1455bcd817 100644 --- a/embassy-stm32/src/hrtim/mod.rs +++ b/embassy-stm32/src/hrtim/mod.rs @@ -7,6 +7,11 @@ use core::marker::PhantomData; use embassy_hal_internal::Peri; pub use traits::Instance; +pub mod bridge_converter; +pub mod resonant_converter; +pub use bridge_converter::BridgeConverter; +pub use resonant_converter::ResonantConverter; + use crate::gpio::{AfType, Flex, OutputType, Speed}; use crate::rcc; use crate::time::Hertz; @@ -278,219 +283,6 @@ impl<'d, T: Instance> AdvancedPwm<'d, T> { } } -/// Fixed-frequency bridge converter driver. -/// -/// Our implementation of the bridge converter uses a single channel and three compare registers, -/// allowing implementation of a synchronous buck or boost converter in continuous or discontinuous -/// conduction mode. -/// -/// It is important to remember that in synchronous topologies, energy can flow in reverse during -/// light loading conditions, and that the low-side switch must be active for a short time to drive -/// a bootstrapped high-side switch. -pub struct BridgeConverter> { - timer: PhantomData, - channel: PhantomData, - dead_time: u16, - primary_duty: u16, - min_secondary_duty: u16, - max_secondary_duty: u16, -} - -impl> BridgeConverter { - /// Create a new HRTIM bridge converter driver. - pub fn new(_channel: C, frequency: Hertz) -> Self { - T::set_channel_frequency(C::raw(), frequency); - - // Always enable preload - T::regs().tim(C::raw()).cr().modify(|w| { - w.set_preen(true); - w.set_repu(true); - w.set_cont(true); - }); - - // Enable timer outputs - T::regs().oenr().modify(|w| { - w.set_t1oen(C::raw(), true); - w.set_t2oen(C::raw(), true); - }); - - // The dead-time generation unit cannot be used because it forces the other output - // to be completely complementary to the first output, which restricts certain waveforms - // Therefore, software-implemented dead time must be used when setting the duty cycles - - // Set output 1 to active on a period event - T::regs().tim(C::raw()).setr(0).modify(|w| w.set_per(true)); - - // Set output 1 to inactive on a compare 1 event - T::regs().tim(C::raw()).rstr(0).modify(|w| w.set_cmp(0, true)); - - // Set output 2 to active on a compare 2 event - T::regs().tim(C::raw()).setr(1).modify(|w| w.set_cmp(1, true)); - - // Set output 2 to inactive on a compare 3 event - T::regs().tim(C::raw()).rstr(1).modify(|w| w.set_cmp(2, true)); - - Self { - timer: PhantomData, - channel: PhantomData, - dead_time: 0, - primary_duty: 0, - min_secondary_duty: 0, - max_secondary_duty: 0, - } - } - - /// Start HRTIM. - pub fn start(&mut self) { - T::regs().mcr().modify(|w| w.set_tcen(C::raw(), true)); - } - - /// Stop HRTIM. - pub fn stop(&mut self) { - T::regs().mcr().modify(|w| w.set_tcen(C::raw(), false)); - } - - /// Enable burst mode. - pub fn enable_burst_mode(&mut self) { - T::regs().tim(C::raw()).outr().modify(|w| { - // Enable Burst Mode - w.set_idlem(0, true); - w.set_idlem(1, true); - - // Set output to active during the burst - w.set_idles(0, true); - w.set_idles(1, true); - }) - } - - /// Disable burst mode. - pub fn disable_burst_mode(&mut self) { - T::regs().tim(C::raw()).outr().modify(|w| { - // Disable Burst Mode - w.set_idlem(0, false); - w.set_idlem(1, false); - }) - } - - fn update_primary_duty_or_dead_time(&mut self) { - self.min_secondary_duty = self.primary_duty + self.dead_time; - - T::regs().tim(C::raw()).cmp(0).modify(|w| w.set_cmp(self.primary_duty)); - T::regs() - .tim(C::raw()) - .cmp(1) - .modify(|w| w.set_cmp(self.min_secondary_duty)); - } - - /// Set the dead time as a proportion of the maximum compare value - pub fn set_dead_time(&mut self, dead_time: u16) { - self.dead_time = dead_time; - self.max_secondary_duty = self.get_max_compare_value() - dead_time; - self.update_primary_duty_or_dead_time(); - } - - /// Get the maximum compare value of a duty cycle - pub fn get_max_compare_value(&mut self) -> u16 { - T::regs().tim(C::raw()).per().read().per() - } - - /// The primary duty is the period in which the primary switch is active - /// - /// In the case of a buck converter, this is the high-side switch - /// In the case of a boost converter, this is the low-side switch - pub fn set_primary_duty(&mut self, primary_duty: u16) { - self.primary_duty = primary_duty; - self.update_primary_duty_or_dead_time(); - } - - /// The secondary duty is the period in any switch is active - /// - /// If less than or equal to the primary duty, the secondary switch will be active for one tick - /// If a fully complementary output is desired, the secondary duty can be set to the max compare - pub fn set_secondary_duty(&mut self, secondary_duty: u16) { - let secondary_duty = if secondary_duty > self.max_secondary_duty { - self.max_secondary_duty - } else if secondary_duty <= self.min_secondary_duty { - self.min_secondary_duty + 1 - } else { - secondary_duty - }; - - T::regs().tim(C::raw()).cmp(2).modify(|w| w.set_cmp(secondary_duty)); - } -} - -/// Variable-frequency resonant converter driver. -/// -/// This implementation of a resonsant converter is appropriate for a half or full bridge, -/// but does not include secondary rectification, which is appropriate for applications -/// with a low-voltage on the secondary side. -pub struct ResonantConverter> { - timer: PhantomData, - channel: PhantomData, - min_period: u16, - max_period: u16, -} - -impl> ResonantConverter { - /// Create a new variable-frequency resonant converter driver. - pub fn new(_channel: C, min_frequency: Hertz, max_frequency: Hertz) -> Self { - T::set_channel_frequency(C::raw(), min_frequency); - - // Always enable preload - T::regs().tim(C::raw()).cr().modify(|w| { - w.set_preen(true); - w.set_repu(true); - - w.set_cont(true); - w.set_half(true); - }); - - // Enable timer outputs - T::regs().oenr().modify(|w| { - w.set_t1oen(C::raw(), true); - w.set_t2oen(C::raw(), true); - }); - - // Dead-time generator can be used in this case because the primary fets - // of a resonant converter are always complementary - T::regs().tim(C::raw()).outr().modify(|w| w.set_dten(true)); - - let max_period = T::regs().tim(C::raw()).per().read().per(); - let min_period = max_period * (min_frequency.0 / max_frequency.0) as u16; - - Self { - timer: PhantomData, - channel: PhantomData, - min_period: min_period, - max_period: max_period, - } - } - - /// Set the dead time as a proportion of the maximum compare value - pub fn set_dead_time(&mut self, value: u16) { - T::set_channel_dead_time(C::raw(), value); - } - - /// Set the timer period. - pub fn set_period(&mut self, period: u16) { - assert!(period < self.max_period); - assert!(period > self.min_period); - - T::regs().tim(C::raw()).per().modify(|w| w.set_per(period)); - } - - /// Get the minimum compare value of a duty cycle - pub fn get_min_period(&mut self) -> u16 { - self.min_period - } - - /// Get the maximum compare value of a duty cycle - pub fn get_max_period(&mut self) -> u16 { - self.max_period - } -} - pin_trait!(ChannelAPin, Instance); pin_trait!(ChannelAComplementaryPin, Instance); pin_trait!(ChannelBPin, Instance); diff --git a/embassy-stm32/src/hrtim/resonant_converter.rs b/embassy-stm32/src/hrtim/resonant_converter.rs new file mode 100644 index 0000000000..f2811a7654 --- /dev/null +++ b/embassy-stm32/src/hrtim/resonant_converter.rs @@ -0,0 +1,78 @@ +//! Variable-frequency resonant converter driver. + +use core::marker::PhantomData; + +use super::traits::Instance; +use super::AdvancedChannel; +use crate::time::Hertz; + +/// Variable-frequency resonant converter driver. +/// +/// This implementation of a resonsant converter is appropriate for a half or full bridge, +/// but does not include secondary rectification, which is appropriate for applications +/// with a low-voltage on the secondary side. +pub struct ResonantConverter> { + timer: PhantomData, + channel: PhantomData, + min_period: u16, + max_period: u16, +} + +impl> ResonantConverter { + /// Create a new variable-frequency resonant converter driver. + pub fn new(_channel: C, min_frequency: Hertz, max_frequency: Hertz) -> Self { + T::set_channel_frequency(C::raw(), min_frequency); + + // Always enable preload + T::regs().tim(C::raw()).cr().modify(|w| { + w.set_preen(true); + w.set_repu(true); + + w.set_cont(true); + w.set_half(true); + }); + + // Enable timer outputs + T::regs().oenr().modify(|w| { + w.set_t1oen(C::raw(), true); + w.set_t2oen(C::raw(), true); + }); + + // Dead-time generator can be used in this case because the primary fets + // of a resonant converter are always complementary + T::regs().tim(C::raw()).outr().modify(|w| w.set_dten(true)); + + let max_period = T::regs().tim(C::raw()).per().read().per(); + let min_period = max_period * (min_frequency.0 / max_frequency.0) as u16; + + Self { + timer: PhantomData, + channel: PhantomData, + min_period: min_period, + max_period: max_period, + } + } + + /// Set the dead time as a proportion of the maximum compare value + pub fn set_dead_time(&mut self, value: u16) { + T::set_channel_dead_time(C::raw(), value); + } + + /// Set the timer period. + pub fn set_period(&mut self, period: u16) { + assert!(period < self.max_period); + assert!(period > self.min_period); + + T::regs().tim(C::raw()).per().modify(|w| w.set_per(period)); + } + + /// Get the minimum compare value of a duty cycle + pub fn get_min_period(&mut self) -> u16 { + self.min_period + } + + /// Get the maximum compare value of a duty cycle + pub fn get_max_period(&mut self) -> u16 { + self.max_period + } +} diff --git a/examples/stm32f334/src/bin/pwm.rs b/examples/stm32f334/src/bin/pwm.rs index 68a61ae229..cb9f214943 100644 --- a/examples/stm32f334/src/bin/pwm.rs +++ b/examples/stm32f334/src/bin/pwm.rs @@ -3,7 +3,7 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_stm32::Config; +use embassy_stm32::hrtim::BridgeConverter; use embassy_stm32::hrtim::*; use embassy_stm32::time::{khz, mhz}; use embassy_time::Timer; From c84544439573e50667f752b7af170929426b0a64 Mon Sep 17 00:00:00 2001 From: Hatim Thayyil Date: Tue, 10 Feb 2026 14:38:43 +0000 Subject: [PATCH 02/17] stm32/hrtim: move advanced_pwm to module --- embassy-stm32/src/hrtim/advanced_pwm.rs | 187 ++++++++++++++++++++++++ embassy-stm32/src/hrtim/mod.rs | 163 +-------------------- 2 files changed, 188 insertions(+), 162 deletions(-) create mode 100644 embassy-stm32/src/hrtim/advanced_pwm.rs diff --git a/embassy-stm32/src/hrtim/advanced_pwm.rs b/embassy-stm32/src/hrtim/advanced_pwm.rs new file mode 100644 index 0000000000..5cd6ec3c3f --- /dev/null +++ b/embassy-stm32/src/hrtim/advanced_pwm.rs @@ -0,0 +1,187 @@ +//! AdvancedPwm + +use core::marker::PhantomData; + +use embassy_hal_internal::Peri; + +use crate::gpio::{AfType, Flex, OutputType, Speed}; +pub use crate::timer::simple_pwm::PwmPinConfig; + +use super::{BurstController, ChA, ChB, ChC, ChD, ChE, Instance, Master}; +#[cfg(hrtim_v2)] +use super::{ChF, ChannelFComplementaryPin, ChannelFPin}; +use super::{ + ChannelAComplementaryPin, ChannelBComplementaryPin, ChannelCComplementaryPin, ChannelDComplementaryPin, + ChannelEComplementaryPin, +}; +use super::{ChannelAPin, ChannelBPin, ChannelCPin, ChannelDPin, ChannelEPin}; + +use crate::rcc; + +/// Struct used to divide a high resolution timer into multiple channels +pub struct AdvancedPwm<'d, T: Instance> { + _inner: Peri<'d, T>, + /// Master instance. + pub master: Master, + /// Burst controller. + pub burst_controller: BurstController, + /// Channel A. + pub ch_a: ChA, + /// Channel B. + pub ch_b: ChB, + /// Channel C. + pub ch_c: ChC, + /// Channel D. + pub ch_d: ChD, + /// Channel E. + pub ch_e: ChE, + /// Channel F. + #[cfg(hrtim_v2)] + pub ch_f: ChF, +} + +impl<'d, T: Instance> AdvancedPwm<'d, T> { + /// Create a new HRTIM driver. + /// + /// This splits the HRTIM into its constituent parts, which you can then use individually. + pub fn new( + tim: Peri<'d, T>, + _cha: Option>>, + _chan: Option>>, + _chb: Option>>, + _chbn: Option>>, + _chc: Option>>, + _chcn: Option>>, + _chd: Option>>, + _chdn: Option>>, + _che: Option>>, + _chen: Option>>, + #[cfg(hrtim_v2)] _chf: Option>>, + #[cfg(hrtim_v2)] _chfn: Option>>, + ) -> Self { + Self::new_inner(tim) + } + + fn new_inner(tim: Peri<'d, T>) -> Self { + rcc::enable_and_reset::(); + + #[cfg(stm32f334)] + if crate::pac::RCC.cfgr3().read().hrtim1sw() == crate::pac::rcc::vals::Timsw::PLL1_P { + // Enable and and stabilize the DLL + T::regs().dllcr().modify(|w| { + w.set_cal(true); + }); + + trace!("hrtim: wait for dll calibration"); + while !T::regs().isr().read().dllrdy() {} + + trace!("hrtim: dll calibration complete"); + + // Enable periodic calibration + // Cal must be disabled before we can enable it + T::regs().dllcr().modify(|w| { + w.set_cal(false); + }); + + T::regs().dllcr().modify(|w| { + w.set_calen(true); + w.set_calrte(11); + }); + } + + Self { + _inner: tim, + master: Master { phantom: PhantomData }, + burst_controller: BurstController { phantom: PhantomData }, + ch_a: ChA { phantom: PhantomData }, + ch_b: ChB { phantom: PhantomData }, + ch_c: ChC { phantom: PhantomData }, + ch_d: ChD { phantom: PhantomData }, + ch_e: ChE { phantom: PhantomData }, + #[cfg(hrtim_v2)] + ch_f: ChF { phantom: PhantomData }, + } + } +} + +/// HRTIM PWM pin. +pub struct PwmPin<'d, T, C> { + _pin: Flex<'d>, + phantom: PhantomData<(T, C)>, +} + +/// HRTIM complementary PWM pin. +pub struct ComplementaryPwmPin<'d, T, C> { + _pin: Flex<'d>, + phantom: PhantomData<(T, C)>, +} + +macro_rules! channel_impl { + ($new_chx:ident, $new_chx_with_config:ident, $channel:tt, $ch_num:expr, $pin_trait:ident, $complementary_pin_trait:ident) => { + impl<'d, T: Instance> PwmPin<'d, T, $channel> { + #[doc = concat!("Create a new ", stringify!($channel), " PWM pin instance.")] + pub fn $new_chx(pin: Peri<'d, impl $pin_trait>) -> Self { + critical_section::with(|_| { + pin.set_low(); + set_as_af!(pin, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + }); + PwmPin { + _pin: Flex::new(pin), + phantom: PhantomData, + } + } + + #[doc = concat!("Create a new ", stringify!($channel), " PWM pin instance with a specific configuration.")] + pub fn $new_chx_with_config( + pin: Peri<'d, impl $pin_trait>, + pin_config: PwmPinConfig, + ) -> Self { + critical_section::with(|_| { + pin.set_low(); + set_as_af!(pin, AfType::output(pin_config.output_type, pin_config.speed)); + }); + PwmPin { + _pin: Flex::new(pin), + phantom: PhantomData, + } + } + } + + impl<'d, T: Instance> ComplementaryPwmPin<'d, T, $channel> { + #[doc = concat!("Create a new ", stringify!($channel), " complementary PWM pin instance.")] + pub fn $new_chx(pin: Peri<'d, impl $complementary_pin_trait>) -> Self { + critical_section::with(|_| { + pin.set_low(); + set_as_af!(pin, AfType::output(OutputType::PushPull, Speed::VeryHigh)); + }); + ComplementaryPwmPin { + _pin: Flex::new(pin), + phantom: PhantomData, + } + } + + #[doc = concat!("Create a new ", stringify!($channel), " complementary PWM pin instance with a specific configuration.")] + pub fn $new_chx_with_config( + pin: Peri<'d, impl $complementary_pin_trait>, + pin_config: PwmPinConfig, + ) -> Self { + critical_section::with(|_| { + pin.set_low(); + set_as_af!(pin, AfType::output(pin_config.output_type, pin_config.speed)); + }); + ComplementaryPwmPin { + _pin: Flex::new(pin), + phantom: PhantomData, + } + } + } + }; +} + +channel_impl!(new_cha, new_cha_with_config, ChA, 0, ChannelAPin, ChannelAComplementaryPin); +channel_impl!(new_chb, new_chb_with_config, ChB, 1, ChannelBPin, ChannelBComplementaryPin); +channel_impl!(new_chc, new_chc_with_config, ChC, 2, ChannelCPin, ChannelCComplementaryPin); +channel_impl!(new_chd, new_chd_with_config, ChD, 3, ChannelDPin, ChannelDComplementaryPin); +channel_impl!(new_che, new_che_with_config, ChE, 4, ChannelEPin, ChannelEComplementaryPin); +#[cfg(hrtim_v2)] +channel_impl!(new_chf, new_chf_with_config, ChF, 5, ChannelFPin, ChannelFComplementaryPin); diff --git a/embassy-stm32/src/hrtim/mod.rs b/embassy-stm32/src/hrtim/mod.rs index 1455bcd817..ae109ed986 100644 --- a/embassy-stm32/src/hrtim/mod.rs +++ b/embassy-stm32/src/hrtim/mod.rs @@ -4,19 +4,14 @@ mod traits; use core::marker::PhantomData; -use embassy_hal_internal::Peri; pub use traits::Instance; +pub mod advanced_pwm; pub mod bridge_converter; pub mod resonant_converter; pub use bridge_converter::BridgeConverter; pub use resonant_converter::ResonantConverter; -use crate::gpio::{AfType, Flex, OutputType, Speed}; -use crate::rcc; -use crate::time::Hertz; -pub use crate::timer::simple_pwm::PwmPinConfig; - /// HRTIM burst controller instance. pub struct BurstController { phantom: PhantomData, @@ -66,78 +61,8 @@ trait SealedAdvancedChannel { #[allow(private_bounds)] pub trait AdvancedChannel: SealedAdvancedChannel {} -/// HRTIM PWM pin. -pub struct PwmPin<'d, T, C> { - _pin: Flex<'d>, - phantom: PhantomData<(T, C)>, -} - -/// HRTIM complementary PWM pin. -pub struct ComplementaryPwmPin<'d, T, C> { - _pin: Flex<'d>, - phantom: PhantomData<(T, C)>, -} - macro_rules! advanced_channel_impl { ($new_chx:ident, $new_chx_with_config:ident, $channel:tt, $ch_num:expr, $pin_trait:ident, $complementary_pin_trait:ident) => { - impl<'d, T: Instance> PwmPin<'d, T, $channel> { - #[doc = concat!("Create a new ", stringify!($channel), " PWM pin instance.")] - pub fn $new_chx(pin: Peri<'d, impl $pin_trait>) -> Self { - critical_section::with(|_| { - pin.set_low(); - set_as_af!(pin, AfType::output(OutputType::PushPull, Speed::VeryHigh)); - }); - PwmPin { - _pin: Flex::new(pin), - phantom: PhantomData, - } - } - - #[doc = concat!("Create a new ", stringify!($channel), " PWM pin instance with a specific configuration.")] - pub fn $new_chx_with_config( - pin: Peri<'d, impl $pin_trait>, - pin_config: PwmPinConfig, - ) -> Self { - critical_section::with(|_| { - pin.set_low(); - set_as_af!(pin, AfType::output(pin_config.output_type, pin_config.speed)); - }); - PwmPin { - _pin: Flex::new(pin), - phantom: PhantomData, - } - } - } - - impl<'d, T: Instance> ComplementaryPwmPin<'d, T, $channel> { - #[doc = concat!("Create a new ", stringify!($channel), " complementary PWM pin instance.")] - pub fn $new_chx(pin: Peri<'d, impl $complementary_pin_trait>) -> Self { - critical_section::with(|_| { - pin.set_low(); - set_as_af!(pin, AfType::output(OutputType::PushPull, Speed::VeryHigh)); - }); - ComplementaryPwmPin { - _pin: Flex::new(pin), - phantom: PhantomData, - } - } - - #[doc = concat!("Create a new ", stringify!($channel), " complementary PWM pin instance with a specific configuration.")] - pub fn $new_chx_with_config( - pin: Peri<'d, impl $complementary_pin_trait>, - pin_config: PwmPinConfig, - ) -> Self { - critical_section::with(|_| { - pin.set_low(); - set_as_af!(pin, AfType::output(pin_config.output_type, pin_config.speed)); - }); - ComplementaryPwmPin { - _pin: Flex::new(pin), - phantom: PhantomData, - } - } - } - impl SealedAdvancedChannel for $channel { fn raw() -> usize { $ch_num @@ -197,92 +122,6 @@ advanced_channel_impl!( ChannelFComplementaryPin ); -/// Struct used to divide a high resolution timer into multiple channels -pub struct AdvancedPwm<'d, T: Instance> { - _inner: Peri<'d, T>, - /// Master instance. - pub master: Master, - /// Burst controller. - pub burst_controller: BurstController, - /// Channel A. - pub ch_a: ChA, - /// Channel B. - pub ch_b: ChB, - /// Channel C. - pub ch_c: ChC, - /// Channel D. - pub ch_d: ChD, - /// Channel E. - pub ch_e: ChE, - /// Channel F. - #[cfg(hrtim_v2)] - pub ch_f: ChF, -} - -impl<'d, T: Instance> AdvancedPwm<'d, T> { - /// Create a new HRTIM driver. - /// - /// This splits the HRTIM into its constituent parts, which you can then use individually. - pub fn new( - tim: Peri<'d, T>, - _cha: Option>>, - _chan: Option>>, - _chb: Option>>, - _chbn: Option>>, - _chc: Option>>, - _chcn: Option>>, - _chd: Option>>, - _chdn: Option>>, - _che: Option>>, - _chen: Option>>, - #[cfg(hrtim_v2)] _chf: Option>>, - #[cfg(hrtim_v2)] _chfn: Option>>, - ) -> Self { - Self::new_inner(tim) - } - - fn new_inner(tim: Peri<'d, T>) -> Self { - rcc::enable_and_reset::(); - - #[cfg(stm32f334)] - if crate::pac::RCC.cfgr3().read().hrtim1sw() == crate::pac::rcc::vals::Timsw::PLL1_P { - // Enable and and stabilize the DLL - T::regs().dllcr().modify(|w| { - w.set_cal(true); - }); - - trace!("hrtim: wait for dll calibration"); - while !T::regs().isr().read().dllrdy() {} - - trace!("hrtim: dll calibration complete"); - - // Enable periodic calibration - // Cal must be disabled before we can enable it - T::regs().dllcr().modify(|w| { - w.set_cal(false); - }); - - T::regs().dllcr().modify(|w| { - w.set_calen(true); - w.set_calrte(11); - }); - } - - Self { - _inner: tim, - master: Master { phantom: PhantomData }, - burst_controller: BurstController { phantom: PhantomData }, - ch_a: ChA { phantom: PhantomData }, - ch_b: ChB { phantom: PhantomData }, - ch_c: ChC { phantom: PhantomData }, - ch_d: ChD { phantom: PhantomData }, - ch_e: ChE { phantom: PhantomData }, - #[cfg(hrtim_v2)] - ch_f: ChF { phantom: PhantomData }, - } - } -} - pin_trait!(ChannelAPin, Instance); pin_trait!(ChannelAComplementaryPin, Instance); pin_trait!(ChannelBPin, Instance); From ab6750b1f034b7b17fc17a7b3a9ce1d4af2e392e Mon Sep 17 00:00:00 2001 From: Hatim Thayyil Date: Tue, 10 Feb 2026 15:04:49 +0000 Subject: [PATCH 03/17] stm32/hrtim: move Instance and Prescaler to module root --- embassy-stm32/src/hrtim/bridge_converter.rs | 3 +- embassy-stm32/src/hrtim/mod.rs | 177 +++++++++++++++++- embassy-stm32/src/hrtim/resonant_converter.rs | 3 +- embassy-stm32/src/hrtim/traits.rs | 172 ----------------- 4 files changed, 175 insertions(+), 180 deletions(-) delete mode 100644 embassy-stm32/src/hrtim/traits.rs diff --git a/embassy-stm32/src/hrtim/bridge_converter.rs b/embassy-stm32/src/hrtim/bridge_converter.rs index c129c942ab..745c7c2ec3 100644 --- a/embassy-stm32/src/hrtim/bridge_converter.rs +++ b/embassy-stm32/src/hrtim/bridge_converter.rs @@ -2,8 +2,7 @@ use core::marker::PhantomData; -use super::traits::Instance; -use super::AdvancedChannel; +use super::{AdvancedChannel, Instance}; use crate::time::Hertz; /// Fixed-frequency bridge converter driver. diff --git a/embassy-stm32/src/hrtim/mod.rs b/embassy-stm32/src/hrtim/mod.rs index ae109ed986..bb1e8334af 100644 --- a/embassy-stm32/src/hrtim/mod.rs +++ b/embassy-stm32/src/hrtim/mod.rs @@ -1,17 +1,18 @@ //! High Resolution Timer (HRTIM) -mod traits; - use core::marker::PhantomData; -pub use traits::Instance; - pub mod advanced_pwm; pub mod bridge_converter; pub mod resonant_converter; pub use bridge_converter::BridgeConverter; pub use resonant_converter::ResonantConverter; +use embassy_hal_internal::PeripheralType; + +use crate::rcc::RccPeripheral; +use crate::time::Hertz; + /// HRTIM burst controller instance. pub struct BurstController { phantom: PhantomData, @@ -136,3 +137,171 @@ pin_trait!(ChannelEComplementaryPin, Instance); pin_trait!(ChannelFPin, Instance); #[cfg(hrtim_v2)] pin_trait!(ChannelFComplementaryPin, Instance); + +#[repr(u8)] +#[derive(Clone, Copy)] +pub(crate) enum Prescaler { + Div1 = 1, + Div2 = 2, + Div4 = 4, + Div8 = 8, + Div16 = 16, + Div32 = 32, + Div64 = 64, + Div128 = 128, +} + +impl From for u8 { + fn from(val: Prescaler) -> Self { + match val { + Prescaler::Div1 => 0b000, + Prescaler::Div2 => 0b001, + Prescaler::Div4 => 0b010, + Prescaler::Div8 => 0b011, + Prescaler::Div16 => 0b100, + Prescaler::Div32 => 0b101, + Prescaler::Div64 => 0b110, + Prescaler::Div128 => 0b111, + } + } +} + +impl From for Prescaler { + fn from(val: u8) -> Self { + match val { + 0b000 => Prescaler::Div1, + 0b001 => Prescaler::Div2, + 0b010 => Prescaler::Div4, + 0b011 => Prescaler::Div8, + 0b100 => Prescaler::Div16, + 0b101 => Prescaler::Div32, + 0b110 => Prescaler::Div64, + 0b111 => Prescaler::Div128, + _ => unreachable!(), + } + } +} + +impl Prescaler { + pub fn compute_min_high_res(val: u32) -> Self { + *[ + Prescaler::Div1, + Prescaler::Div2, + Prescaler::Div4, + Prescaler::Div8, + Prescaler::Div16, + Prescaler::Div32, + Prescaler::Div64, + Prescaler::Div128, + ] + .iter() + .skip_while(|psc| **psc as u32 <= val) + .next() + .unwrap() + } + + pub fn compute_min_low_res(val: u32) -> Self { + *[Prescaler::Div32, Prescaler::Div64, Prescaler::Div128] + .iter() + .skip_while(|psc| **psc as u32 <= val) + .next() + .unwrap() + } +} + +pub(crate) trait SealedInstance: RccPeripheral { + fn regs() -> crate::pac::hrtim::Hrtim; + + #[allow(unused)] + fn set_master_frequency(frequency: Hertz) { + let f = frequency.0; + + // TODO: wire up HRTIM to the RCC mux infra. + //#[cfg(stm32f334)] + //let timer_f = unsafe { crate::rcc::get_freqs() }.hrtim.unwrap_or(Self::frequency()).0; + //#[cfg(not(stm32f334))] + let timer_f = Self::frequency().0; + + let psc_min = (timer_f / f) / (u16::MAX as u32 / 32); + let psc = if Self::regs().isr().read().dllrdy() { + Prescaler::compute_min_high_res(psc_min) + } else { + Prescaler::compute_min_low_res(psc_min) + }; + + let timer_f = 32 * (timer_f / psc as u32); + let per: u16 = (timer_f / f) as u16; + + let regs = Self::regs(); + + regs.mcr().modify(|w| w.set_ckpsc(psc.into())); + regs.mper().modify(|w| w.set_mper(per)); + } + + fn set_channel_frequency(channel: usize, frequency: Hertz) { + let f = frequency.0; + + // TODO: wire up HRTIM to the RCC mux infra. + //#[cfg(stm32f334)] + //let timer_f = unsafe { crate::rcc::get_freqs() }.hrtim.unwrap_or(Self::frequency()).0; + //#[cfg(not(stm32f334))] + let timer_f = Self::frequency().0; + + let psc_min = (timer_f / f) / (u16::MAX as u32 / 32); + let psc = if Self::regs().isr().read().dllrdy() { + Prescaler::compute_min_high_res(psc_min) + } else { + Prescaler::compute_min_low_res(psc_min) + }; + + let timer_f = 32 * (timer_f / psc as u32); + let per: u16 = (timer_f / f) as u16; + + let regs = Self::regs(); + + regs.tim(channel).cr().modify(|w| w.set_ckpsc(psc.into())); + regs.tim(channel).per().modify(|w| w.set_per(per)); + } + + /// Set the dead time as a proportion of max_duty + fn set_channel_dead_time(channel: usize, dead_time: u16) { + let regs = Self::regs(); + + let channel_psc: Prescaler = regs.tim(channel).cr().read().ckpsc().into(); + + // The dead-time base clock runs 4 times slower than the hrtim base clock + // u9::MAX = 511 + let psc_min = (channel_psc as u32 * dead_time as u32) / (4 * 511); + let psc = if Self::regs().isr().read().dllrdy() { + Prescaler::compute_min_high_res(psc_min) + } else { + Prescaler::compute_min_low_res(psc_min) + }; + + let dt_val = (psc as u32 * dead_time as u32) / (4 * channel_psc as u32); + + regs.tim(channel).dt().modify(|w| { + w.set_dtprsc(psc.into()); + w.set_dtf(dt_val as u16); + w.set_dtr(dt_val as u16); + }); + } +} + +/// HRTIM instance trait. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + PeripheralType + 'static {} + +foreach_interrupt! { + ($inst:ident, hrtim, HRTIM, MASTER, $irq:ident) => { + impl SealedInstance for crate::peripherals::$inst { + fn regs() -> crate::pac::hrtim::Hrtim { + crate::pac::$inst + } + } + + impl Instance for crate::peripherals::$inst { + + } + }; +} diff --git a/embassy-stm32/src/hrtim/resonant_converter.rs b/embassy-stm32/src/hrtim/resonant_converter.rs index f2811a7654..1f87fb0d95 100644 --- a/embassy-stm32/src/hrtim/resonant_converter.rs +++ b/embassy-stm32/src/hrtim/resonant_converter.rs @@ -2,8 +2,7 @@ use core::marker::PhantomData; -use super::traits::Instance; -use super::AdvancedChannel; +use super::{AdvancedChannel, Instance}; use crate::time::Hertz; /// Variable-frequency resonant converter driver. diff --git a/embassy-stm32/src/hrtim/traits.rs b/embassy-stm32/src/hrtim/traits.rs deleted file mode 100644 index 6c0661146a..0000000000 --- a/embassy-stm32/src/hrtim/traits.rs +++ /dev/null @@ -1,172 +0,0 @@ -use embassy_hal_internal::PeripheralType; - -use crate::rcc::RccPeripheral; -use crate::time::Hertz; - -#[repr(u8)] -#[derive(Clone, Copy)] -pub(crate) enum Prescaler { - Div1 = 1, - Div2 = 2, - Div4 = 4, - Div8 = 8, - Div16 = 16, - Div32 = 32, - Div64 = 64, - Div128 = 128, -} - -impl From for u8 { - fn from(val: Prescaler) -> Self { - match val { - Prescaler::Div1 => 0b000, - Prescaler::Div2 => 0b001, - Prescaler::Div4 => 0b010, - Prescaler::Div8 => 0b011, - Prescaler::Div16 => 0b100, - Prescaler::Div32 => 0b101, - Prescaler::Div64 => 0b110, - Prescaler::Div128 => 0b111, - } - } -} - -impl From for Prescaler { - fn from(val: u8) -> Self { - match val { - 0b000 => Prescaler::Div1, - 0b001 => Prescaler::Div2, - 0b010 => Prescaler::Div4, - 0b011 => Prescaler::Div8, - 0b100 => Prescaler::Div16, - 0b101 => Prescaler::Div32, - 0b110 => Prescaler::Div64, - 0b111 => Prescaler::Div128, - _ => unreachable!(), - } - } -} - -impl Prescaler { - pub fn compute_min_high_res(val: u32) -> Self { - *[ - Prescaler::Div1, - Prescaler::Div2, - Prescaler::Div4, - Prescaler::Div8, - Prescaler::Div16, - Prescaler::Div32, - Prescaler::Div64, - Prescaler::Div128, - ] - .iter() - .skip_while(|psc| **psc as u32 <= val) - .next() - .unwrap() - } - - pub fn compute_min_low_res(val: u32) -> Self { - *[Prescaler::Div32, Prescaler::Div64, Prescaler::Div128] - .iter() - .skip_while(|psc| **psc as u32 <= val) - .next() - .unwrap() - } -} - -pub(crate) trait SealedInstance: RccPeripheral { - fn regs() -> crate::pac::hrtim::Hrtim; - - #[allow(unused)] - fn set_master_frequency(frequency: Hertz) { - let f = frequency.0; - - // TODO: wire up HRTIM to the RCC mux infra. - //#[cfg(stm32f334)] - //let timer_f = unsafe { crate::rcc::get_freqs() }.hrtim.unwrap_or(Self::frequency()).0; - //#[cfg(not(stm32f334))] - let timer_f = Self::frequency().0; - - let psc_min = (timer_f / f) / (u16::MAX as u32 / 32); - let psc = if Self::regs().isr().read().dllrdy() { - Prescaler::compute_min_high_res(psc_min) - } else { - Prescaler::compute_min_low_res(psc_min) - }; - - let timer_f = 32 * (timer_f / psc as u32); - let per: u16 = (timer_f / f) as u16; - - let regs = Self::regs(); - - regs.mcr().modify(|w| w.set_ckpsc(psc.into())); - regs.mper().modify(|w| w.set_mper(per)); - } - - fn set_channel_frequency(channel: usize, frequency: Hertz) { - let f = frequency.0; - - // TODO: wire up HRTIM to the RCC mux infra. - //#[cfg(stm32f334)] - //let timer_f = unsafe { crate::rcc::get_freqs() }.hrtim.unwrap_or(Self::frequency()).0; - //#[cfg(not(stm32f334))] - let timer_f = Self::frequency().0; - - let psc_min = (timer_f / f) / (u16::MAX as u32 / 32); - let psc = if Self::regs().isr().read().dllrdy() { - Prescaler::compute_min_high_res(psc_min) - } else { - Prescaler::compute_min_low_res(psc_min) - }; - - let timer_f = 32 * (timer_f / psc as u32); - let per: u16 = (timer_f / f) as u16; - - let regs = Self::regs(); - - regs.tim(channel).cr().modify(|w| w.set_ckpsc(psc.into())); - regs.tim(channel).per().modify(|w| w.set_per(per)); - } - - /// Set the dead time as a proportion of max_duty - fn set_channel_dead_time(channel: usize, dead_time: u16) { - let regs = Self::regs(); - - let channel_psc: Prescaler = regs.tim(channel).cr().read().ckpsc().into(); - - // The dead-time base clock runs 4 times slower than the hrtim base clock - // u9::MAX = 511 - let psc_min = (channel_psc as u32 * dead_time as u32) / (4 * 511); - let psc = if Self::regs().isr().read().dllrdy() { - Prescaler::compute_min_high_res(psc_min) - } else { - Prescaler::compute_min_low_res(psc_min) - }; - - let dt_val = (psc as u32 * dead_time as u32) / (4 * channel_psc as u32); - - regs.tim(channel).dt().modify(|w| { - w.set_dtprsc(psc.into()); - w.set_dtf(dt_val as u16); - w.set_dtr(dt_val as u16); - }); - } -} - -/// HRTIM instance trait. -#[allow(private_bounds)] -pub trait Instance: SealedInstance + PeripheralType + 'static {} - -foreach_interrupt! { - ($inst:ident, hrtim, HRTIM, MASTER, $irq:ident) => { - impl SealedInstance for crate::peripherals::$inst { - fn regs() -> crate::pac::hrtim::Hrtim { - crate::pac::$inst - } - } - - impl Instance for crate::peripherals::$inst { - - } - }; -} From 4800a6e4273b574f3a20b600bf2e12e0f6fc5f8a Mon Sep 17 00:00:00 2001 From: Hatim Thayyil Date: Tue, 10 Feb 2026 15:23:25 +0000 Subject: [PATCH 04/17] stm32/hrtim: split off AdvancedChannel --- embassy-stm32/src/hrtim/advanced_channel.rs | 87 ++++++++++++ embassy-stm32/src/hrtim/advanced_pwm.rs | 29 +++- embassy-stm32/src/hrtim/bridge_converter.rs | 35 ++--- embassy-stm32/src/hrtim/mod.rs | 132 ++---------------- embassy-stm32/src/hrtim/resonant_converter.rs | 16 +-- examples/stm32f334/src/bin/pwm.rs | 2 +- 6 files changed, 152 insertions(+), 149 deletions(-) create mode 100644 embassy-stm32/src/hrtim/advanced_channel.rs diff --git a/embassy-stm32/src/hrtim/advanced_channel.rs b/embassy-stm32/src/hrtim/advanced_channel.rs new file mode 100644 index 0000000000..f74e20d873 --- /dev/null +++ b/embassy-stm32/src/hrtim/advanced_channel.rs @@ -0,0 +1,87 @@ +//! AdvancedChannel + +#[cfg(hrtim_v2)] +use super::ChF; +use super::{ChA, ChB, ChC, ChD, ChE, Instance, Prescaler}; +use crate::time::Hertz; + +trait SealedAdvancedChannel { + fn raw() -> usize; +} + +/// Advanced channel instance trait. +#[allow(private_bounds)] +pub trait AdvancedChannel: SealedAdvancedChannel { + /// Channel index + fn index() -> usize { + Self::raw() + } + /// Set channel frequency + fn set_channel_frequency(channel: usize, frequency: Hertz) { + let f = frequency.0; + + // TODO: wire up HRTIM to the RCC mux infra. + //#[cfg(stm32f334)] + //let timer_f = unsafe { crate::rcc::get_freqs() }.hrtim.unwrap_or(T::frequency()).0; + //#[cfg(not(stm32f334))] + let timer_f = T::frequency().0; + + let psc_min = (timer_f / f) / (u16::MAX as u32 / 32); + let psc = if T::regs().isr().read().dllrdy() { + Prescaler::compute_min_high_res(psc_min) + } else { + Prescaler::compute_min_low_res(psc_min) + }; + + let timer_f = 32 * (timer_f / psc as u32); + let per: u16 = (timer_f / f) as u16; + + let regs = T::regs(); + + regs.tim(channel).cr().modify(|w| w.set_ckpsc(psc.into())); + regs.tim(channel).per().modify(|w| w.set_per(per)); + } + + /// Set the dead time as a proportion of max_duty + fn set_channel_dead_time(channel: usize, dead_time: u16) { + let regs = T::regs(); + + let channel_psc: Prescaler = regs.tim(channel).cr().read().ckpsc().into(); + + // The dead-time base clock runs 4 times slower than the hrtim base clock + // u9::MAX = 511 + let psc_min = (channel_psc as u32 * dead_time as u32) / (4 * 511); + let psc = if T::regs().isr().read().dllrdy() { + Prescaler::compute_min_high_res(psc_min) + } else { + Prescaler::compute_min_low_res(psc_min) + }; + + let dt_val = (psc as u32 * dead_time as u32) / (4 * channel_psc as u32); + + regs.tim(channel).dt().modify(|w| { + w.set_dtprsc(psc.into()); + w.set_dtf(dt_val as u16); + w.set_dtr(dt_val as u16); + }); + } +} + +macro_rules! advanced_channel_impl { + ($new_chx:ident, $channel:tt, $ch_num:expr, $pin_trait:ident, $complementary_pin_trait:ident) => { + impl SealedAdvancedChannel for $channel { + fn raw() -> usize { + $ch_num + } + } + impl AdvancedChannel for $channel {} + }; +} + +advanced_channel_impl!(new_cha, ChA, 0, ChannelAPin, ChannelAComplementaryPin); +advanced_channel_impl!(new_chb, ChB, 1, ChannelBPin, ChannelBComplementaryPin); +advanced_channel_impl!(new_chc, ChC, 2, ChannelCPin, ChannelCComplementaryPin); +advanced_channel_impl!(new_chd, ChD, 3, ChannelDPin, ChannelDComplementaryPin); +advanced_channel_impl!(new_che, ChE, 4, ChannelEPin, ChannelEComplementaryPin); +#[cfg(hrtim_v2)] +advanced_channel_impl!(new_chf, ChF, 5, ChannelFPin, ChannelFComplementaryPin); diff --git a/embassy-stm32/src/hrtim/advanced_pwm.rs b/embassy-stm32/src/hrtim/advanced_pwm.rs index 5cd6ec3c3f..3bc1bb83e3 100644 --- a/embassy-stm32/src/hrtim/advanced_pwm.rs +++ b/embassy-stm32/src/hrtim/advanced_pwm.rs @@ -7,7 +7,7 @@ use embassy_hal_internal::Peri; use crate::gpio::{AfType, Flex, OutputType, Speed}; pub use crate::timer::simple_pwm::PwmPinConfig; -use super::{BurstController, ChA, ChB, ChC, ChD, ChE, Instance, Master}; +use super::{BurstController, ChA, ChB, ChC, ChD, ChE, Instance, Master, Prescaler}; #[cfg(hrtim_v2)] use super::{ChF, ChannelFComplementaryPin, ChannelFPin}; use super::{ @@ -17,6 +17,7 @@ use super::{ use super::{ChannelAPin, ChannelBPin, ChannelCPin, ChannelDPin, ChannelEPin}; use crate::rcc; +use crate::time::Hertz; /// Struct used to divide a high resolution timer into multiple channels pub struct AdvancedPwm<'d, T: Instance> { @@ -102,6 +103,32 @@ impl<'d, T: Instance> AdvancedPwm<'d, T> { ch_f: ChF { phantom: PhantomData }, } } + + /// Set master frequency + pub fn set_master_frequency(&mut self, frequency: Hertz) { + let f = frequency.0; + + // TODO: wire up HRTIM to the RCC mux infra. + //#[cfg(stm32f334)] + //let timer_f = unsafe { crate::rcc::get_freqs() }.hrtim.unwrap_or(T::frequency()).0; + //#[cfg(not(stm32f334))] + let timer_f = T::frequency().0; + + let psc_min = (timer_f / f) / (u16::MAX as u32 / 32); + let psc = if T::regs().isr().read().dllrdy() { + Prescaler::compute_min_high_res(psc_min) + } else { + Prescaler::compute_min_low_res(psc_min) + }; + + let timer_f = 32 * (timer_f / psc as u32); + let per: u16 = (timer_f / f) as u16; + + let regs = T::regs(); + + regs.mcr().modify(|w| w.set_ckpsc(psc.into())); + regs.mper().modify(|w| w.set_mper(per)); + } } /// HRTIM PWM pin. diff --git a/embassy-stm32/src/hrtim/bridge_converter.rs b/embassy-stm32/src/hrtim/bridge_converter.rs index 745c7c2ec3..eea968a331 100644 --- a/embassy-stm32/src/hrtim/bridge_converter.rs +++ b/embassy-stm32/src/hrtim/bridge_converter.rs @@ -26,10 +26,10 @@ pub struct BridgeConverter> { impl> BridgeConverter { /// Create a new HRTIM bridge converter driver. pub fn new(_channel: C, frequency: Hertz) -> Self { - T::set_channel_frequency(C::raw(), frequency); + C::set_channel_frequency(C::index(), frequency); // Always enable preload - T::regs().tim(C::raw()).cr().modify(|w| { + T::regs().tim(C::index()).cr().modify(|w| { w.set_preen(true); w.set_repu(true); w.set_cont(true); @@ -37,8 +37,8 @@ impl> BridgeConverter { // Enable timer outputs T::regs().oenr().modify(|w| { - w.set_t1oen(C::raw(), true); - w.set_t2oen(C::raw(), true); + w.set_t1oen(C::index(), true); + w.set_t2oen(C::index(), true); }); // The dead-time generation unit cannot be used because it forces the other output @@ -46,16 +46,16 @@ impl> BridgeConverter { // Therefore, software-implemented dead time must be used when setting the duty cycles // Set output 1 to active on a period event - T::regs().tim(C::raw()).setr(0).modify(|w| w.set_per(true)); + T::regs().tim(C::index()).setr(0).modify(|w| w.set_per(true)); // Set output 1 to inactive on a compare 1 event - T::regs().tim(C::raw()).rstr(0).modify(|w| w.set_cmp(0, true)); + T::regs().tim(C::index()).rstr(0).modify(|w| w.set_cmp(0, true)); // Set output 2 to active on a compare 2 event - T::regs().tim(C::raw()).setr(1).modify(|w| w.set_cmp(1, true)); + T::regs().tim(C::index()).setr(1).modify(|w| w.set_cmp(1, true)); // Set output 2 to inactive on a compare 3 event - T::regs().tim(C::raw()).rstr(1).modify(|w| w.set_cmp(2, true)); + T::regs().tim(C::index()).rstr(1).modify(|w| w.set_cmp(2, true)); Self { timer: PhantomData, @@ -69,17 +69,17 @@ impl> BridgeConverter { /// Start HRTIM. pub fn start(&mut self) { - T::regs().mcr().modify(|w| w.set_tcen(C::raw(), true)); + T::regs().mcr().modify(|w| w.set_tcen(C::index(), true)); } /// Stop HRTIM. pub fn stop(&mut self) { - T::regs().mcr().modify(|w| w.set_tcen(C::raw(), false)); + T::regs().mcr().modify(|w| w.set_tcen(C::index(), false)); } /// Enable burst mode. pub fn enable_burst_mode(&mut self) { - T::regs().tim(C::raw()).outr().modify(|w| { + T::regs().tim(C::index()).outr().modify(|w| { // Enable Burst Mode w.set_idlem(0, true); w.set_idlem(1, true); @@ -92,7 +92,7 @@ impl> BridgeConverter { /// Disable burst mode. pub fn disable_burst_mode(&mut self) { - T::regs().tim(C::raw()).outr().modify(|w| { + T::regs().tim(C::index()).outr().modify(|w| { // Disable Burst Mode w.set_idlem(0, false); w.set_idlem(1, false); @@ -102,9 +102,12 @@ impl> BridgeConverter { fn update_primary_duty_or_dead_time(&mut self) { self.min_secondary_duty = self.primary_duty + self.dead_time; - T::regs().tim(C::raw()).cmp(0).modify(|w| w.set_cmp(self.primary_duty)); T::regs() - .tim(C::raw()) + .tim(C::index()) + .cmp(0) + .modify(|w| w.set_cmp(self.primary_duty)); + T::regs() + .tim(C::index()) .cmp(1) .modify(|w| w.set_cmp(self.min_secondary_duty)); } @@ -118,7 +121,7 @@ impl> BridgeConverter { /// Get the maximum compare value of a duty cycle pub fn get_max_compare_value(&mut self) -> u16 { - T::regs().tim(C::raw()).per().read().per() + T::regs().tim(C::index()).per().read().per() } /// The primary duty is the period in which the primary switch is active @@ -143,6 +146,6 @@ impl> BridgeConverter { secondary_duty }; - T::regs().tim(C::raw()).cmp(2).modify(|w| w.set_cmp(secondary_duty)); + T::regs().tim(C::index()).cmp(2).modify(|w| w.set_cmp(secondary_duty)); } } diff --git a/embassy-stm32/src/hrtim/mod.rs b/embassy-stm32/src/hrtim/mod.rs index bb1e8334af..9968bae85f 100644 --- a/embassy-stm32/src/hrtim/mod.rs +++ b/embassy-stm32/src/hrtim/mod.rs @@ -2,14 +2,18 @@ use core::marker::PhantomData; -pub mod advanced_pwm; -pub mod bridge_converter; -pub mod resonant_converter; +use embassy_hal_internal::PeripheralType; + +mod advanced_channel; +mod advanced_pwm; +mod bridge_converter; +mod resonant_converter; + +pub use advanced_channel::AdvancedChannel; +pub use advanced_pwm::{AdvancedPwm, ComplementaryPwmPin, PwmPin}; pub use bridge_converter::BridgeConverter; pub use resonant_converter::ResonantConverter; -use embassy_hal_internal::PeripheralType; - use crate::rcc::RccPeripheral; use crate::time::Hertz; @@ -54,75 +58,6 @@ pub struct ChF { phantom: PhantomData, } -trait SealedAdvancedChannel { - fn raw() -> usize; -} - -/// Advanced channel instance trait. -#[allow(private_bounds)] -pub trait AdvancedChannel: SealedAdvancedChannel {} - -macro_rules! advanced_channel_impl { - ($new_chx:ident, $new_chx_with_config:ident, $channel:tt, $ch_num:expr, $pin_trait:ident, $complementary_pin_trait:ident) => { - impl SealedAdvancedChannel for $channel { - fn raw() -> usize { - $ch_num - } - } - impl AdvancedChannel for $channel {} - }; -} - -advanced_channel_impl!( - new_cha, - new_cha_with_config, - ChA, - 0, - ChannelAPin, - ChannelAComplementaryPin -); -advanced_channel_impl!( - new_chb, - new_chb_with_config, - ChB, - 1, - ChannelBPin, - ChannelBComplementaryPin -); -advanced_channel_impl!( - new_chc, - new_chc_with_config, - ChC, - 2, - ChannelCPin, - ChannelCComplementaryPin -); -advanced_channel_impl!( - new_chd, - new_chd_with_config, - ChD, - 3, - ChannelDPin, - ChannelDComplementaryPin -); -advanced_channel_impl!( - new_che, - new_che_with_config, - ChE, - 4, - ChannelEPin, - ChannelEComplementaryPin -); -#[cfg(hrtim_v2)] -advanced_channel_impl!( - new_chf, - new_chf_with_config, - ChF, - 5, - ChannelFPin, - ChannelFComplementaryPin -); - pin_trait!(ChannelAPin, Instance); pin_trait!(ChannelAComplementaryPin, Instance); pin_trait!(ChannelBPin, Instance); @@ -237,55 +172,6 @@ pub(crate) trait SealedInstance: RccPeripheral { regs.mcr().modify(|w| w.set_ckpsc(psc.into())); regs.mper().modify(|w| w.set_mper(per)); } - - fn set_channel_frequency(channel: usize, frequency: Hertz) { - let f = frequency.0; - - // TODO: wire up HRTIM to the RCC mux infra. - //#[cfg(stm32f334)] - //let timer_f = unsafe { crate::rcc::get_freqs() }.hrtim.unwrap_or(Self::frequency()).0; - //#[cfg(not(stm32f334))] - let timer_f = Self::frequency().0; - - let psc_min = (timer_f / f) / (u16::MAX as u32 / 32); - let psc = if Self::regs().isr().read().dllrdy() { - Prescaler::compute_min_high_res(psc_min) - } else { - Prescaler::compute_min_low_res(psc_min) - }; - - let timer_f = 32 * (timer_f / psc as u32); - let per: u16 = (timer_f / f) as u16; - - let regs = Self::regs(); - - regs.tim(channel).cr().modify(|w| w.set_ckpsc(psc.into())); - regs.tim(channel).per().modify(|w| w.set_per(per)); - } - - /// Set the dead time as a proportion of max_duty - fn set_channel_dead_time(channel: usize, dead_time: u16) { - let regs = Self::regs(); - - let channel_psc: Prescaler = regs.tim(channel).cr().read().ckpsc().into(); - - // The dead-time base clock runs 4 times slower than the hrtim base clock - // u9::MAX = 511 - let psc_min = (channel_psc as u32 * dead_time as u32) / (4 * 511); - let psc = if Self::regs().isr().read().dllrdy() { - Prescaler::compute_min_high_res(psc_min) - } else { - Prescaler::compute_min_low_res(psc_min) - }; - - let dt_val = (psc as u32 * dead_time as u32) / (4 * channel_psc as u32); - - regs.tim(channel).dt().modify(|w| { - w.set_dtprsc(psc.into()); - w.set_dtf(dt_val as u16); - w.set_dtr(dt_val as u16); - }); - } } /// HRTIM instance trait. diff --git a/embassy-stm32/src/hrtim/resonant_converter.rs b/embassy-stm32/src/hrtim/resonant_converter.rs index 1f87fb0d95..dbe69cdb24 100644 --- a/embassy-stm32/src/hrtim/resonant_converter.rs +++ b/embassy-stm32/src/hrtim/resonant_converter.rs @@ -20,10 +20,10 @@ pub struct ResonantConverter> { impl> ResonantConverter { /// Create a new variable-frequency resonant converter driver. pub fn new(_channel: C, min_frequency: Hertz, max_frequency: Hertz) -> Self { - T::set_channel_frequency(C::raw(), min_frequency); + C::set_channel_frequency(C::index(), min_frequency); // Always enable preload - T::regs().tim(C::raw()).cr().modify(|w| { + T::regs().tim(C::index()).cr().modify(|w| { w.set_preen(true); w.set_repu(true); @@ -33,15 +33,15 @@ impl> ResonantConverter { // Enable timer outputs T::regs().oenr().modify(|w| { - w.set_t1oen(C::raw(), true); - w.set_t2oen(C::raw(), true); + w.set_t1oen(C::index(), true); + w.set_t2oen(C::index(), true); }); // Dead-time generator can be used in this case because the primary fets // of a resonant converter are always complementary - T::regs().tim(C::raw()).outr().modify(|w| w.set_dten(true)); + T::regs().tim(C::index()).outr().modify(|w| w.set_dten(true)); - let max_period = T::regs().tim(C::raw()).per().read().per(); + let max_period = T::regs().tim(C::index()).per().read().per(); let min_period = max_period * (min_frequency.0 / max_frequency.0) as u16; Self { @@ -54,7 +54,7 @@ impl> ResonantConverter { /// Set the dead time as a proportion of the maximum compare value pub fn set_dead_time(&mut self, value: u16) { - T::set_channel_dead_time(C::raw(), value); + C::set_channel_dead_time(C::index(), value); } /// Set the timer period. @@ -62,7 +62,7 @@ impl> ResonantConverter { assert!(period < self.max_period); assert!(period > self.min_period); - T::regs().tim(C::raw()).per().modify(|w| w.set_per(period)); + T::regs().tim(C::index()).per().modify(|w| w.set_per(period)); } /// Get the minimum compare value of a duty cycle diff --git a/examples/stm32f334/src/bin/pwm.rs b/examples/stm32f334/src/bin/pwm.rs index cb9f214943..71b676fd5c 100644 --- a/examples/stm32f334/src/bin/pwm.rs +++ b/examples/stm32f334/src/bin/pwm.rs @@ -3,8 +3,8 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_stm32::hrtim::BridgeConverter; use embassy_stm32::hrtim::*; +use embassy_stm32::hrtim::{AdvancedPwm, BridgeConverter, ComplementaryPwmPin, PwmPin}; use embassy_stm32::time::{khz, mhz}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; From e12180b10e405b4ed38f4a930c02088334d0fd4b Mon Sep 17 00:00:00 2001 From: Hatim Thayyil Date: Tue, 10 Feb 2026 15:25:28 +0000 Subject: [PATCH 05/17] add stm32g474 example --- examples/stm32g474/src/bin/hrtim.rs | 57 +++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 examples/stm32g474/src/bin/hrtim.rs diff --git a/examples/stm32g474/src/bin/hrtim.rs b/examples/stm32g474/src/bin/hrtim.rs new file mode 100644 index 0000000000..1d335761be --- /dev/null +++ b/examples/stm32g474/src/bin/hrtim.rs @@ -0,0 +1,57 @@ +#![no_std] +#![no_main] + +use defmt::*; + +use embassy_executor::Spawner; +use embassy_stm32::hrtim::{AdvancedPwm, BridgeConverter, ComplementaryPwmPin, PwmPin}; +use embassy_stm32::time::Hertz; +use embassy_stm32::Config; +use embassy_time::Timer; + +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Config::default()); + + info!("Hello World!"); + + let ch1 = PwmPin::new_cha(p.PA8); + let ch1n = ComplementaryPwmPin::new_cha(p.PA9); + let pwm = AdvancedPwm::new( + p.HRTIM1, + Some(ch1), + Some(ch1n), + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + ); + + info!("pwm constructed"); + + let mut buck_converter = BridgeConverter::new(pwm.ch_a, Hertz::khz(5)); + + let max_duty = buck_converter.get_max_compare_value(); + + info!("max compare value: {}", max_duty); + + buck_converter.set_dead_time(max_duty / 20); + buck_converter.set_primary_duty(max_duty / 2); + buck_converter.set_secondary_duty(3 * max_duty / 4); + + buck_converter.start(); + + Timer::after_millis(500).await; + + info!("end program"); + + cortex_m::asm::bkpt(); +} From 043dccff2f028d90734b45761a02d82024a4fe07 Mon Sep 17 00:00:00 2001 From: Hatim Thayyil Date: Tue, 10 Feb 2026 15:47:29 +0000 Subject: [PATCH 06/17] add docs to Prescaler and defmt --- embassy-stm32/src/hrtim/mod.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/embassy-stm32/src/hrtim/mod.rs b/embassy-stm32/src/hrtim/mod.rs index 9968bae85f..6ef550e0e3 100644 --- a/embassy-stm32/src/hrtim/mod.rs +++ b/embassy-stm32/src/hrtim/mod.rs @@ -73,16 +73,26 @@ pin_trait!(ChannelFPin, Instance); #[cfg(hrtim_v2)] pin_trait!(ChannelFComplementaryPin, Instance); +/// Prescaler #[repr(u8)] #[derive(Clone, Copy)] -pub(crate) enum Prescaler { +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Prescaler { + /// Prescaler ratio 1 Div1 = 1, + /// Prescaler ratio 2 Div2 = 2, + /// Prescaler ratio 4 Div4 = 4, + /// Prescaler ratio 8 Div8 = 8, + /// Prescaler ratio 16 Div16 = 16, + /// Prescaler ratio 32 Div32 = 32, + /// Prescaler ratio 64 Div64 = 64, + /// Prescaler ratio 128 Div128 = 128, } From c998b3465dd2fe70bc7cfcde7a264495e1d178bb Mon Sep 17 00:00:00 2001 From: Hatim Thayyil Date: Tue, 10 Feb 2026 15:49:20 +0000 Subject: [PATCH 07/17] stm32/hrtim: fix overflow issue with hrtim Frequency calculation. 32*168MHz exceeds u32 repr --- embassy-stm32/src/hrtim/advanced_channel.rs | 4 ++-- embassy-stm32/src/hrtim/advanced_pwm.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/embassy-stm32/src/hrtim/advanced_channel.rs b/embassy-stm32/src/hrtim/advanced_channel.rs index f74e20d873..3209b2c38f 100644 --- a/embassy-stm32/src/hrtim/advanced_channel.rs +++ b/embassy-stm32/src/hrtim/advanced_channel.rs @@ -33,8 +33,8 @@ pub trait AdvancedChannel: SealedAdvancedChannel { Prescaler::compute_min_low_res(psc_min) }; - let timer_f = 32 * (timer_f / psc as u32); - let per: u16 = (timer_f / f) as u16; + let timer_f = 32 * (timer_f as u64/ psc as u64); + let per: u16 = (timer_f / f as u64) as u16; let regs = T::regs(); diff --git a/embassy-stm32/src/hrtim/advanced_pwm.rs b/embassy-stm32/src/hrtim/advanced_pwm.rs index 3bc1bb83e3..1706fba466 100644 --- a/embassy-stm32/src/hrtim/advanced_pwm.rs +++ b/embassy-stm32/src/hrtim/advanced_pwm.rs @@ -121,8 +121,8 @@ impl<'d, T: Instance> AdvancedPwm<'d, T> { Prescaler::compute_min_low_res(psc_min) }; - let timer_f = 32 * (timer_f / psc as u32); - let per: u16 = (timer_f / f) as u16; + let timer_f = 32 * (timer_f as u64 / psc as u64); + let per: u16 = (timer_f / f as u64) as u16; let regs = T::regs(); From 976da4297e4cb791332a534317ae81c8c8b9daf9 Mon Sep 17 00:00:00 2001 From: Hatim Thayyil Date: Tue, 10 Feb 2026 16:05:03 +0000 Subject: [PATCH 08/17] stm32/hrtim: add a low_level mod for HrTimer --- embassy-stm32/src/hrtim/advanced_pwm.rs | 26 ++------- embassy-stm32/src/hrtim/low_level.rs | 70 +++++++++++++++++++++++++ embassy-stm32/src/hrtim/mod.rs | 1 + 3 files changed, 75 insertions(+), 22 deletions(-) create mode 100644 embassy-stm32/src/hrtim/low_level.rs diff --git a/embassy-stm32/src/hrtim/advanced_pwm.rs b/embassy-stm32/src/hrtim/advanced_pwm.rs index 1706fba466..b6ebac3b00 100644 --- a/embassy-stm32/src/hrtim/advanced_pwm.rs +++ b/embassy-stm32/src/hrtim/advanced_pwm.rs @@ -7,6 +7,7 @@ use embassy_hal_internal::Peri; use crate::gpio::{AfType, Flex, OutputType, Speed}; pub use crate::timer::simple_pwm::PwmPinConfig; +use super::low_level::HrTimer; use super::{BurstController, ChA, ChB, ChC, ChD, ChE, Instance, Master, Prescaler}; #[cfg(hrtim_v2)] use super::{ChF, ChannelFComplementaryPin, ChannelFPin}; @@ -21,7 +22,7 @@ use crate::time::Hertz; /// Struct used to divide a high resolution timer into multiple channels pub struct AdvancedPwm<'d, T: Instance> { - _inner: Peri<'d, T>, + _inner: HrTimer<'d, T>, /// Master instance. pub master: Master, /// Burst controller. @@ -64,30 +65,11 @@ impl<'d, T: Instance> AdvancedPwm<'d, T> { } fn new_inner(tim: Peri<'d, T>) -> Self { - rcc::enable_and_reset::(); + let mut tim = HrTimer::new(tim); #[cfg(stm32f334)] if crate::pac::RCC.cfgr3().read().hrtim1sw() == crate::pac::rcc::vals::Timsw::PLL1_P { - // Enable and and stabilize the DLL - T::regs().dllcr().modify(|w| { - w.set_cal(true); - }); - - trace!("hrtim: wait for dll calibration"); - while !T::regs().isr().read().dllrdy() {} - - trace!("hrtim: dll calibration complete"); - - // Enable periodic calibration - // Cal must be disabled before we can enable it - T::regs().dllcr().modify(|w| { - w.set_cal(false); - }); - - T::regs().dllcr().modify(|w| { - w.set_calen(true); - w.set_calrte(11); - }); + tim.calibrate(); } Self { diff --git a/embassy-stm32/src/hrtim/low_level.rs b/embassy-stm32/src/hrtim/low_level.rs new file mode 100644 index 0000000000..a1884af6a2 --- /dev/null +++ b/embassy-stm32/src/hrtim/low_level.rs @@ -0,0 +1,70 @@ +//! Low-level high resolution timer driver. + +use embassy_hal_internal::Peri; + +use super::*; +use crate::rcc; +use crate::time::Hertz; + +impl<'d, T: Instance> Drop for HrTimer<'d, T> { + fn drop(&mut self) { + rcc::disable::(); + } +} + +/// Low-level HRTIM driver. +pub struct HrTimer<'d, T: Instance> { + _tim: Peri<'d, T>, +} + +impl<'d, T: Instance> HrTimer<'d, T> { + /// Create a new timer driver. + pub fn new(tim: Peri<'d, T>) -> Self { + + rcc::enable_and_reset::(); + + Self { _tim: tim } + } + + /// Get access to the timer registers. + pub fn regs(&self) -> crate::pac::hrtim::Hrtim { + T::regs() + } + + /// Calibrate the HRTIM DLL + pub fn calibrate(&mut self) { + let regs = self.regs(); + + // Enable and and stabilize the DLL + regs.dllcr().modify(|w| { + w.set_cal(true); + }); + + trace!("hrtim: wait for dll calibration"); + while !regs.isr().read().dllrdy() {} + + trace!("hrtim: dll calibration complete"); + + self.set_periodic_calibration(); + } + + /// Enable and set periodic calibration + pub fn set_periodic_calibration(&mut self) { + let regs = self.regs(); + + // Cal must be disabled before we can enable it + regs.dllcr().modify(|w| { + w.set_cal(false); + }); + + regs.dllcr().modify(|w| { + w.set_calen(true); + w.set_calrte(11); + }); + } + + /// Get the clock frequency of the timer (before prescaler is applied). + pub fn get_clock_frequency(&self) -> Hertz { + T::frequency() + } +} diff --git a/embassy-stm32/src/hrtim/mod.rs b/embassy-stm32/src/hrtim/mod.rs index 6ef550e0e3..23e9e05da4 100644 --- a/embassy-stm32/src/hrtim/mod.rs +++ b/embassy-stm32/src/hrtim/mod.rs @@ -7,6 +7,7 @@ use embassy_hal_internal::PeripheralType; mod advanced_channel; mod advanced_pwm; mod bridge_converter; +pub mod low_level; mod resonant_converter; pub use advanced_channel::AdvancedChannel; From cd16c2af51a4f7cddf7f6b404b099ecd5f2111d1 Mon Sep 17 00:00:00 2001 From: Hatim Thayyil Date: Tue, 10 Feb 2026 16:11:58 +0000 Subject: [PATCH 09/17] stm32/hrtim: add functions for setting channel prescaler and period --- embassy-stm32/src/hrtim/advanced_channel.rs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/embassy-stm32/src/hrtim/advanced_channel.rs b/embassy-stm32/src/hrtim/advanced_channel.rs index 3209b2c38f..4c0da10e11 100644 --- a/embassy-stm32/src/hrtim/advanced_channel.rs +++ b/embassy-stm32/src/hrtim/advanced_channel.rs @@ -16,6 +16,17 @@ pub trait AdvancedChannel: SealedAdvancedChannel { fn index() -> usize { Self::raw() } + + /// Set channel prescaler + fn set_channel_prescaler(channel: usize, ckpsc: Prescaler) { + T::regs().tim(channel).cr().modify(|w| w.set_ckpsc(ckpsc.into())) + } + + /// Set channel period + fn set_channel_period(channel: usize, per: u16) { + T::regs().tim(channel).per().modify(|w| w.set_per(per)); + } + /// Set channel frequency fn set_channel_frequency(channel: usize, frequency: Hertz) { let f = frequency.0; @@ -33,13 +44,11 @@ pub trait AdvancedChannel: SealedAdvancedChannel { Prescaler::compute_min_low_res(psc_min) }; - let timer_f = 32 * (timer_f as u64/ psc as u64); + let timer_f = 32 * (timer_f as u64 / psc as u64); let per: u16 = (timer_f / f as u64) as u16; - let regs = T::regs(); - - regs.tim(channel).cr().modify(|w| w.set_ckpsc(psc.into())); - regs.tim(channel).per().modify(|w| w.set_per(per)); + Self::set_channel_prescaler(channel, psc); + Self::set_channel_period(channel, per); } /// Set the dead time as a proportion of max_duty From 9541f0b2ff9d20a14b7b74f4c181fe2958c9ecbf Mon Sep 17 00:00:00 2001 From: Hatim Thayyil Date: Wed, 11 Feb 2026 09:40:05 +0000 Subject: [PATCH 10/17] add MasterAdvancedChannel trait instance --- embassy-stm32/src/hrtim/advanced_channel.rs | 37 ++++++++++++++++++++- embassy-stm32/src/hrtim/advanced_pwm.rs | 26 --------------- 2 files changed, 36 insertions(+), 27 deletions(-) diff --git a/embassy-stm32/src/hrtim/advanced_channel.rs b/embassy-stm32/src/hrtim/advanced_channel.rs index 4c0da10e11..bbc53762df 100644 --- a/embassy-stm32/src/hrtim/advanced_channel.rs +++ b/embassy-stm32/src/hrtim/advanced_channel.rs @@ -2,7 +2,7 @@ #[cfg(hrtim_v2)] use super::ChF; -use super::{ChA, ChB, ChC, ChD, ChE, Instance, Prescaler}; +use super::{Master, ChA, ChB, ChC, ChD, ChE, Instance, Prescaler}; use crate::time::Hertz; trait SealedAdvancedChannel { @@ -76,6 +76,41 @@ pub trait AdvancedChannel: SealedAdvancedChannel { } } +trait SealedAdvancedChannelMaster {} +impl SealedAdvancedChannelMaster for Master {} + +/// Master channel instance trait. +#[allow(private_bounds)] +pub trait AdvancedChannelMaster: SealedAdvancedChannelMaster { + /// Set master frequency + fn set_master_frequency(&mut self, frequency: Hertz) { + let f = frequency.0; + + // TODO: wire up HRTIM to the RCC mux infra. + //#[cfg(stm32f334)] + //let timer_f = unsafe { crate::rcc::get_freqs() }.hrtim.unwrap_or(T::frequency()).0; + //#[cfg(not(stm32f334))] + let timer_f = T::frequency().0; + + let psc_min = (timer_f / f) / (u16::MAX as u32 / 32); + let psc = if T::regs().isr().read().dllrdy() { + Prescaler::compute_min_high_res(psc_min) + } else { + Prescaler::compute_min_low_res(psc_min) + }; + + let timer_f = 32 * (timer_f as u64 / psc as u64); + let per: u16 = (timer_f / f as u64) as u16; + + let regs = T::regs(); + + regs.mcr().modify(|w| w.set_ckpsc(psc.into())); + regs.mper().modify(|w| w.set_mper(per)); + } +} + +impl AdvancedChannelMaster for Master {} + macro_rules! advanced_channel_impl { ($new_chx:ident, $channel:tt, $ch_num:expr, $pin_trait:ident, $complementary_pin_trait:ident) => { impl SealedAdvancedChannel for $channel { diff --git a/embassy-stm32/src/hrtim/advanced_pwm.rs b/embassy-stm32/src/hrtim/advanced_pwm.rs index b6ebac3b00..76857a5482 100644 --- a/embassy-stm32/src/hrtim/advanced_pwm.rs +++ b/embassy-stm32/src/hrtim/advanced_pwm.rs @@ -85,32 +85,6 @@ impl<'d, T: Instance> AdvancedPwm<'d, T> { ch_f: ChF { phantom: PhantomData }, } } - - /// Set master frequency - pub fn set_master_frequency(&mut self, frequency: Hertz) { - let f = frequency.0; - - // TODO: wire up HRTIM to the RCC mux infra. - //#[cfg(stm32f334)] - //let timer_f = unsafe { crate::rcc::get_freqs() }.hrtim.unwrap_or(T::frequency()).0; - //#[cfg(not(stm32f334))] - let timer_f = T::frequency().0; - - let psc_min = (timer_f / f) / (u16::MAX as u32 / 32); - let psc = if T::regs().isr().read().dllrdy() { - Prescaler::compute_min_high_res(psc_min) - } else { - Prescaler::compute_min_low_res(psc_min) - }; - - let timer_f = 32 * (timer_f as u64 / psc as u64); - let per: u16 = (timer_f / f as u64) as u16; - - let regs = T::regs(); - - regs.mcr().modify(|w| w.set_ckpsc(psc.into())); - regs.mper().modify(|w| w.set_mper(per)); - } } /// HRTIM PWM pin. From f4e796fa618c390d54a7dbf87877bba50fcd5129 Mon Sep 17 00:00:00 2001 From: Hatim Thayyil Date: Wed, 11 Feb 2026 09:46:18 +0000 Subject: [PATCH 11/17] Add hrtim DLL calibrate for non stm32f334 --- embassy-stm32/src/hrtim/advanced_pwm.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/embassy-stm32/src/hrtim/advanced_pwm.rs b/embassy-stm32/src/hrtim/advanced_pwm.rs index 76857a5482..05093092b2 100644 --- a/embassy-stm32/src/hrtim/advanced_pwm.rs +++ b/embassy-stm32/src/hrtim/advanced_pwm.rs @@ -72,6 +72,9 @@ impl<'d, T: Instance> AdvancedPwm<'d, T> { tim.calibrate(); } + #[cfg(not(stm32f334))] + tim.calibrate(); + Self { _inner: tim, master: Master { phantom: PhantomData }, From ab1a3f5531d97f3faff80d2c9acc2af77edb1182 Mon Sep 17 00:00:00 2001 From: Hatim Thayyil Date: Wed, 11 Feb 2026 09:54:53 +0000 Subject: [PATCH 12/17] add fullbridge converter with example --- embassy-stm32/src/hrtim/fullbridge.rs | 182 ++++++++++++++++++++++++++ embassy-stm32/src/hrtim/mod.rs | 2 + examples/stm32g474/src/bin/hrtim.rs | 51 +++++--- 3 files changed, 218 insertions(+), 17 deletions(-) create mode 100644 embassy-stm32/src/hrtim/fullbridge.rs diff --git a/embassy-stm32/src/hrtim/fullbridge.rs b/embassy-stm32/src/hrtim/fullbridge.rs new file mode 100644 index 0000000000..b014efae61 --- /dev/null +++ b/embassy-stm32/src/hrtim/fullbridge.rs @@ -0,0 +1,182 @@ +//! Fixed-frequency bridge converter driver. + +use core::marker::PhantomData; + +use super::AdvancedChannel; +use super::Instance; +use super::Prescaler; +use crate::time::Hertz; + +/// Fixed-frequency full-bridge converter driver. +pub struct FullBridgeConverter, CH2: AdvancedChannel> { + timer: PhantomData, + ch1: PhantomData, + ch2: PhantomData, + dead_time: u16, + duty: u16, + minimum_duty: u16, +} + +impl, CH2: AdvancedChannel> FullBridgeConverter { + /// Create a new HRTIM bridge converter driver. + pub fn new(_ch1: &mut CH1, _ch2: &mut CH2, frequency: Hertz) -> Self { + CH1::set_channel_frequency(CH1::index(), frequency); + CH2::set_channel_frequency(CH2::index(), frequency); + + T::regs().mcr().modify(|w| { + w.set_preen(true); + w.set_mrepu(true); + w.set_cont(true); + }); + + // Always enable preload + T::regs().tim(CH1::index()).cr().modify(|w| { + w.set_preen(true); + w.set_repu(true); + w.set_cont(true); + }); + + // Always enable preload + T::regs().tim(CH2::index()).cr().modify(|w| { + w.set_preen(true); + w.set_repu(true); + w.set_cont(true); + }); + + // Enable timer outputs + T::regs().oenr().modify(|w| { + w.set_t1oen(CH1::index(), true); + w.set_t2oen(CH1::index(), true); + w.set_t1oen(CH2::index(), true); + w.set_t2oen(CH2::index(), true); + }); + + // Set output 1 to active on a period event + T::regs().tim(CH1::index()).setr(0).modify(|w| w.set_mstper(true)); + // Set output 1 to inactive on a cmp event + T::regs().tim(CH1::index()).rstr(0).modify(|w| w.set_mstcmp(0, true)); + // Enable deadtime + T::regs().tim(CH1::index()).outr().modify(|w| { + w.set_dten(true); + w.set_idles(0, false); + w.set_idles(1, true); + }); + + // Set output 2 to inactive on a period event + T::regs().tim(CH2::index()).rstr(0).modify(|w| w.set_mstper(true)); + // Set output 2 to inactive on a cmp event + T::regs().tim(CH2::index()).setr(0).modify(|w| w.set_mstcmpx(0, true)); + // Enable deadtime + T::regs().tim(CH2::index()).outr().modify(|w| { + w.set_dten(true); + w.set_idles(0, false); + w.set_idles(1, true); + }); + + // Reset timing units on master period event + T::regs().tim(CH1::index()).rst().modify(|w| w.set_mstper(true)); + T::regs().tim(CH2::index()).rst().modify(|w| w.set_mstper(true)); + + Self { + timer: PhantomData, + ch1: PhantomData, + ch2: PhantomData, + dead_time: 0, + duty: 0, + minimum_duty: 0, + } + } + + /// Start HRTIM. + pub fn start(&mut self) { + T::regs().mcr().modify(|w| { + w.set_mcen(true); + w.set_tcen(CH1::index(), true); + w.set_tcen(CH2::index(), true); + }); + } + + /// Stop HRTIM. + pub fn stop(&mut self) { + T::regs().mcr().modify(|w| { + w.set_mcen(false); + w.set_tcen(CH1::index(), false); + w.set_tcen(CH2::index(), false); + }); + } + + /// Set duty + fn set_duty_inner(&mut self) { + T::regs().mcmp(0).modify(|w| w.set_mcmp(self.duty)); + T::regs().tim(CH1::index()).cmp(0).modify(|w| w.set_cmp(self.duty)); + + T::regs().tim(CH2::index()).cmp(0).modify(|w| w.set_cmp(self.duty)); + } + + /// Get the maximum compare value of a duty cycle + pub fn get_max_compare_value_master(&mut self) -> u16 { + T::regs().mper().read().mper() + } + + /// Get the maximum compare value of a duty cycle + pub fn get_max_compare_value_ch1(&mut self) -> u16 { + T::regs().tim(CH1::index()).per().read().per() + } + + /// Get the maximum compare value of a duty cycle + pub fn get_max_compare_value_ch2(&mut self) -> u16 { + T::regs().tim(CH2::index()).per().read().per() + } + + /// Set the dead time as a proportion of the maximum compare value + pub fn set_dead_time(&mut self, value: u16) { + self.dead_time = value; + CH1::set_channel_dead_time(CH1::index(), value); + CH2::set_channel_dead_time(CH2::index(), value); + } + + /// The duty is the period in which the primary switch is active + pub fn set_duty(&mut self, requested_duty: u16) { + let fet_sound_duty = requested_duty + .max(self.dead_time + self.minimum_duty) + .min(self.get_max_compare_value_master() - (self.dead_time + self.minimum_duty)); + + // TODO Use the Prescaler setting to compute the minimum + let psc: Prescaler = T::regs().mcr().read().ckpsc().into(); + + let fhrtim_3_clock_cycles = 0x60 / psc as u16; + let hrtim_sound_duty = fet_sound_duty + .max(fhrtim_3_clock_cycles) + .min(self.get_max_compare_value_master() - fhrtim_3_clock_cycles); + + self.duty = hrtim_sound_duty; + self.set_duty_inner(); + } + + /// The duty is the period in which the primary switch is active + pub fn enable_outputs(&mut self) { + // Enable timer outputs + T::regs().oenr().modify(|w| { + w.set_t1oen(CH1::index(), true); + w.set_t2oen(CH1::index(), true); + w.set_t1oen(CH2::index(), true); + w.set_t2oen(CH2::index(), true); + }); + } + + /// The duty is the period in which the primary switch is active + pub fn disable_outputs(&mut self) { + // Enable timer outputs + T::regs().odisr().modify(|w| { + w.set_t1odis(CH1::index(), true); + w.set_t2odis(CH1::index(), true); + w.set_t1odis(CH2::index(), true); + w.set_t2odis(CH2::index(), true); + }); + } + + /// Set minimum FET pulse width in counts + pub fn set_minimum_duty(&mut self, duty: u16) { + self.minimum_duty = duty; + } +} diff --git a/embassy-stm32/src/hrtim/mod.rs b/embassy-stm32/src/hrtim/mod.rs index 23e9e05da4..d784d5558f 100644 --- a/embassy-stm32/src/hrtim/mod.rs +++ b/embassy-stm32/src/hrtim/mod.rs @@ -7,12 +7,14 @@ use embassy_hal_internal::PeripheralType; mod advanced_channel; mod advanced_pwm; mod bridge_converter; +mod fullbridge; pub mod low_level; mod resonant_converter; pub use advanced_channel::AdvancedChannel; pub use advanced_pwm::{AdvancedPwm, ComplementaryPwmPin, PwmPin}; pub use bridge_converter::BridgeConverter; +pub use fullbridge::FullBridgeConverter; pub use resonant_converter::ResonantConverter; use crate::rcc::RccPeripheral; diff --git a/examples/stm32g474/src/bin/hrtim.rs b/examples/stm32g474/src/bin/hrtim.rs index 1d335761be..3de596e093 100644 --- a/examples/stm32g474/src/bin/hrtim.rs +++ b/examples/stm32g474/src/bin/hrtim.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_stm32::hrtim::{AdvancedPwm, BridgeConverter, ComplementaryPwmPin, PwmPin}; +use embassy_stm32::hrtim::{AdvancedPwm, BridgeConverter, ComplementaryPwmPin, FullBridgeConverter, PwmPin}; use embassy_stm32::time::Hertz; use embassy_stm32::Config; use embassy_time::Timer; @@ -17,41 +17,58 @@ async fn main(_spawner: Spawner) { info!("Hello World!"); - let ch1 = PwmPin::new_cha(p.PA8); - let ch1n = ComplementaryPwmPin::new_cha(p.PA9); - let pwm = AdvancedPwm::new( + let ch3 = PwmPin::new_chc(p.PB12); + let ch3n = ComplementaryPwmPin::new_chc(p.PB13); + + let ch4 = PwmPin::new_chd(p.PB14); + let ch4n = ComplementaryPwmPin::new_chd(p.PB15); + + let mut pwm = AdvancedPwm::new( p.HRTIM1, - Some(ch1), - Some(ch1n), - None, - None, None, None, None, None, + Some(ch3), + Some(ch3n), + Some(ch4), + Some(ch4n), None, None, None, None, ); + pwm.set_master_frequency(Hertz::mhz(1)); + info!("pwm constructed"); - let mut buck_converter = BridgeConverter::new(pwm.ch_a, Hertz::khz(5)); + let mut bridge = FullBridgeConverter::new(&mut pwm.ch_c, &mut pwm.ch_d, Hertz::mhz(1)); - let max_duty = buck_converter.get_max_compare_value(); + let max_duty_master = bridge.get_max_compare_value_master(); - info!("max compare value: {}", max_duty); + info!("max compare value master: {}", max_duty_master); - buck_converter.set_dead_time(max_duty / 20); - buck_converter.set_primary_duty(max_duty / 2); - buck_converter.set_secondary_duty(3 * max_duty / 4); + bridge.set_dead_time(54); // 10ns * 5.376ns step/ns + bridge.set_duty(max_duty_master / 2); + bridge.set_minimum_duty(135); // 25ns * 5.376 step/ns - buck_converter.start(); + bridge.start(); Timer::after_millis(500).await; - info!("end program"); + info!("end setup"); + + let mut duty = 0; + loop { + bridge.set_duty(duty); + + if duty == max_duty_master { + duty = 0; + } else { + duty = duty + 1; + } - cortex_m::asm::bkpt(); + Timer::after_millis(10).await; + } } From 909be1b68c545f0b65e28cb17ff3b64f9efe182e Mon Sep 17 00:00:00 2001 From: Hatim Thayyil Date: Wed, 11 Feb 2026 10:00:30 +0000 Subject: [PATCH 13/17] chore: fmt, clean up --- embassy-stm32/src/hrtim/advanced_channel.rs | 4 +- embassy-stm32/src/hrtim/advanced_pwm.rs | 73 ++++++++++++++++----- embassy-stm32/src/hrtim/fullbridge.rs | 4 +- embassy-stm32/src/hrtim/low_level.rs | 1 - embassy-stm32/src/hrtim/mod.rs | 2 + 5 files changed, 60 insertions(+), 24 deletions(-) diff --git a/embassy-stm32/src/hrtim/advanced_channel.rs b/embassy-stm32/src/hrtim/advanced_channel.rs index bbc53762df..01ae8484f4 100644 --- a/embassy-stm32/src/hrtim/advanced_channel.rs +++ b/embassy-stm32/src/hrtim/advanced_channel.rs @@ -2,7 +2,7 @@ #[cfg(hrtim_v2)] use super::ChF; -use super::{Master, ChA, ChB, ChC, ChD, ChE, Instance, Prescaler}; +use super::{ChA, ChB, ChC, ChD, ChE, Instance, Master, Prescaler}; use crate::time::Hertz; trait SealedAdvancedChannel { @@ -79,7 +79,7 @@ pub trait AdvancedChannel: SealedAdvancedChannel { trait SealedAdvancedChannelMaster {} impl SealedAdvancedChannelMaster for Master {} -/// Master channel instance trait. +/// Advanced channel instance trait. #[allow(private_bounds)] pub trait AdvancedChannelMaster: SealedAdvancedChannelMaster { /// Set master frequency diff --git a/embassy-stm32/src/hrtim/advanced_pwm.rs b/embassy-stm32/src/hrtim/advanced_pwm.rs index 05093092b2..5c48849eaf 100644 --- a/embassy-stm32/src/hrtim/advanced_pwm.rs +++ b/embassy-stm32/src/hrtim/advanced_pwm.rs @@ -4,21 +4,16 @@ use core::marker::PhantomData; use embassy_hal_internal::Peri; -use crate::gpio::{AfType, Flex, OutputType, Speed}; -pub use crate::timer::simple_pwm::PwmPinConfig; - use super::low_level::HrTimer; -use super::{BurstController, ChA, ChB, ChC, ChD, ChE, Instance, Master, Prescaler}; -#[cfg(hrtim_v2)] -use super::{ChF, ChannelFComplementaryPin, ChannelFPin}; use super::{ - ChannelAComplementaryPin, ChannelBComplementaryPin, ChannelCComplementaryPin, ChannelDComplementaryPin, - ChannelEComplementaryPin, + BurstController, ChA, ChB, ChC, ChD, ChE, ChannelAComplementaryPin, ChannelAPin, ChannelBComplementaryPin, + ChannelBPin, ChannelCComplementaryPin, ChannelCPin, ChannelDComplementaryPin, ChannelDPin, + ChannelEComplementaryPin, ChannelEPin, Instance, Master, }; -use super::{ChannelAPin, ChannelBPin, ChannelCPin, ChannelDPin, ChannelEPin}; - -use crate::rcc; -use crate::time::Hertz; +#[cfg(hrtim_v2)] +use super::{ChF, ChannelFComplementaryPin, ChannelFPin}; +use crate::gpio::{AfType, Flex, OutputType, Speed}; +pub use crate::timer::simple_pwm::PwmPinConfig; /// Struct used to divide a high resolution timer into multiple channels pub struct AdvancedPwm<'d, T: Instance> { @@ -164,10 +159,52 @@ macro_rules! channel_impl { }; } -channel_impl!(new_cha, new_cha_with_config, ChA, 0, ChannelAPin, ChannelAComplementaryPin); -channel_impl!(new_chb, new_chb_with_config, ChB, 1, ChannelBPin, ChannelBComplementaryPin); -channel_impl!(new_chc, new_chc_with_config, ChC, 2, ChannelCPin, ChannelCComplementaryPin); -channel_impl!(new_chd, new_chd_with_config, ChD, 3, ChannelDPin, ChannelDComplementaryPin); -channel_impl!(new_che, new_che_with_config, ChE, 4, ChannelEPin, ChannelEComplementaryPin); +channel_impl!( + new_cha, + new_cha_with_config, + ChA, + 0, + ChannelAPin, + ChannelAComplementaryPin +); +channel_impl!( + new_chb, + new_chb_with_config, + ChB, + 1, + ChannelBPin, + ChannelBComplementaryPin +); +channel_impl!( + new_chc, + new_chc_with_config, + ChC, + 2, + ChannelCPin, + ChannelCComplementaryPin +); +channel_impl!( + new_chd, + new_chd_with_config, + ChD, + 3, + ChannelDPin, + ChannelDComplementaryPin +); +channel_impl!( + new_che, + new_che_with_config, + ChE, + 4, + ChannelEPin, + ChannelEComplementaryPin +); #[cfg(hrtim_v2)] -channel_impl!(new_chf, new_chf_with_config, ChF, 5, ChannelFPin, ChannelFComplementaryPin); +channel_impl!( + new_chf, + new_chf_with_config, + ChF, + 5, + ChannelFPin, + ChannelFComplementaryPin +); diff --git a/embassy-stm32/src/hrtim/fullbridge.rs b/embassy-stm32/src/hrtim/fullbridge.rs index b014efae61..a7ec6271df 100644 --- a/embassy-stm32/src/hrtim/fullbridge.rs +++ b/embassy-stm32/src/hrtim/fullbridge.rs @@ -2,9 +2,7 @@ use core::marker::PhantomData; -use super::AdvancedChannel; -use super::Instance; -use super::Prescaler; +use super::{AdvancedChannel, Instance, Prescaler}; use crate::time::Hertz; /// Fixed-frequency full-bridge converter driver. diff --git a/embassy-stm32/src/hrtim/low_level.rs b/embassy-stm32/src/hrtim/low_level.rs index a1884af6a2..7503dcd17a 100644 --- a/embassy-stm32/src/hrtim/low_level.rs +++ b/embassy-stm32/src/hrtim/low_level.rs @@ -20,7 +20,6 @@ pub struct HrTimer<'d, T: Instance> { impl<'d, T: Instance> HrTimer<'d, T> { /// Create a new timer driver. pub fn new(tim: Peri<'d, T>) -> Self { - rcc::enable_and_reset::(); Self { _tim: tim } diff --git a/embassy-stm32/src/hrtim/mod.rs b/embassy-stm32/src/hrtim/mod.rs index d784d5558f..8df18425f2 100644 --- a/embassy-stm32/src/hrtim/mod.rs +++ b/embassy-stm32/src/hrtim/mod.rs @@ -131,6 +131,7 @@ impl From for Prescaler { } impl Prescaler { + /// Computer the minium prescaler for high resolution pub fn compute_min_high_res(val: u32) -> Self { *[ Prescaler::Div1, @@ -148,6 +149,7 @@ impl Prescaler { .unwrap() } + /// Compute the minium prescaler for low resolution pub fn compute_min_low_res(val: u32) -> Self { *[Prescaler::Div32, Prescaler::Div64, Prescaler::Div128] .iter() From 3015f597506b0ce92f7bc48c7b62103fa332c554 Mon Sep 17 00:00:00 2001 From: xoviat Date: Fri, 20 Mar 2026 14:34:19 -0500 Subject: [PATCH 14/17] wip --- embassy-stm32/src/hrtim/advanced_channel.rs | 2 + embassy-stm32/src/hrtim/mod.rs | 6 +- embassy-stm32/src/hrtim/traits.rs | 19 ------- examples/stm32f334/src/bin/pwm.rs | 63 ++++++++++----------- 4 files changed, 38 insertions(+), 52 deletions(-) delete mode 100644 embassy-stm32/src/hrtim/traits.rs diff --git a/embassy-stm32/src/hrtim/advanced_channel.rs b/embassy-stm32/src/hrtim/advanced_channel.rs index 01ae8484f4..9bd098be5e 100644 --- a/embassy-stm32/src/hrtim/advanced_channel.rs +++ b/embassy-stm32/src/hrtim/advanced_channel.rs @@ -76,11 +76,13 @@ pub trait AdvancedChannel: SealedAdvancedChannel { } } +#[allow(unused)] trait SealedAdvancedChannelMaster {} impl SealedAdvancedChannelMaster for Master {} /// Advanced channel instance trait. #[allow(private_bounds)] +#[allow(unused)] pub trait AdvancedChannelMaster: SealedAdvancedChannelMaster { /// Set master frequency fn set_master_frequency(&mut self, frequency: Hertz) { diff --git a/embassy-stm32/src/hrtim/mod.rs b/embassy-stm32/src/hrtim/mod.rs index 493c35a979..fb393c228a 100644 --- a/embassy-stm32/src/hrtim/mod.rs +++ b/embassy-stm32/src/hrtim/mod.rs @@ -11,11 +11,16 @@ mod fullbridge; pub mod low_level; mod resonant_converter; +use core::mem::MaybeUninit; + pub use advanced_channel::AdvancedChannel; pub use advanced_pwm::{AdvancedPwm, ComplementaryPwmPin, PwmPin}; pub use bridge_converter::BridgeConverter; +use embassy_hal_internal::Peri; pub use fullbridge::FullBridgeConverter; pub use resonant_converter::ResonantConverter; +use stm32_hrtim::control::{HrPwmControl, HrTimOngoingCalibration}; +use stm32_hrtim::output::{Output1Pin, Output2Pin}; use crate::rcc::RccPeripheral; use crate::time::Hertz; @@ -61,7 +66,6 @@ use stm32_hrtim::pac::HRTIM_TIMF; use stm32_hrtim::pac::{HRTIM_MASTER, HRTIM_TIMA, HRTIM_TIMB, HRTIM_TIMC, HRTIM_TIMD, HRTIM_TIME}; pub use stm32_hrtim::{self, Pscl1, Pscl2, Pscl4, Pscl8, Pscl16, Pscl32, Pscl64, Pscl128, PsclDefault}; use stm32_hrtim::{DacResetTrigger, DacStepTrigger, HrParts, HrPwmBuilder}; -use traits::Instance; use crate::gpio::{AfType, OutputType, Speed}; use crate::peripherals::HRTIM1; diff --git a/embassy-stm32/src/hrtim/traits.rs b/embassy-stm32/src/hrtim/traits.rs deleted file mode 100644 index 242c34d3e7..0000000000 --- a/embassy-stm32/src/hrtim/traits.rs +++ /dev/null @@ -1,19 +0,0 @@ -use embassy_hal_internal::PeripheralType; - -use crate::rcc::RccPeripheral; - -pub(crate) trait SealedInstance: RccPeripheral {} - -/// HRTIM instance trait. -#[allow(private_bounds)] -pub trait Instance: SealedInstance + PeripheralType + 'static {} - -foreach_interrupt! { - ($inst:ident, hrtim, HRTIM, MASTER, $irq:ident) => { - impl SealedInstance for crate::peripherals::$inst { } - - impl Instance for crate::peripherals::$inst { - - } - }; -} diff --git a/examples/stm32f334/src/bin/pwm.rs b/examples/stm32f334/src/bin/pwm.rs index 68c63443d8..233bb114a9 100644 --- a/examples/stm32f334/src/bin/pwm.rs +++ b/examples/stm32f334/src/bin/pwm.rs @@ -3,9 +3,8 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_stm32::hrtim::{AdvancedPwm, BridgeConverter, ComplementaryPwmPin, PwmPin, *}; -use embassy_stm32::time::{khz, mhz}; -use embassy_stm32::{Config, hrtim}; +use embassy_stm32::Config; +use embassy_stm32::time::mhz; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; @@ -30,30 +29,30 @@ async fn main(_spawner: Spawner) { config.rcc.mux.hrtim1sw = embassy_stm32::rcc::mux::Timsw::PLL1_P; } - let p = embassy_stm32::init(config); + let _p = embassy_stm32::init(config); info!("Hello World!"); - let ch1 = hrtim::Pin { - pin: p.PA8, - speed: Speed::Low, - }; - let ch1n = hrtim::Pin { - pin: p.PA9, - speed: Speed::Low, - }; - - // ...with a prescaler of 4 this gives us a HrTimer with a tick rate of 1152MHz - // With max the max period set, this would be 1152MHz/2^16 ~= 17.6kHz... - let prescaler = hrtim::Pscl4; - - let Parts { control, tima, .. } = p.HRTIM1.hr_control(); - let (control, ..) = control.wait_for_calibration(); - let mut control = control.constrain(); - - info!("pwm constructed"); + // let ch1 = hrtim::Pin { + // pin: p.PA8, + // speed: Speed::Low, + // }; + // let ch1n = hrtim::Pin { + // pin: p.PA9, + // speed: Speed::Low, + // }; + // + // // ...with a prescaler of 4 this gives us a HrTimer with a tick rate of 1152MHz + // // With max the max period set, this would be 1152MHz/2^16 ~= 17.6kHz... + // let prescaler = hrtim::Pscl4; + // + // let Parts { control, tima, .. } = p.HRTIM1.hr_control(); + // let (control, ..) = control.wait_for_calibration(); + // let mut control = control.constrain(); + // + // info!("pwm constructed"); - let mut buck_converter = BridgeConverter::new(tima, ch1, ch1n, khz(5), prescaler, &mut control); + // let mut buck_converter = BridgeConverter::new(tima, ch1, ch1n, khz(5), prescaler, &mut control); // embassy_stm32::pac::HRTIM1 // .tim(0) @@ -67,15 +66,15 @@ async fn main(_spawner: Spawner) { // .rstr(0) // .modify(|w| w.set_srt(true)); - let max_duty = buck_converter.get_max_compare_value(); - - info!("max compare value: {}", max_duty); - - buck_converter.set_dead_time(max_duty / 20); - buck_converter.set_primary_duty(max_duty / 2); - buck_converter.set_secondary_duty(3 * max_duty / 4); - - buck_converter.start(&mut control.control); + // let max_duty = buck_converter.get_max_compare_value(); + // + // info!("max compare value: {}", max_duty); + // + // buck_converter.set_dead_time(max_duty / 20); + // buck_converter.set_primary_duty(max_duty / 2); + // buck_converter.set_secondary_duty(3 * max_duty / 4); + // + // buck_converter.start(&mut control.control); Timer::after_millis(500).await; From 102b49d92fe0329914f451281d0fd0586f7cb7e7 Mon Sep 17 00:00:00 2001 From: xoviat Date: Fri, 20 Mar 2026 14:40:36 -0500 Subject: [PATCH 15/17] wip --- embassy-stm32/src/hrtim/mod.rs | 5 +++ examples/stm32g474/src/bin/hrtim.rs | 70 ++++++++++++++--------------- 2 files changed, 39 insertions(+), 36 deletions(-) diff --git a/embassy-stm32/src/hrtim/mod.rs b/embassy-stm32/src/hrtim/mod.rs index fb393c228a..d1fb3b9de5 100644 --- a/embassy-stm32/src/hrtim/mod.rs +++ b/embassy-stm32/src/hrtim/mod.rs @@ -60,7 +60,12 @@ pub struct ChE { phantom: PhantomData, } +#[cfg(hrtim_v2)] /// HRTIM channel F instance. +pub struct ChF { + phantom: PhantomData, +} + #[cfg(hrtim_v2)] use stm32_hrtim::pac::HRTIM_TIMF; use stm32_hrtim::pac::{HRTIM_MASTER, HRTIM_TIMA, HRTIM_TIMB, HRTIM_TIMC, HRTIM_TIMD, HRTIM_TIME}; diff --git a/examples/stm32g474/src/bin/hrtim.rs b/examples/stm32g474/src/bin/hrtim.rs index dcb268d63e..53c87f16bf 100644 --- a/examples/stm32g474/src/bin/hrtim.rs +++ b/examples/stm32g474/src/bin/hrtim.rs @@ -4,9 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_stm32::Config; -use embassy_stm32::hrtim::{AdvancedPwm, BridgeConverter, ComplementaryPwmPin, FullBridgeConverter, PwmPin}; -use embassy_stm32::time::Hertz; -use embassy_time::Timer; +use embassy_stm32::hrtim::{AdvancedPwm, ComplementaryPwmPin, PwmPin}; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] @@ -21,7 +19,7 @@ async fn main(_spawner: Spawner) { let ch4 = PwmPin::new_chd(p.PB14); let ch4n = ComplementaryPwmPin::new_chd(p.PB15); - let mut pwm = AdvancedPwm::new( + let _pwm = AdvancedPwm::new( p.HRTIM1, None, None, @@ -37,36 +35,36 @@ async fn main(_spawner: Spawner) { None, ); - pwm.set_master_frequency(Hertz::mhz(1)); - - info!("pwm constructed"); - - let mut bridge = FullBridgeConverter::new(&mut pwm.ch_c, &mut pwm.ch_d, Hertz::mhz(1)); - - let max_duty_master = bridge.get_max_compare_value_master(); - - info!("max compare value master: {}", max_duty_master); - - bridge.set_dead_time(54); // 10ns * 5.376ns step/ns - bridge.set_duty(max_duty_master / 2); - bridge.set_minimum_duty(135); // 25ns * 5.376 step/ns - - bridge.start(); - - Timer::after_millis(500).await; - - info!("end setup"); - - let mut duty = 0; - loop { - bridge.set_duty(duty); - - if duty == max_duty_master { - duty = 0; - } else { - duty = duty + 1; - } - - Timer::after_millis(10).await; - } + // pwm.set_master_frequency(Hertz::mhz(1)); + // + // info!("pwm constructed"); + // + // let mut bridge = FullBridgeConverter::new(&mut pwm.ch_c, &mut pwm.ch_d, Hertz::mhz(1)); + // + // let max_duty_master = bridge.get_max_compare_value_master(); + // + // info!("max compare value master: {}", max_duty_master); + // + // bridge.set_dead_time(54); // 10ns * 5.376ns step/ns + // bridge.set_duty(max_duty_master / 2); + // bridge.set_minimum_duty(135); // 25ns * 5.376 step/ns + // + // bridge.start(); + // + // Timer::after_millis(500).await; + // + // info!("end setup"); + // + // let mut duty = 0; + // loop { + // bridge.set_duty(duty); + // + // if duty == max_duty_master { + // duty = 0; + // } else { + // duty = duty + 1; + // } + // + // Timer::after_millis(10).await; + // } } From 82f145f80de7ba5d0fb74d6b210f3d9510569ed2 Mon Sep 17 00:00:00 2001 From: xoviat Date: Fri, 20 Mar 2026 18:07:29 -0500 Subject: [PATCH 16/17] wip --- embassy-stm32/build.rs | 24 +- embassy-stm32/src/hrtim/advanced_channel.rs | 26 +- embassy-stm32/src/hrtim/advanced_pwm.rs | 187 +------------- embassy-stm32/src/hrtim/mod.rs | 267 ++++++++++++++------ examples/stm32g474/src/bin/hrtim.rs | 43 ++-- 5 files changed, 229 insertions(+), 318 deletions(-) diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index 7ac7927c2f..8552718f9e 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -1273,18 +1273,18 @@ fn main() { (("timer", "BKIN2"), quote!(crate::timer::BreakInputPin)), (("timer", "BKIN2_COMP1"), quote!(crate::timer::BreakInputComparator1Pin)), (("timer", "BKIN2_COMP2"), quote!(crate::timer::BreakInputComparator2Pin)), - (("hrtim", "CHA1"), quote!(crate::hrtim::ChannelAPin)), - (("hrtim", "CHA2"), quote!(crate::hrtim::ChannelAComplementaryPin)), - (("hrtim", "CHB1"), quote!(crate::hrtim::ChannelBPin)), - (("hrtim", "CHB2"), quote!(crate::hrtim::ChannelBComplementaryPin)), - (("hrtim", "CHC1"), quote!(crate::hrtim::ChannelCPin)), - (("hrtim", "CHC2"), quote!(crate::hrtim::ChannelCComplementaryPin)), - (("hrtim", "CHD1"), quote!(crate::hrtim::ChannelDPin)), - (("hrtim", "CHD2"), quote!(crate::hrtim::ChannelDComplementaryPin)), - (("hrtim", "CHE1"), quote!(crate::hrtim::ChannelEPin)), - (("hrtim", "CHE2"), quote!(crate::hrtim::ChannelEComplementaryPin)), - (("hrtim", "CHF1"), quote!(crate::hrtim::ChannelFPin)), - (("hrtim", "CHF2"), quote!(crate::hrtim::ChannelFComplementaryPin)), + (("hrtim", "CHA1"), quote!(crate::hrtim::HRTimerPin)), + (("hrtim", "CHA2"), quote!(crate::hrtim::HRTimerComplementaryPin)), + (("hrtim", "CHB1"), quote!(crate::hrtim::HRTimerPin)), + (("hrtim", "CHB2"), quote!(crate::hrtim::HRTimerComplementaryPin)), + (("hrtim", "CHC1"), quote!(crate::hrtim::HRTimerPin)), + (("hrtim", "CHC2"), quote!(crate::hrtim::HRTimerComplementaryPin)), + (("hrtim", "CHD1"), quote!(crate::hrtim::HRTimerPin)), + (("hrtim", "CHD2"), quote!(crate::hrtim::HRTimerComplementaryPin)), + (("hrtim", "CHE1"), quote!(crate::hrtim::HRTimerPin)), + (("hrtim", "CHE2"), quote!(crate::hrtim::HRTimerComplementaryPin)), + (("hrtim", "CHF1"), quote!(crate::hrtim::HRTimerPin)), + (("hrtim", "CHF2"), quote!(crate::hrtim::HRTimerComplementaryPin)), (("lptim", "CH1"), quote!(crate::lptim::Channel1Pin)), (("lptim", "CH2"), quote!(crate::lptim::Channel2Pin)), (("lptim", "OUT"), quote!(crate::lptim::OutputPin)), diff --git a/embassy-stm32/src/hrtim/advanced_channel.rs b/embassy-stm32/src/hrtim/advanced_channel.rs index 9bd098be5e..044a151d20 100644 --- a/embassy-stm32/src/hrtim/advanced_channel.rs +++ b/embassy-stm32/src/hrtim/advanced_channel.rs @@ -1,8 +1,6 @@ //! AdvancedChannel -#[cfg(hrtim_v2)] -use super::ChF; -use super::{ChA, ChB, ChC, ChD, ChE, Instance, Master, Prescaler}; +use super::{Instance, Prescaler}; use crate::time::Hertz; trait SealedAdvancedChannel { @@ -78,7 +76,6 @@ pub trait AdvancedChannel: SealedAdvancedChannel { #[allow(unused)] trait SealedAdvancedChannelMaster {} -impl SealedAdvancedChannelMaster for Master {} /// Advanced channel instance trait. #[allow(private_bounds)] @@ -110,24 +107,3 @@ pub trait AdvancedChannelMaster: SealedAdvancedChannelMaster { regs.mper().modify(|w| w.set_mper(per)); } } - -impl AdvancedChannelMaster for Master {} - -macro_rules! advanced_channel_impl { - ($new_chx:ident, $channel:tt, $ch_num:expr, $pin_trait:ident, $complementary_pin_trait:ident) => { - impl SealedAdvancedChannel for $channel { - fn raw() -> usize { - $ch_num - } - } - impl AdvancedChannel for $channel {} - }; -} - -advanced_channel_impl!(new_cha, ChA, 0, ChannelAPin, ChannelAComplementaryPin); -advanced_channel_impl!(new_chb, ChB, 1, ChannelBPin, ChannelBComplementaryPin); -advanced_channel_impl!(new_chc, ChC, 2, ChannelCPin, ChannelCComplementaryPin); -advanced_channel_impl!(new_chd, ChD, 3, ChannelDPin, ChannelDComplementaryPin); -advanced_channel_impl!(new_che, ChE, 4, ChannelEPin, ChannelEComplementaryPin); -#[cfg(hrtim_v2)] -advanced_channel_impl!(new_chf, ChF, 5, ChannelFPin, ChannelFComplementaryPin); diff --git a/embassy-stm32/src/hrtim/advanced_pwm.rs b/embassy-stm32/src/hrtim/advanced_pwm.rs index 5c48849eaf..5e3e75db8e 100644 --- a/embassy-stm32/src/hrtim/advanced_pwm.rs +++ b/embassy-stm32/src/hrtim/advanced_pwm.rs @@ -1,40 +1,16 @@ //! AdvancedPwm -use core::marker::PhantomData; - use embassy_hal_internal::Peri; -use super::low_level::HrTimer; -use super::{ - BurstController, ChA, ChB, ChC, ChD, ChE, ChannelAComplementaryPin, ChannelAPin, ChannelBComplementaryPin, - ChannelBPin, ChannelCComplementaryPin, ChannelCPin, ChannelDComplementaryPin, ChannelDPin, - ChannelEComplementaryPin, ChannelEPin, Instance, Master, -}; #[cfg(hrtim_v2)] -use super::{ChF, ChannelFComplementaryPin, ChannelFPin}; -use crate::gpio::{AfType, Flex, OutputType, Speed}; -pub use crate::timer::simple_pwm::PwmPinConfig; +use super::ChF; +use super::low_level::HrTimer; +use super::{ChA, ChB, ChC, ChD, ChE, Instance}; +use crate::hrtim::PwmPin; /// Struct used to divide a high resolution timer into multiple channels pub struct AdvancedPwm<'d, T: Instance> { _inner: HrTimer<'d, T>, - /// Master instance. - pub master: Master, - /// Burst controller. - pub burst_controller: BurstController, - /// Channel A. - pub ch_a: ChA, - /// Channel B. - pub ch_b: ChB, - /// Channel C. - pub ch_c: ChC, - /// Channel D. - pub ch_d: ChD, - /// Channel E. - pub ch_e: ChE, - /// Channel F. - #[cfg(hrtim_v2)] - pub ch_f: ChF, } impl<'d, T: Instance> AdvancedPwm<'d, T> { @@ -43,18 +19,12 @@ impl<'d, T: Instance> AdvancedPwm<'d, T> { /// This splits the HRTIM into its constituent parts, which you can then use individually. pub fn new( tim: Peri<'d, T>, - _cha: Option>>, - _chan: Option>>, - _chb: Option>>, - _chbn: Option>>, - _chc: Option>>, - _chcn: Option>>, - _chd: Option>>, - _chdn: Option>>, - _che: Option>>, - _chen: Option>>, - #[cfg(hrtim_v2)] _chf: Option>>, - #[cfg(hrtim_v2)] _chfn: Option>>, + _cha: Option)>, + _chb: Option)>, + _chc: Option)>, + _chd: Option)>, + _che: Option)>, + #[cfg(hrtim_v2)] _chf: Option)>, ) -> Self { Self::new_inner(tim) } @@ -70,141 +40,6 @@ impl<'d, T: Instance> AdvancedPwm<'d, T> { #[cfg(not(stm32f334))] tim.calibrate(); - Self { - _inner: tim, - master: Master { phantom: PhantomData }, - burst_controller: BurstController { phantom: PhantomData }, - ch_a: ChA { phantom: PhantomData }, - ch_b: ChB { phantom: PhantomData }, - ch_c: ChC { phantom: PhantomData }, - ch_d: ChD { phantom: PhantomData }, - ch_e: ChE { phantom: PhantomData }, - #[cfg(hrtim_v2)] - ch_f: ChF { phantom: PhantomData }, - } + Self { _inner: tim } } } - -/// HRTIM PWM pin. -pub struct PwmPin<'d, T, C> { - _pin: Flex<'d>, - phantom: PhantomData<(T, C)>, -} - -/// HRTIM complementary PWM pin. -pub struct ComplementaryPwmPin<'d, T, C> { - _pin: Flex<'d>, - phantom: PhantomData<(T, C)>, -} - -macro_rules! channel_impl { - ($new_chx:ident, $new_chx_with_config:ident, $channel:tt, $ch_num:expr, $pin_trait:ident, $complementary_pin_trait:ident) => { - impl<'d, T: Instance> PwmPin<'d, T, $channel> { - #[doc = concat!("Create a new ", stringify!($channel), " PWM pin instance.")] - pub fn $new_chx(pin: Peri<'d, impl $pin_trait>) -> Self { - critical_section::with(|_| { - pin.set_low(); - set_as_af!(pin, AfType::output(OutputType::PushPull, Speed::VeryHigh)); - }); - PwmPin { - _pin: Flex::new(pin), - phantom: PhantomData, - } - } - - #[doc = concat!("Create a new ", stringify!($channel), " PWM pin instance with a specific configuration.")] - pub fn $new_chx_with_config( - pin: Peri<'d, impl $pin_trait>, - pin_config: PwmPinConfig, - ) -> Self { - critical_section::with(|_| { - pin.set_low(); - set_as_af!(pin, AfType::output(pin_config.output_type, pin_config.speed)); - }); - PwmPin { - _pin: Flex::new(pin), - phantom: PhantomData, - } - } - } - - impl<'d, T: Instance> ComplementaryPwmPin<'d, T, $channel> { - #[doc = concat!("Create a new ", stringify!($channel), " complementary PWM pin instance.")] - pub fn $new_chx(pin: Peri<'d, impl $complementary_pin_trait>) -> Self { - critical_section::with(|_| { - pin.set_low(); - set_as_af!(pin, AfType::output(OutputType::PushPull, Speed::VeryHigh)); - }); - ComplementaryPwmPin { - _pin: Flex::new(pin), - phantom: PhantomData, - } - } - - #[doc = concat!("Create a new ", stringify!($channel), " complementary PWM pin instance with a specific configuration.")] - pub fn $new_chx_with_config( - pin: Peri<'d, impl $complementary_pin_trait>, - pin_config: PwmPinConfig, - ) -> Self { - critical_section::with(|_| { - pin.set_low(); - set_as_af!(pin, AfType::output(pin_config.output_type, pin_config.speed)); - }); - ComplementaryPwmPin { - _pin: Flex::new(pin), - phantom: PhantomData, - } - } - } - }; -} - -channel_impl!( - new_cha, - new_cha_with_config, - ChA, - 0, - ChannelAPin, - ChannelAComplementaryPin -); -channel_impl!( - new_chb, - new_chb_with_config, - ChB, - 1, - ChannelBPin, - ChannelBComplementaryPin -); -channel_impl!( - new_chc, - new_chc_with_config, - ChC, - 2, - ChannelCPin, - ChannelCComplementaryPin -); -channel_impl!( - new_chd, - new_chd_with_config, - ChD, - 3, - ChannelDPin, - ChannelDComplementaryPin -); -channel_impl!( - new_che, - new_che_with_config, - ChE, - 4, - ChannelEPin, - ChannelEComplementaryPin -); -#[cfg(hrtim_v2)] -channel_impl!( - new_chf, - new_chf_with_config, - ChF, - 5, - ChannelFPin, - ChannelFComplementaryPin -); diff --git a/embassy-stm32/src/hrtim/mod.rs b/embassy-stm32/src/hrtim/mod.rs index d1fb3b9de5..44119fb3e2 100644 --- a/embassy-stm32/src/hrtim/mod.rs +++ b/embassy-stm32/src/hrtim/mod.rs @@ -1,7 +1,5 @@ //! High Resolution Timer (HRTIM) -use core::marker::PhantomData; - use embassy_hal_internal::PeripheralType; mod advanced_channel; @@ -11,70 +9,111 @@ mod fullbridge; pub mod low_level; mod resonant_converter; +use core::marker::PhantomData; use core::mem::MaybeUninit; pub use advanced_channel::AdvancedChannel; -pub use advanced_pwm::{AdvancedPwm, ComplementaryPwmPin, PwmPin}; +pub use advanced_pwm::AdvancedPwm; pub use bridge_converter::BridgeConverter; use embassy_hal_internal::Peri; pub use fullbridge::FullBridgeConverter; pub use resonant_converter::ResonantConverter; use stm32_hrtim::control::{HrPwmControl, HrTimOngoingCalibration}; use stm32_hrtim::output::{Output1Pin, Output2Pin}; +#[cfg(hrtim_v2)] +use stm32_hrtim::pac::HRTIM_TIMF; +use stm32_hrtim::pac::{HRTIM_MASTER, HRTIM_TIMA, HRTIM_TIMB, HRTIM_TIMC, HRTIM_TIMD, HRTIM_TIME}; +pub use stm32_hrtim::{self, Pscl1, Pscl2, Pscl4, Pscl8, Pscl16, Pscl32, Pscl64, Pscl128, PsclDefault}; +use stm32_hrtim::{DacResetTrigger, DacStepTrigger, HrParts, HrPwmBuilder}; +use crate::gpio::{AfType, Flex, OutputType, Speed}; +use crate::peripherals::HRTIM1; +use crate::rcc; use crate::rcc::RccPeripheral; use crate::time::Hertz; +use crate::timer::simple_pwm::PwmPinConfig; -/// HRTIM burst controller instance. -pub struct BurstController { - phantom: PhantomData, +/// Timer channel. +#[derive(Clone, Copy)] +pub enum Channel { + /// Channel A. + ChA, + /// Channel B. + ChB, + /// Channel C. + ChC, + /// Channel D. + ChD, + /// Channel E. + ChE, + #[cfg(hrtim_v2)] + /// Channel F. + ChF, } -/// HRTIM master instance. -pub struct Master { - phantom: PhantomData, +/// Burst Controller marker type. +pub enum BurstController {} + +/// Master marker type. +pub enum Master {} + +/// Channel A marker type. +pub enum ChA {} +/// Channel B marker type. +pub enum ChB {} +/// Channel C marker type. +pub enum ChC {} +/// Channel D marker type. +pub enum ChD {} + +/// Channel E marker type. +pub enum ChE {} + +#[cfg(hrtim_v2)] +/// Channel F marker type. +pub enum ChF {} + +/// Timer channel trait. +#[allow(private_bounds)] +pub trait HRTimerChannel: SealedTimerChannel { + /// The runtime channel. + const CHANNEL: Channel; } -/// HRTIM channel A instance. -pub struct ChA { - phantom: PhantomData, +trait SealedTimerChannel {} + +impl HRTimerChannel for ChA { + const CHANNEL: Channel = Channel::ChA; } -/// HRTIM channel B instance. -pub struct ChB { - phantom: PhantomData, +impl HRTimerChannel for ChB { + const CHANNEL: Channel = Channel::ChB; } -/// HRTIM channel C instance. -pub struct ChC { - phantom: PhantomData, +impl HRTimerChannel for ChC { + const CHANNEL: Channel = Channel::ChC; } -/// HRTIM channel D instance. -pub struct ChD { - phantom: PhantomData, +impl HRTimerChannel for ChD { + const CHANNEL: Channel = Channel::ChD; } -/// HRTIM channel E instance. -pub struct ChE { - phantom: PhantomData, +impl HRTimerChannel for ChE { + const CHANNEL: Channel = Channel::ChE; } #[cfg(hrtim_v2)] -/// HRTIM channel F instance. -pub struct ChF { - phantom: PhantomData, +impl HRTimerChannel for ChF { + const CHANNEL: Channel = Channel::ChF; } +impl SealedTimerChannel for ChA {} +impl SealedTimerChannel for ChB {} +impl SealedTimerChannel for ChC {} +impl SealedTimerChannel for ChD {} +impl SealedTimerChannel for ChE {} #[cfg(hrtim_v2)] -use stm32_hrtim::pac::HRTIM_TIMF; -use stm32_hrtim::pac::{HRTIM_MASTER, HRTIM_TIMA, HRTIM_TIMB, HRTIM_TIMC, HRTIM_TIMD, HRTIM_TIME}; -pub use stm32_hrtim::{self, Pscl1, Pscl2, Pscl4, Pscl8, Pscl16, Pscl32, Pscl64, Pscl128, PsclDefault}; -use stm32_hrtim::{DacResetTrigger, DacStepTrigger, HrParts, HrPwmBuilder}; - -use crate::gpio::{AfType, OutputType, Speed}; -use crate::peripherals::HRTIM1; -use crate::rcc; +impl SealedTimerChannel for ChF {} /// Uninitialized HRTIM resources as returned by [HrControltExt::hr_control] pub struct Parts { @@ -166,6 +205,91 @@ macro_rules! impl_finalize { )+}; } +/// PWM pin wrapper. +/// +/// This wraps a pin to make it usable with PWM. +pub struct PwmPin<'d, T, C, #[cfg(afio)] A> { + #[allow(unused)] + pub(crate) pin: Flex<'d>, + phantom: PhantomData, +} + +impl<'d, T: Instance, C: HRTimerChannel, #[cfg(afio)] A> if_afio!(PwmPin<'d, T, C, A>) { + /// Create a new PWM pin instance. + pub fn new(pin: Peri<'d, if_afio!(impl HRTimerPin)>, output_type: OutputType) -> Self { + critical_section::with(|_| { + pin.set_low(); + set_as_af!(pin, AfType::output(output_type, Speed::VeryHigh)); + }); + PwmPin { + pin: Flex::new(pin), + phantom: PhantomData, + } + } + + /// Create a new PWM pin instance with a specific configuration. + pub fn new_with_config(pin: Peri<'d, if_afio!(impl HRTimerPin)>, pin_config: PwmPinConfig) -> Self { + critical_section::with(|_| { + pin.set_low(); + #[cfg(gpio_v1)] + set_as_af!(pin, AfType::output(pin_config.output_type, pin_config.speed)); + #[cfg(gpio_v2)] + set_as_af!( + pin, + AfType::output_pull(pin_config.output_type, pin_config.speed, pin_config.pull) + ); + }); + PwmPin { + pin: Flex::new(pin), + phantom: PhantomData, + } + } +} + +/// PWM pin wrapper. +/// +/// This wraps a pin to make it usable with PWM. +pub struct ComplementaryPwmPin<'d, T, C, #[cfg(afio)] A> { + #[allow(unused)] + pub(crate) pin: Flex<'d>, + phantom: PhantomData, +} + +impl<'d, T: Instance, C: HRTimerChannel, #[cfg(afio)] A> if_afio!(ComplementaryPwmPin<'d, T, C, A>) { + /// Create a new PWM pin instance. + pub fn new(pin: Peri<'d, if_afio!(impl HRTimerComplementaryPin)>, output_type: OutputType) -> Self { + critical_section::with(|_| { + pin.set_low(); + set_as_af!(pin, AfType::output(output_type, Speed::VeryHigh)); + }); + Self { + pin: Flex::new(pin), + phantom: PhantomData, + } + } + + /// Create a new PWM pin instance with a specific configuration. + pub fn new_with_config( + pin: Peri<'d, if_afio!(impl HRTimerComplementaryPin)>, + pin_config: PwmPinConfig, + ) -> Self { + critical_section::with(|_| { + pin.set_low(); + #[cfg(gpio_v1)] + set_as_af!(pin, AfType::output(pin_config.output_type, pin_config.speed)); + #[cfg(gpio_v2)] + set_as_af!( + pin, + AfType::output_pull(pin_config.output_type, pin_config.speed, pin_config.pull) + ); + }); + Self { + pin: Flex::new(pin), + phantom: PhantomData, + } + } +} + /// Wrapper a pin that can be used as output to one of the HRTIM timer instances pub struct Pin

{ /// The speed setting of the pin @@ -201,9 +325,9 @@ pub trait Out2Pin: Output2Pin { } macro_rules! pins_helper { - ($TIMX:ty, $ChannelXPin:ident, $OutYPin:ident, $OutputYPin:ident, $HrOutY:ident) => { - unsafe impl<'d, T: $ChannelXPin> $OutputYPin<$TIMX> for Pin> {} - impl<'d, T: $ChannelXPin> $OutYPin<$TIMX> for Pin> { + ($TIMX:ty, $ChannelXPin:ident<$Channel:ident>, $OutYPin:ident, $OutputYPin:ident, $HrOutY:ident) => { + unsafe impl<'d, T: $ChannelXPin> $OutputYPin<$TIMX> for Pin> {} + impl<'d, T: $ChannelXPin> $OutYPin<$TIMX> for Pin> { fn connect_to_hrtim(self) { self.pin.set_as_af( self.pin.af_num(), @@ -214,53 +338,28 @@ macro_rules! pins_helper { }; } -pins_helper!(HRTIM_TIMA, ChannelAPin, Out1Pin, Output1Pin, HrOut1); -pins_helper!(HRTIM_TIMA, ChannelAComplementaryPin, Out2Pin, Output2Pin, HrOut2); -pins_helper!(HRTIM_TIMB, ChannelBPin, Out1Pin, Output1Pin, HrOut1); -pins_helper!(HRTIM_TIMB, ChannelBComplementaryPin, Out2Pin, Output2Pin, HrOut2); -pins_helper!(HRTIM_TIMC, ChannelCPin, Out1Pin, Output1Pin, HrOut1); -pins_helper!(HRTIM_TIMC, ChannelCComplementaryPin, Out2Pin, Output2Pin, HrOut2); -pins_helper!(HRTIM_TIMD, ChannelDPin, Out1Pin, Output1Pin, HrOut1); -pins_helper!(HRTIM_TIMD, ChannelDComplementaryPin, Out2Pin, Output2Pin, HrOut2); -pins_helper!(HRTIM_TIME, ChannelEPin, Out1Pin, Output1Pin, HrOut1); -pins_helper!(HRTIM_TIME, ChannelEComplementaryPin, Out2Pin, Output2Pin, HrOut2); - -#[cfg(stm32g4)] -pins_helper!(HRTIM_TIMF, ChannelFPin, Out1Pin, Output1Pin, HrOut1); -#[cfg(stm32g4)] -pins_helper!(HRTIM_TIMF, ChannelFComplementaryPin, Out2Pin, Output2Pin, HrOut2); - -// macro_rules! pins { -// ($($TIMX:ty: CH1: $CH1:ident<$CH1_AF:literal>, CH2: $CH2:ident<$CH2_AF:literal>),+) => {$( -// pins_helper!($TIMX, Out1Pin, Output1Pin, HrOut1, $CH1<$CH1_AF>); -// pins_helper!($TIMX, Out2Pin, Output2Pin, HrOut2, $CH2<$CH1_AF>); -// )+}; -// } - -// #[cfg(stm32g4)] -// pins! { -// HRTIM_TIMA: CH1: PA8<13>, CH2: PA9<13>, -// HRTIM_TIMB: CH1: PA10<13>, CH2: PA11<13>, -// HRTIM_TIMC: CH1: PB12<13>, CH2: PB13<13>, -// HRTIM_TIMD: CH1: PB14<13>, CH2: PB15<13>, -// HRTIM_TIME: CH1: PC8<3>, CH2: PC9<3>, -// HRTIM_TIMF: CH1: PC6<13>, CH2: PC7<13> -// } - -pin_trait!(ChannelAPin, Instance); -pin_trait!(ChannelAComplementaryPin, Instance); -pin_trait!(ChannelBPin, Instance); -pin_trait!(ChannelBComplementaryPin, Instance); -pin_trait!(ChannelCPin, Instance); -pin_trait!(ChannelCComplementaryPin, Instance); -pin_trait!(ChannelDPin, Instance); -pin_trait!(ChannelDComplementaryPin, Instance); -pin_trait!(ChannelEPin, Instance); -pin_trait!(ChannelEComplementaryPin, Instance); -#[cfg(hrtim_v2)] -pin_trait!(ChannelFPin, Instance); +macro_rules! pins { + ($($TIMX:ty: CH1: $CH1:ident<$CH1_AF:ident>, CH2: $CH2:ident<$CH2_AF:ident>),+) => {$( + pins_helper!($TIMX, $CH1<$CH1_AF>, Out1Pin, Output1Pin, HrOut1); + pins_helper!($TIMX, $CH2<$CH2_AF>, Out2Pin, Output2Pin, HrOut2); + )+}; +} + +pins! { + HRTIM_TIMA: CH1: HRTimerPin, CH2: HRTimerComplementaryPin, + HRTIM_TIMB: CH1: HRTimerPin, CH2: HRTimerComplementaryPin, + HRTIM_TIMC: CH1: HRTimerPin, CH2: HRTimerComplementaryPin, + HRTIM_TIMD: CH1: HRTimerPin, CH2: HRTimerComplementaryPin, + HRTIM_TIME: CH1: HRTimerPin, CH2: HRTimerComplementaryPin +} + #[cfg(hrtim_v2)] -pin_trait!(ChannelFComplementaryPin, Instance); +pins! { + HRTIM_TIMF: CH1: HRTimerPin, CH2: HRTimerComplementaryPin +} + +pin_trait!(HRTimerPin, Instance, HRTimerChannel, @A); +pin_trait!(HRTimerComplementaryPin, Instance, HRTimerChannel, @A); /// Prescaler #[repr(u8)] diff --git a/examples/stm32g474/src/bin/hrtim.rs b/examples/stm32g474/src/bin/hrtim.rs index 53c87f16bf..6220238930 100644 --- a/examples/stm32g474/src/bin/hrtim.rs +++ b/examples/stm32g474/src/bin/hrtim.rs @@ -4,7 +4,8 @@ use defmt::*; use embassy_executor::Spawner; use embassy_stm32::Config; -use embassy_stm32::hrtim::{AdvancedPwm, ComplementaryPwmPin, PwmPin}; +use embassy_stm32::gpio::OutputType; +use embassy_stm32::hrtim::{ComplementaryPwmPin, PwmPin}; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] @@ -13,27 +14,27 @@ async fn main(_spawner: Spawner) { info!("Hello World!"); - let ch3 = PwmPin::new_chc(p.PB12); - let ch3n = ComplementaryPwmPin::new_chc(p.PB13); + let _ch3 = PwmPin::new(p.PB12, OutputType::PushPull); + let _ch3n = ComplementaryPwmPin::new(p.PB13, OutputType::PushPull); - let ch4 = PwmPin::new_chd(p.PB14); - let ch4n = ComplementaryPwmPin::new_chd(p.PB15); - - let _pwm = AdvancedPwm::new( - p.HRTIM1, - None, - None, - None, - None, - Some(ch3), - Some(ch3n), - Some(ch4), - Some(ch4n), - None, - None, - None, - None, - ); + // let ch4 = PwmPin::new_chd(p.PB14); + // let ch4n = ComplementaryPwmPin::new_chd(p.PB15); + // + // let _pwm = AdvancedPwm::new( + // p.HRTIM1, + // None, + // None, + // None, + // None, + // Some(ch3), + // Some(ch3n), + // Some(ch4), + // Some(ch4n), + // None, + // None, + // None, + // None, + // ); // pwm.set_master_frequency(Hertz::mhz(1)); // From ba5fb2be2e4e1898b8206e7ec87fdc059cba3038 Mon Sep 17 00:00:00 2001 From: xoviat Date: Fri, 20 Mar 2026 18:11:45 -0500 Subject: [PATCH 17/17] wip --- examples/stm32g474/src/bin/hrtim.rs | 158 ++++++++++++++++++---------- 1 file changed, 100 insertions(+), 58 deletions(-) diff --git a/examples/stm32g474/src/bin/hrtim.rs b/examples/stm32g474/src/bin/hrtim.rs index 6220238930..c4f7b495fb 100644 --- a/examples/stm32g474/src/bin/hrtim.rs +++ b/examples/stm32g474/src/bin/hrtim.rs @@ -3,69 +3,111 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_stm32::Config; -use embassy_stm32::gpio::OutputType; -use embassy_stm32::hrtim::{ComplementaryPwmPin, PwmPin}; +use embassy_stm32::gpio::Speed; +use embassy_stm32::hrtim::stm32_hrtim::compare_register::HrCompareRegister; +use embassy_stm32::hrtim::stm32_hrtim::output::HrOutput; +use embassy_stm32::hrtim::stm32_hrtim::timer::HrTimer; +use embassy_stm32::hrtim::stm32_hrtim::{HrParts, HrPwmAdvExt, PreloadSource}; +use embassy_stm32::hrtim::{HrControltExt, HrPwmBuilderExt, Parts}; +use embassy_stm32::{Config, hrtim}; +use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] async fn main(_spawner: Spawner) { - let p = embassy_stm32::init(Config::default()); + let mut config = Config::default(); + { + // Set system frequency to 16MHz * 15/1/2 = 120MHz + // This would lead to HrTim running at 120MHz * 32 = 3.84GHz... + use embassy_stm32::rcc::*; + config.rcc.hsi = true; + config.rcc.pll = Some(Pll { + source: PllSource::HSI, + divp: None, + divq: None, + divr: Some(PllRDiv::DIV2), + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL15, + }); + config.rcc.sys = Sysclk::PLL1_R; + } + let p = embassy_stm32::init(config); info!("Hello World!"); - let _ch3 = PwmPin::new(p.PB12, OutputType::PushPull); - let _ch3n = ComplementaryPwmPin::new(p.PB13, OutputType::PushPull); - - // let ch4 = PwmPin::new_chd(p.PB14); - // let ch4n = ComplementaryPwmPin::new_chd(p.PB15); - // - // let _pwm = AdvancedPwm::new( - // p.HRTIM1, - // None, - // None, - // None, - // None, - // Some(ch3), - // Some(ch3n), - // Some(ch4), - // Some(ch4n), - // None, - // None, - // None, - // None, - // ); - - // pwm.set_master_frequency(Hertz::mhz(1)); - // - // info!("pwm constructed"); - // - // let mut bridge = FullBridgeConverter::new(&mut pwm.ch_c, &mut pwm.ch_d, Hertz::mhz(1)); - // - // let max_duty_master = bridge.get_max_compare_value_master(); - // - // info!("max compare value master: {}", max_duty_master); - // - // bridge.set_dead_time(54); // 10ns * 5.376ns step/ns - // bridge.set_duty(max_duty_master / 2); - // bridge.set_minimum_duty(135); // 25ns * 5.376 step/ns - // - // bridge.start(); - // - // Timer::after_millis(500).await; - // - // info!("end setup"); - // - // let mut duty = 0; - // loop { - // bridge.set_duty(duty); - // - // if duty == max_duty_master { - // duty = 0; - // } else { - // duty = duty + 1; - // } - // - // Timer::after_millis(10).await; - // } + let pin1 = hrtim::Pin { + pin: p.PA8, + speed: Speed::Low, + }; + let pin2 = hrtim::Pin { + pin: p.PA9, + speed: Speed::Low, + }; + + // ...with a prescaler of 4 this gives us a HrTimer with a tick rate of 960MHz + // With max the max period set, this would be 960MHz/2^16 ~= 14.6kHz... + let prescaler = hrtim::Pscl4; + + let Parts { control, tima, .. } = p.HRTIM1.hr_control(); + let (control, ..) = control.wait_for_calibration(); + let mut control = control.constrain(); + + // . . . . + // . 30% . . . + // .---- . .---- . + //pin1 | | . | | . + // | | . | | . + // -------- ---------------------------- -------------------- + // . .---- . .---- + //pin2 . | | . | | + // . | | . | | + // ------------------------ ---------------------------- ---- + // . . . . + // . . . . + + let HrParts { + mut timer, + mut cr1, + mut out1, + mut out2, + .. + } = tima + .pwm_advanced(pin1, pin2) + .prescaler(prescaler) + .period(0xFFFF) + .push_pull_mode(true) // Set push pull mode, out1 and out2 are + // alternated every period with one being + // inactive and the other getting to output its wave form + // as normal + .preload(PreloadSource::OnRepetitionUpdate) + .finalize(&mut control); + + out1.enable_rst_event(&cr1); // Set low on compare match with cr1 + out2.enable_rst_event(&cr1); + + out1.enable_set_event(&timer); // Set high at new period + out2.enable_set_event(&timer); + + info!("pwm constructed"); + + out1.enable(); + out2.enable(); + timer.start(&mut control.control); + + // Step frequency from 14.6kHz to about 146kHz(half of that when only looking at one pin) + for i in 1..=10 { + let new_period = u16::MAX / i; + + cr1.set_duty(new_period / 3); + timer.set_period(new_period); + + Timer::after_millis(1).await; + } + + out1.disable(); + out2.disable(); + + info!("end program"); + + cortex_m::asm::bkpt(); }