From aceba1c03fc45179d4e910fad254a31191cf0c44 Mon Sep 17 00:00:00 2001 From: xoviat Date: Sat, 1 Jul 2023 21:47:44 -0500 Subject: [PATCH] hrtim: fix example and auto adjust psc. --- embassy-stm32/src/pwm/advanced_pwm.rs | 87 ++++++++++++++++++++------- embassy-stm32/src/pwm/mod.rs | 37 ++++++++++-- examples/stm32f334/src/bin/button.rs | 27 +++++++++ examples/stm32f334/src/bin/pwm.rs | 65 +++++++++++--------- 4 files changed, 160 insertions(+), 56 deletions(-) create mode 100644 examples/stm32f334/src/bin/button.rs diff --git a/embassy-stm32/src/pwm/advanced_pwm.rs b/embassy-stm32/src/pwm/advanced_pwm.rs index 65f4e7ca7..fa34b2e18 100644 --- a/embassy-stm32/src/pwm/advanced_pwm.rs +++ b/embassy-stm32/src/pwm/advanced_pwm.rs @@ -144,6 +144,18 @@ impl<'d, T: HighResolutionCaptureCompare16bitInstance> AdvancedPwm<'d, T> { T::enable(); ::reset(); + // // Enable and and stabilize the DLL + // T::regs().dllcr().modify(|w| { + // // w.set_calen(true); + // // w.set_calrte(11); + // w.set_cal(true); + // }); + // + // debug!("wait for dll calibration"); + // while !T::regs().isr().read().dllrdy() {} + // + // debug!("dll calibration complete"); + Self { _inner: tim, master: Master { phantom: PhantomData }, @@ -173,12 +185,14 @@ impl BurstController { /// 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> { - phantom: PhantomData, - pub ch: C, + timer: PhantomData, + channel: PhantomData, + dead_time: u16, + primary_duty: u16, } impl> BridgeConverter { - pub fn new(channel: C, frequency: Hertz) -> Self { + pub fn new(_channel: C, frequency: Hertz) -> Self { use crate::pac::hrtim::vals::{Activeeffect, Cont, Inactiveeffect}; T::set_channel_frequency(C::raw(), frequency); @@ -186,15 +200,21 @@ impl> Bridge // Always enable preload T::regs().tim(C::raw()).cr().modify(|w| { w.set_preen(true); + w.set_repu(true); w.set_cont(Cont::CONTINUOUS); }); + // 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()) @@ -207,21 +227,23 @@ impl> Bridge .rstr(0) .modify(|w| w.set_cmp(0, Inactiveeffect::SETINACTIVE)); - // Set output 2 to active on a compare 1 event + // Set output 2 to active on a compare 2 event T::regs() .tim(C::raw()) .setr(1) - .modify(|w| w.set_cmp(0, Activeeffect::SETACTIVE)); + .modify(|w| w.set_cmp(1, Activeeffect::SETACTIVE)); - // Set output 2 to inactive on a compare 2 event + // Set output 2 to inactive on a compare 3 event T::regs() .tim(C::raw()) .rstr(1) - .modify(|w| w.set_cmp(1, Inactiveeffect::SETINACTIVE)); + .modify(|w| w.set_cmp(2, Inactiveeffect::SETINACTIVE)); Self { - phantom: PhantomData, - ch: channel, + timer: PhantomData, + channel: PhantomData, + dead_time: 0, + primary_duty: 0, } } @@ -236,7 +258,6 @@ impl> Bridge pub fn enable_burst_mode(&mut self) { use crate::pac::hrtim::vals::{Idlem, Idles}; - // TODO: fix metapac T::regs().tim(C::raw()).outr().modify(|w| { w.set_idlem(0, Idlem::SETIDLE); w.set_idlem(1, Idlem::SETIDLE); @@ -258,9 +279,18 @@ impl> Bridge }) } + fn update_primary_duty_or_dead_time(&mut self) { + 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.primary_duty + self.dead_time)); + } + /// 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); + pub fn set_dead_time(&mut self, dead_time: u16) { + self.dead_time = dead_time; + self.update_primary_duty_or_dead_time(); } /// Get the maximum compare value of a duty cycle @@ -272,15 +302,17 @@ impl> Bridge /// /// 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: u16) { - T::regs().tim(C::raw()).cmp(0).modify(|w| w.set_cmp(primary)); + pub fn set_primary_duty(&mut self, primary_duty: u16) { + self.primary_duty = primary_duty; + self.update_primary_duty_or_dead_time(); } - /// The primary duty is the period in any switch is active + /// The secondary duty is the period in any switch is active /// /// If less than or equal to the primary duty, the secondary switch will never be active - pub fn set_secondary_duty(&mut self, secondary: u16) { - T::regs().tim(C::raw()).cmp(1).modify(|w| w.set_cmp(secondary)); + /// 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) { + T::regs().tim(C::raw()).cmp(2).modify(|w| w.set_cmp(secondary_duty)); } } @@ -290,14 +322,14 @@ impl> Bridge /// but does not include secondary rectification, which is appropriate for applications /// with a low-voltage on the secondary side. pub struct ResonantConverter> { - phantom: PhantomData, + timer: PhantomData, + channel: PhantomData, min_period: u16, max_period: u16, - pub ch: C, } impl> ResonantConverter { - pub fn new(channel: C, min_frequency: Hertz, max_frequency: Hertz) -> Self { + pub fn new(_channel: C, min_frequency: Hertz, max_frequency: Hertz) -> Self { use crate::pac::hrtim::vals::Cont; T::set_channel_frequency(C::raw(), min_frequency); @@ -305,19 +337,30 @@ impl> Resona // Always enable preload T::regs().tim(C::raw()).cr().modify(|w| { w.set_preen(true); + w.set_repu(true); w.set_cont(Cont::CONTINUOUS); 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, - phantom: PhantomData, - ch: channel, } } diff --git a/embassy-stm32/src/pwm/mod.rs b/embassy-stm32/src/pwm/mod.rs index d09e38d0e..429a290ee 100644 --- a/embassy-stm32/src/pwm/mod.rs +++ b/embassy-stm32/src/pwm/mod.rs @@ -123,7 +123,7 @@ impl From for HighResolutionControlPrescaler { #[cfg(hrtim_v1)] impl HighResolutionControlPrescaler { - pub fn compute_min(val: u32) -> Self { + pub fn compute_min_high_res(val: u32) -> Self { *[ HighResolutionControlPrescaler::Div1, HighResolutionControlPrescaler::Div2, @@ -139,6 +139,18 @@ impl HighResolutionControlPrescaler { .next() .unwrap() } + + pub fn compute_min_low_res(val: u32) -> Self { + *[ + HighResolutionControlPrescaler::Div32, + HighResolutionControlPrescaler::Div64, + HighResolutionControlPrescaler::Div128, + ] + .iter() + .skip_while(|psc| >::into(**psc) <= val) + .next() + .unwrap() + } } pub(crate) mod sealed { @@ -367,10 +379,14 @@ foreach_interrupt! { let f = frequency.0; let timer_f = Self::frequency().0; let psc_min = (timer_f / f) / (u16::MAX as u32 / 32); - let psc = HighResolutionControlPrescaler::compute_min(psc_min); + let psc = if Self::regs().isr().read().dllrdy() { + HighResolutionControlPrescaler::compute_min_high_res(psc_min) + } else { + HighResolutionControlPrescaler::compute_min_low_res(psc_min) + }; let psc_val: u32 = psc.into(); - let timer_f = timer_f / psc_val; + let timer_f = 32 * (timer_f / psc_val); let per: u16 = (timer_f / f) as u16; let regs = Self::regs(); @@ -386,10 +402,14 @@ foreach_interrupt! { let f = frequency.0; let timer_f = Self::frequency().0; let psc_min = (timer_f / f) / (u16::MAX as u32 / 32); - let psc = HighResolutionControlPrescaler::compute_min(psc_min); + let psc = if Self::regs().isr().read().dllrdy() { + HighResolutionControlPrescaler::compute_min_high_res(psc_min) + } else { + HighResolutionControlPrescaler::compute_min_low_res(psc_min) + }; let psc_val: u32 = psc.into(); - let timer_f = timer_f / psc_val; + let timer_f = 32 * (timer_f / psc_val); let per: u16 = (timer_f / f) as u16; let regs = Self::regs(); @@ -410,7 +430,12 @@ foreach_interrupt! { // The dead-time base clock runs 4 times slower than the hrtim base clock // u9::MAX = 511 let psc_min = (psc_val * dead_time as u32) / (4 * 511); - let psc = HighResolutionControlPrescaler::compute_min(psc_min); + let psc = if Self::regs().isr().read().dllrdy() { + HighResolutionControlPrescaler::compute_min_high_res(psc_min) + } else { + HighResolutionControlPrescaler::compute_min_low_res(psc_min) + }; + let dt_psc_val: u32 = psc.into(); let dt_val = (dt_psc_val * dead_time as u32) / (4 * psc_val); diff --git a/examples/stm32f334/src/bin/button.rs b/examples/stm32f334/src/bin/button.rs new file mode 100644 index 000000000..599c0f27d --- /dev/null +++ b/examples/stm32f334/src/bin/button.rs @@ -0,0 +1,27 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello World!"); + + let p = embassy_stm32::init(Default::default()); + + let mut out1 = Output::new(p.PA8, Level::Low, Speed::High); + + out1.set_high(); + Timer::after(Duration::from_millis(500)).await; + out1.set_low(); + + Timer::after(Duration::from_millis(500)).await; + info!("end program"); + + cortex_m::asm::bkpt(); +} diff --git a/examples/stm32f334/src/bin/pwm.rs b/examples/stm32f334/src/bin/pwm.rs index 1b5d509e7..364119744 100644 --- a/examples/stm32f334/src/bin/pwm.rs +++ b/examples/stm32f334/src/bin/pwm.rs @@ -5,12 +5,20 @@ use defmt::*; use embassy_executor::Spawner; use embassy_stm32::pwm::advanced_pwm::*; -use embassy_stm32::time::khz; +use embassy_stm32::time::{khz, mhz}; +use embassy_stm32::Config; +use embassy_time::{Duration, Timer}; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] async fn main(_spawner: Spawner) { - let p = embassy_stm32::init(Default::default()); + let mut config: Config = Default::default(); + config.rcc.sysclk = Some(mhz(64)); + config.rcc.hclk = Some(mhz(64)); + config.rcc.pclk1 = Some(mhz(32)); + config.rcc.pclk2 = Some(mhz(64)); + + let p = embassy_stm32::init(config); info!("Hello World!"); let ch1 = PwmPin::new_cha(p.PA8); @@ -29,34 +37,35 @@ async fn main(_spawner: Spawner) { None, ); - let mut buck_converter = BridgeConverter::new(pwm.ch_a, khz(100)); + info!("pwm constructed"); - buck_converter.set_primary_duty(0); - buck_converter.set_secondary_duty(0); - buck_converter.set_dead_time(0); + let mut buck_converter = BridgeConverter::new(pwm.ch_a, khz(5)); - // note: if the pins are not passed into the advanced pwm struct, they will not be output - let mut boost_converter = BridgeConverter::new(pwm.ch_b, khz(100)); - - boost_converter.set_primary_duty(0); - boost_converter.set_secondary_duty(0); - - // let max = pwm.get_max_duty(); - // pwm.set_dead_time(max / 1024); + // embassy_stm32::pac::HRTIM1 + // .tim(0) + // .setr(0) + // .modify(|w| w.set_sst(Activeeffect::SETACTIVE)); // - // pwm.enable(Channel::Ch1); + // Timer::after(Duration::from_millis(500)).await; // - // info!("PWM initialized"); - // info!("PWM max duty {}", max); - // - // loop { - // pwm.set_duty(Channel::Ch1, 0); - // Timer::after(Duration::from_millis(300)).await; - // pwm.set_duty(Channel::Ch1, max / 4); - // Timer::after(Duration::from_millis(300)).await; - // pwm.set_duty(Channel::Ch1, max / 2); - // Timer::after(Duration::from_millis(300)).await; - // pwm.set_duty(Channel::Ch1, max - 1); - // Timer::after(Duration::from_millis(300)).await; - // } + // embassy_stm32::pac::HRTIM1 + // .tim(0) + // .rstr(0) + // .modify(|w| w.set_srt(Inactiveeffect::SETINACTIVE)); + + 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(Duration::from_millis(500)).await; + + info!("end program"); + + cortex_m::asm::bkpt(); }