hrtim: fix example and auto adjust psc.

This commit is contained in:
xoviat 2023-07-01 21:47:44 -05:00
parent 8141d53d94
commit aceba1c03f
4 changed files with 160 additions and 56 deletions

View file

@ -144,6 +144,18 @@ impl<'d, T: HighResolutionCaptureCompare16bitInstance> AdvancedPwm<'d, T> {
T::enable();
<T as crate::rcc::sealed::RccPeripheral>::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<T: HighResolutionCaptureCompare16bitInstance> BurstController<T> {
/// 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<T: HighResolutionCaptureCompare16bitInstance, C: AdvancedChannel<T>> {
phantom: PhantomData<T>,
pub ch: C,
timer: PhantomData<T>,
channel: PhantomData<C>,
dead_time: u16,
primary_duty: u16,
}
impl<T: HighResolutionCaptureCompare16bitInstance, C: AdvancedChannel<T>> BridgeConverter<T, C> {
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<T: HighResolutionCaptureCompare16bitInstance, C: AdvancedChannel<T>> 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<T: HighResolutionCaptureCompare16bitInstance, C: AdvancedChannel<T>> 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<T: HighResolutionCaptureCompare16bitInstance, C: AdvancedChannel<T>> 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<T: HighResolutionCaptureCompare16bitInstance, C: AdvancedChannel<T>> 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<T: HighResolutionCaptureCompare16bitInstance, C: AdvancedChannel<T>> 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<T: HighResolutionCaptureCompare16bitInstance, C: AdvancedChannel<T>> Bridge
/// but does not include secondary rectification, which is appropriate for applications
/// with a low-voltage on the secondary side.
pub struct ResonantConverter<T: HighResolutionCaptureCompare16bitInstance, C: AdvancedChannel<T>> {
phantom: PhantomData<T>,
timer: PhantomData<T>,
channel: PhantomData<C>,
min_period: u16,
max_period: u16,
pub ch: C,
}
impl<T: HighResolutionCaptureCompare16bitInstance, C: AdvancedChannel<T>> ResonantConverter<T, C> {
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<T: HighResolutionCaptureCompare16bitInstance, C: AdvancedChannel<T>> 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,
}
}

View file

@ -123,7 +123,7 @@ impl From<u8> 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| <HighResolutionControlPrescaler as Into<u32>>::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);

View file

@ -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();
}

View file

@ -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();
}