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 new file mode 100644 index 0000000000..044a151d20 --- /dev/null +++ b/embassy-stm32/src/hrtim/advanced_channel.rs @@ -0,0 +1,109 @@ +//! AdvancedChannel + +use super::{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 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; + + // 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; + + Self::set_channel_prescaler(channel, psc); + Self::set_channel_period(channel, 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); + }); + } +} + +#[allow(unused)] +trait SealedAdvancedChannelMaster {} + +/// Advanced channel instance trait. +#[allow(private_bounds)] +#[allow(unused)] +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)); + } +} diff --git a/embassy-stm32/src/hrtim/advanced_pwm.rs b/embassy-stm32/src/hrtim/advanced_pwm.rs new file mode 100644 index 0000000000..5e3e75db8e --- /dev/null +++ b/embassy-stm32/src/hrtim/advanced_pwm.rs @@ -0,0 +1,45 @@ +//! AdvancedPwm + +use embassy_hal_internal::Peri; + +#[cfg(hrtim_v2)] +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>, +} + +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)>, + _chb: Option)>, + _chc: Option)>, + _chd: Option)>, + _che: Option)>, + #[cfg(hrtim_v2)] _chf: Option)>, + ) -> Self { + Self::new_inner(tim) + } + + fn new_inner(tim: Peri<'d, T>) -> Self { + let mut tim = HrTimer::new(tim); + + #[cfg(stm32f334)] + if crate::pac::RCC.cfgr3().read().hrtim1sw() == crate::pac::rcc::vals::Timsw::PLL1_P { + tim.calibrate(); + } + + #[cfg(not(stm32f334))] + tim.calibrate(); + + Self { _inner: tim } + } +} diff --git a/embassy-stm32/src/hrtim/bridge_converter.rs b/embassy-stm32/src/hrtim/bridge_converter.rs index 2e2072b873..eea968a331 100644 --- a/embassy-stm32/src/hrtim/bridge_converter.rs +++ b/embassy-stm32/src/hrtim/bridge_converter.rs @@ -1,90 +1,65 @@ //! 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. -use stm32_hrtim::compare_register::HrCompareRegister; -use stm32_hrtim::control::{HrPwmControl, HrPwmCtrl}; -use stm32_hrtim::output::{HrOutput, Output1Pin, Output2Pin}; -use stm32_hrtim::timer::{HrTim, HrTimer}; -use stm32_hrtim::{HrParts, HrPwmAdvExt, HrPwmBuilder, HrtimPrescaler, PreloadSource, capture}; - -use crate::hrtim::HrPwmBuilderExt; -use crate::peripherals::HRTIM1; -use crate::rcc::SealedRccPeripheral; + +use core::marker::PhantomData; + +use super::{AdvancedChannel, Instance}; use crate::time::Hertz; /// Fixed-frequency bridge converter driver. -pub struct BridgeConverter { - timer: HrParts, +/// +/// 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 -where - TIM: stm32_hrtim::timer::InstanceX + HrPwmAdvExt, - HrTim: HrTimer, -{ +impl> BridgeConverter { /// Create a new HRTIM bridge converter driver. - pub fn new( - timer: TIM, - pin1: P1, - pin2: P2, - frequency: Hertz, - prescaler: PSCL, - hr_control: &mut HrPwmControl, - ) -> Self - where - P1: Output1Pin, - P2: Output2Pin, - HrPwmBuilder: HrPwmBuilderExt, - { - let f = frequency.0; - - let timer_f = HRTIM1::frequency().0; - - let psc_min = (timer_f / f) / (u16::MAX as u32 / 32); - let psc = PSCL::VALUE as u32; - assert!( - psc >= psc_min, - "Prescaler set too low to be able to reach target frequency" - ); - - let timer_f = 32 * (timer_f / psc); - let per: u16 = (timer_f / f) as u16; - - let mut timer = timer - .pwm_advanced(pin1, pin2) - .prescaler(prescaler) - .period(per) - .preload(PreloadSource::OnRepetitionUpdate) - .finalize(hr_control); + pub fn new(_channel: C, frequency: Hertz) -> Self { + C::set_channel_frequency(C::index(), frequency); + + // Always enable preload + T::regs().tim(C::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(C::index(), true); + w.set_t2oen(C::index(), 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 - timer.out1.enable_set_event(&timer.timer); + T::regs().tim(C::index()).setr(0).modify(|w| w.set_per(true)); // Set output 1 to inactive on a compare 1 event - timer.out1.enable_rst_event(&timer.cr1); + T::regs().tim(C::index()).rstr(0).modify(|w| w.set_cmp(0, true)); // Set output 2 to active on a compare 2 event - timer.out2.enable_set_event(&timer.cr2); + T::regs().tim(C::index()).setr(1).modify(|w| w.set_cmp(1, true)); // Set output 2 to inactive on a compare 3 event - timer.out2.enable_rst_event(&timer.cr3); + T::regs().tim(C::index()).rstr(1).modify(|w| w.set_cmp(2, true)); Self { - timer, + timer: PhantomData, + channel: PhantomData, dead_time: 0, primary_duty: 0, min_secondary_duty: 0, @@ -93,19 +68,18 @@ where } /// Start HRTIM. - pub fn start(&mut self, hr_control: &mut HrPwmCtrl) { - self.timer.timer.start(hr_control); + pub fn start(&mut self) { + T::regs().mcr().modify(|w| w.set_tcen(C::index(), true)); } /// Stop HRTIM. - pub fn stop(&mut self, hr_control: &mut HrPwmCtrl) { - self.timer.timer.stop(hr_control); + pub fn stop(&mut self) { + 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); @@ -118,17 +92,24 @@ where /// 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); }) - }*/ + } fn update_primary_duty_or_dead_time(&mut self) { self.min_secondary_duty = self.primary_duty + self.dead_time; - self.timer.cr1.set_duty(self.primary_duty); - self.timer.cr2.set_duty(self.min_secondary_duty); + + T::regs() + .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)); } /// Set the dead time as a proportion of the maximum compare value @@ -140,7 +121,7 @@ where /// Get the maximum compare value of a duty cycle pub fn get_max_compare_value(&mut self) -> u16 { - self.timer.timer.get_period() + T::regs().tim(C::index()).per().read().per() } /// The primary duty is the period in which the primary switch is active @@ -165,6 +146,6 @@ where secondary_duty }; - self.timer.cr3.set_duty(secondary_duty); + T::regs().tim(C::index()).cmp(2).modify(|w| w.set_cmp(secondary_duty)); } } diff --git a/embassy-stm32/src/hrtim/fullbridge.rs b/embassy-stm32/src/hrtim/fullbridge.rs new file mode 100644 index 0000000000..a7ec6271df --- /dev/null +++ b/embassy-stm32/src/hrtim/fullbridge.rs @@ -0,0 +1,180 @@ +//! Fixed-frequency bridge converter driver. + +use core::marker::PhantomData; + +use super::{AdvancedChannel, Instance, 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/low_level.rs b/embassy-stm32/src/hrtim/low_level.rs new file mode 100644 index 0000000000..7503dcd17a --- /dev/null +++ b/embassy-stm32/src/hrtim/low_level.rs @@ -0,0 +1,69 @@ +//! 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 0886aaaf26..44119fb3e2 100644 --- a/embassy-stm32/src/hrtim/mod.rs +++ b/embassy-stm32/src/hrtim/mod.rs @@ -1,15 +1,23 @@ //! High Resolution Timer (HRTIM) -mod traits; +use embassy_hal_internal::PeripheralType; -pub mod bridge_converter; -pub mod resonant_converter; +mod advanced_channel; +mod advanced_pwm; +mod bridge_converter; +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; +pub use bridge_converter::BridgeConverter; use embassy_hal_internal::Peri; -#[allow(unused_imports)] // HrPwmAdvExt::pwm_advanced used by doc -use stm32_hrtim::HrPwmAdvExt; +pub use fullbridge::FullBridgeConverter; +pub use resonant_converter::ResonantConverter; use stm32_hrtim::control::{HrPwmControl, HrTimOngoingCalibration}; use stm32_hrtim::output::{Output1Pin, Output2Pin}; #[cfg(hrtim_v2)] @@ -17,11 +25,95 @@ 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::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; + +/// 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, +} + +/// 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; +} + +trait SealedTimerChannel {} + +impl HRTimerChannel for ChA { + const CHANNEL: Channel = Channel::ChA; +} + +impl HRTimerChannel for ChB { + const CHANNEL: Channel = Channel::ChB; +} + +impl HRTimerChannel for ChC { + const CHANNEL: Channel = Channel::ChC; +} + +impl HRTimerChannel for ChD { + const CHANNEL: Channel = Channel::ChD; +} + +impl HRTimerChannel for ChE { + const CHANNEL: Channel = Channel::ChE; +} + +#[cfg(hrtim_v2)] +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)] +impl SealedTimerChannel for ChF {} /// Uninitialized HRTIM resources as returned by [HrControltExt::hr_control] pub struct Parts { @@ -113,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 @@ -148,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(), @@ -161,50 +338,156 @@ 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)] +#[derive(Clone, Copy)] +#[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, +} + +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 { + /// Computer the minium prescaler for high resolution + 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() + } + + /// Compute the minium prescaler for low resolution + 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)); + } +} + +/// 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 20146c49c1..dbe69cdb24 100644 --- a/embassy-stm32/src/hrtim/resonant_converter.rs +++ b/embassy-stm32/src/hrtim/resonant_converter.rs @@ -1,96 +1,68 @@ //! 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. -use stm32_hrtim::control::HrPwmControl; -pub use stm32_hrtim::deadtime::DeadtimeConfig; -use stm32_hrtim::output::{HrOutput, Output1Pin, Output2Pin}; -use stm32_hrtim::timer::{HrTim, HrTimer}; -use stm32_hrtim::{HrParts, HrPwmAdvExt, HrPwmBuilder, HrtimPrescaler, InterleavedMode, PreloadSource, capture}; -use crate::hrtim::HrPwmBuilderExt; -use crate::peripherals::HRTIM1; -use crate::rcc::SealedRccPeripheral; +use core::marker::PhantomData; + +use super::{AdvancedChannel, Instance}; use crate::time::Hertz; /// Variable-frequency resonant converter driver. -pub struct ResonantConverter { - timer: HrParts, +/// +/// 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 -where - TIM: stm32_hrtim::timer::InstanceX + HrPwmAdvExt, - HrTim: HrTimer, -{ +impl> ResonantConverter { /// Create a new variable-frequency resonant converter driver. - pub fn new( - timer: TIM, - pin1: P1, - pin2: P2, - prescaler: PSCL, - min_frequency: Hertz, - max_frequency: Hertz, - hr_control: &mut HrPwmControl, - deadtime_cfg: DeadtimeConfig, - ) -> Self - where - P1: Output1Pin, - P2: Output2Pin, - HrPwmBuilder: HrPwmBuilderExt, - { - let f_min = min_frequency.0; - - let timer_f = HRTIM1::frequency().0; - - let psc_min = (timer_f / f_min) / (u16::MAX as u32 / 32); - let psc = PSCL::VALUE as u32; - assert!( - psc >= psc_min, - "Prescaler set too low to be able to reach target frequency" - ); + pub fn new(_channel: C, min_frequency: Hertz, max_frequency: Hertz) -> Self { + C::set_channel_frequency(C::index(), min_frequency); - let timer_f = 32 * (timer_f / psc); - let max_period: u16 = (timer_f / f_min) as u16; + // Always enable preload + T::regs().tim(C::index()).cr().modify(|w| { + w.set_preen(true); + w.set_repu(true); - let mut timer = timer - .pwm_advanced(pin1, pin2) - .prescaler(prescaler) - .period(max_period) - .preload(PreloadSource::OnRepetitionUpdate) - .interleaved_mode(InterleavedMode::Dual) - // Dead-time generator can be used in this case because the primary fets - // of a resonant converter are always complementary - .deadtime(deadtime_cfg) - .finalize(hr_control); + w.set_cont(true); + w.set_half(true); + }); - // Set output 1 to active on a period event - timer.out1.enable_set_event(&timer.timer); + // Enable timer outputs + T::regs().oenr().modify(|w| { + w.set_t1oen(C::index(), true); + w.set_t2oen(C::index(), true); + }); - // Set output 1 to inactive on a compare 1 event - timer.out1.enable_rst_event(&timer.cr1); - - timer.out1.enable(); - timer.out2.enable(); + // Dead-time generator can be used in this case because the primary fets + // of a resonant converter are always complementary + T::regs().tim(C::index()).outr().modify(|w| w.set_dten(true)); + 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 { - timer, + 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) { + C::set_channel_dead_time(C::index(), value); + } + /// Set the timer period. pub fn set_period(&mut self, period: u16) { assert!(period < self.max_period); assert!(period > self.min_period); - self.timer.timer.set_period(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/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 9528cd9d5a..233bb114a9 100644 --- a/examples/stm32f334/src/bin/pwm.rs +++ b/examples/stm32f334/src/bin/pwm.rs @@ -3,11 +3,8 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_stm32::gpio::Speed; -use embassy_stm32::hrtim::bridge_converter::BridgeConverter; -use embassy_stm32::hrtim::*; -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 _}; @@ -32,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) @@ -69,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;