embassy_stm32/hrtim/
mod.rs

1//! High Resolution Timer (HRTIM)
2
3mod traits;
4
5use core::marker::PhantomData;
6
7use embassy_hal_internal::Peri;
8pub use traits::Instance;
9
10use crate::gpio::{AfType, AnyPin, OutputType, Speed};
11use crate::rcc;
12use crate::time::Hertz;
13
14/// HRTIM burst controller instance.
15pub struct BurstController<T: Instance> {
16    phantom: PhantomData<T>,
17}
18
19/// HRTIM master instance.
20pub struct Master<T: Instance> {
21    phantom: PhantomData<T>,
22}
23
24/// HRTIM channel A instance.
25pub struct ChA<T: Instance> {
26    phantom: PhantomData<T>,
27}
28
29/// HRTIM channel B instance.
30pub struct ChB<T: Instance> {
31    phantom: PhantomData<T>,
32}
33
34/// HRTIM channel C instance.
35pub struct ChC<T: Instance> {
36    phantom: PhantomData<T>,
37}
38
39/// HRTIM channel D instance.
40pub struct ChD<T: Instance> {
41    phantom: PhantomData<T>,
42}
43
44/// HRTIM channel E instance.
45pub struct ChE<T: Instance> {
46    phantom: PhantomData<T>,
47}
48
49/// HRTIM channel F instance.
50#[cfg(hrtim_v2)]
51pub struct ChF<T: Instance> {
52    phantom: PhantomData<T>,
53}
54
55trait SealedAdvancedChannel<T: Instance> {
56    fn raw() -> usize;
57}
58
59/// Advanced channel instance trait.
60#[allow(private_bounds)]
61pub trait AdvancedChannel<T: Instance>: SealedAdvancedChannel<T> {}
62
63/// HRTIM PWM pin.
64pub struct PwmPin<'d, T, C> {
65    _pin: Peri<'d, AnyPin>,
66    phantom: PhantomData<(T, C)>,
67}
68
69/// HRTIM complementary PWM pin.
70pub struct ComplementaryPwmPin<'d, T, C> {
71    _pin: Peri<'d, AnyPin>,
72    phantom: PhantomData<(T, C)>,
73}
74
75macro_rules! advanced_channel_impl {
76    ($new_chx:ident, $channel:tt, $ch_num:expr, $pin_trait:ident, $complementary_pin_trait:ident) => {
77        impl<'d, T: Instance> PwmPin<'d, T, $channel<T>> {
78            #[doc = concat!("Create a new ", stringify!($channel), " PWM pin instance.")]
79            pub fn $new_chx(pin: Peri<'d, impl $pin_trait<T>>) -> Self {
80                critical_section::with(|_| {
81                    pin.set_low();
82                    pin.set_as_af(
83                        pin.af_num(),
84                        AfType::output(OutputType::PushPull, Speed::VeryHigh),
85                    );
86                });
87                PwmPin {
88                    _pin: pin.into(),
89                    phantom: PhantomData,
90                }
91            }
92        }
93
94        impl<'d, T: Instance> ComplementaryPwmPin<'d, T, $channel<T>> {
95            #[doc = concat!("Create a new ", stringify!($channel), " complementary PWM pin instance.")]
96            pub fn $new_chx(pin: Peri<'d, impl $complementary_pin_trait<T>>) -> Self {
97                critical_section::with(|_| {
98                    pin.set_low();
99                    pin.set_as_af(
100                        pin.af_num(),
101                        AfType::output(OutputType::PushPull, Speed::VeryHigh),
102                    );
103                });
104                ComplementaryPwmPin {
105                    _pin: pin.into(),
106                    phantom: PhantomData,
107                }
108            }
109        }
110
111        impl<T: Instance> SealedAdvancedChannel<T> for $channel<T> {
112            fn raw() -> usize {
113                $ch_num
114            }
115        }
116        impl<T: Instance> AdvancedChannel<T> for $channel<T> {}
117    };
118}
119
120advanced_channel_impl!(new_cha, ChA, 0, ChannelAPin, ChannelAComplementaryPin);
121advanced_channel_impl!(new_chb, ChB, 1, ChannelBPin, ChannelBComplementaryPin);
122advanced_channel_impl!(new_chc, ChC, 2, ChannelCPin, ChannelCComplementaryPin);
123advanced_channel_impl!(new_chd, ChD, 3, ChannelDPin, ChannelDComplementaryPin);
124advanced_channel_impl!(new_che, ChE, 4, ChannelEPin, ChannelEComplementaryPin);
125#[cfg(hrtim_v2)]
126advanced_channel_impl!(new_chf, ChF, 5, ChannelFPin, ChannelFComplementaryPin);
127
128/// Struct used to divide a high resolution timer into multiple channels
129pub struct AdvancedPwm<'d, T: Instance> {
130    _inner: Peri<'d, T>,
131    /// Master instance.
132    pub master: Master<T>,
133    /// Burst controller.
134    pub burst_controller: BurstController<T>,
135    /// Channel A.
136    pub ch_a: ChA<T>,
137    /// Channel B.
138    pub ch_b: ChB<T>,
139    /// Channel C.
140    pub ch_c: ChC<T>,
141    /// Channel D.
142    pub ch_d: ChD<T>,
143    /// Channel E.
144    pub ch_e: ChE<T>,
145    /// Channel F.
146    #[cfg(hrtim_v2)]
147    pub ch_f: ChF<T>,
148}
149
150impl<'d, T: Instance> AdvancedPwm<'d, T> {
151    /// Create a new HRTIM driver.
152    ///
153    /// This splits the HRTIM into its constituent parts, which you can then use individually.
154    pub fn new(
155        tim: Peri<'d, T>,
156        _cha: Option<PwmPin<'d, T, ChA<T>>>,
157        _chan: Option<ComplementaryPwmPin<'d, T, ChA<T>>>,
158        _chb: Option<PwmPin<'d, T, ChB<T>>>,
159        _chbn: Option<ComplementaryPwmPin<'d, T, ChB<T>>>,
160        _chc: Option<PwmPin<'d, T, ChC<T>>>,
161        _chcn: Option<ComplementaryPwmPin<'d, T, ChC<T>>>,
162        _chd: Option<PwmPin<'d, T, ChD<T>>>,
163        _chdn: Option<ComplementaryPwmPin<'d, T, ChD<T>>>,
164        _che: Option<PwmPin<'d, T, ChE<T>>>,
165        _chen: Option<ComplementaryPwmPin<'d, T, ChE<T>>>,
166        #[cfg(hrtim_v2)] _chf: Option<PwmPin<'d, T, ChF<T>>>,
167        #[cfg(hrtim_v2)] _chfn: Option<ComplementaryPwmPin<'d, T, ChF<T>>>,
168    ) -> Self {
169        Self::new_inner(tim)
170    }
171
172    fn new_inner(tim: Peri<'d, T>) -> Self {
173        rcc::enable_and_reset::<T>();
174
175        #[cfg(stm32f334)]
176        if crate::pac::RCC.cfgr3().read().hrtim1sw() == crate::pac::rcc::vals::Timsw::PLL1_P {
177            // Enable and and stabilize the DLL
178            T::regs().dllcr().modify(|w| {
179                w.set_cal(true);
180            });
181
182            trace!("hrtim: wait for dll calibration");
183            while !T::regs().isr().read().dllrdy() {}
184
185            trace!("hrtim: dll calibration complete");
186
187            // Enable periodic calibration
188            // Cal must be disabled before we can enable it
189            T::regs().dllcr().modify(|w| {
190                w.set_cal(false);
191            });
192
193            T::regs().dllcr().modify(|w| {
194                w.set_calen(true);
195                w.set_calrte(11);
196            });
197        }
198
199        Self {
200            _inner: tim,
201            master: Master { phantom: PhantomData },
202            burst_controller: BurstController { phantom: PhantomData },
203            ch_a: ChA { phantom: PhantomData },
204            ch_b: ChB { phantom: PhantomData },
205            ch_c: ChC { phantom: PhantomData },
206            ch_d: ChD { phantom: PhantomData },
207            ch_e: ChE { phantom: PhantomData },
208            #[cfg(hrtim_v2)]
209            ch_f: ChF { phantom: PhantomData },
210        }
211    }
212}
213
214/// Fixed-frequency bridge converter driver.
215///
216/// Our implementation of the bridge converter uses a single channel and three compare registers,
217/// allowing implementation of a synchronous buck or boost converter in continuous or discontinuous
218/// conduction mode.
219///
220/// It is important to remember that in synchronous topologies, energy can flow in reverse during
221/// light loading conditions, and that the low-side switch must be active for a short time to drive
222/// a bootstrapped high-side switch.
223pub struct BridgeConverter<T: Instance, C: AdvancedChannel<T>> {
224    timer: PhantomData<T>,
225    channel: PhantomData<C>,
226    dead_time: u16,
227    primary_duty: u16,
228    min_secondary_duty: u16,
229    max_secondary_duty: u16,
230}
231
232impl<T: Instance, C: AdvancedChannel<T>> BridgeConverter<T, C> {
233    /// Create a new HRTIM bridge converter driver.
234    pub fn new(_channel: C, frequency: Hertz) -> Self {
235        T::set_channel_frequency(C::raw(), frequency);
236
237        // Always enable preload
238        T::regs().tim(C::raw()).cr().modify(|w| {
239            w.set_preen(true);
240            w.set_repu(true);
241            w.set_cont(true);
242        });
243
244        // Enable timer outputs
245        T::regs().oenr().modify(|w| {
246            w.set_t1oen(C::raw(), true);
247            w.set_t2oen(C::raw(), true);
248        });
249
250        // The dead-time generation unit cannot be used because it forces the other output
251        // to be completely complementary to the first output, which restricts certain waveforms
252        // Therefore, software-implemented dead time must be used when setting the duty cycles
253
254        // Set output 1 to active on a period event
255        T::regs().tim(C::raw()).setr(0).modify(|w| w.set_per(true));
256
257        // Set output 1 to inactive on a compare 1 event
258        T::regs().tim(C::raw()).rstr(0).modify(|w| w.set_cmp(0, true));
259
260        // Set output 2 to active on a compare 2 event
261        T::regs().tim(C::raw()).setr(1).modify(|w| w.set_cmp(1, true));
262
263        // Set output 2 to inactive on a compare 3 event
264        T::regs().tim(C::raw()).rstr(1).modify(|w| w.set_cmp(2, true));
265
266        Self {
267            timer: PhantomData,
268            channel: PhantomData,
269            dead_time: 0,
270            primary_duty: 0,
271            min_secondary_duty: 0,
272            max_secondary_duty: 0,
273        }
274    }
275
276    /// Start HRTIM.
277    pub fn start(&mut self) {
278        T::regs().mcr().modify(|w| w.set_tcen(C::raw(), true));
279    }
280
281    /// Stop HRTIM.
282    pub fn stop(&mut self) {
283        T::regs().mcr().modify(|w| w.set_tcen(C::raw(), false));
284    }
285
286    /// Enable burst mode.
287    pub fn enable_burst_mode(&mut self) {
288        T::regs().tim(C::raw()).outr().modify(|w| {
289            // Enable Burst Mode
290            w.set_idlem(0, true);
291            w.set_idlem(1, true);
292
293            // Set output to active during the burst
294            w.set_idles(0, true);
295            w.set_idles(1, true);
296        })
297    }
298
299    /// Disable burst mode.
300    pub fn disable_burst_mode(&mut self) {
301        T::regs().tim(C::raw()).outr().modify(|w| {
302            // Disable Burst Mode
303            w.set_idlem(0, false);
304            w.set_idlem(1, false);
305        })
306    }
307
308    fn update_primary_duty_or_dead_time(&mut self) {
309        self.min_secondary_duty = self.primary_duty + self.dead_time;
310
311        T::regs().tim(C::raw()).cmp(0).modify(|w| w.set_cmp(self.primary_duty));
312        T::regs()
313            .tim(C::raw())
314            .cmp(1)
315            .modify(|w| w.set_cmp(self.min_secondary_duty));
316    }
317
318    /// Set the dead time as a proportion of the maximum compare value
319    pub fn set_dead_time(&mut self, dead_time: u16) {
320        self.dead_time = dead_time;
321        self.max_secondary_duty = self.get_max_compare_value() - dead_time;
322        self.update_primary_duty_or_dead_time();
323    }
324
325    /// Get the maximum compare value of a duty cycle
326    pub fn get_max_compare_value(&mut self) -> u16 {
327        T::regs().tim(C::raw()).per().read().per()
328    }
329
330    /// The primary duty is the period in which the primary switch is active
331    ///
332    /// In the case of a buck converter, this is the high-side switch
333    /// In the case of a boost converter, this is the low-side switch
334    pub fn set_primary_duty(&mut self, primary_duty: u16) {
335        self.primary_duty = primary_duty;
336        self.update_primary_duty_or_dead_time();
337    }
338
339    /// The secondary duty is the period in any switch is active
340    ///
341    /// If less than or equal to the primary duty, the secondary switch will be active for one tick
342    /// If a fully complementary output is desired, the secondary duty can be set to the max compare
343    pub fn set_secondary_duty(&mut self, secondary_duty: u16) {
344        let secondary_duty = if secondary_duty > self.max_secondary_duty {
345            self.max_secondary_duty
346        } else if secondary_duty <= self.min_secondary_duty {
347            self.min_secondary_duty + 1
348        } else {
349            secondary_duty
350        };
351
352        T::regs().tim(C::raw()).cmp(2).modify(|w| w.set_cmp(secondary_duty));
353    }
354}
355
356/// Variable-frequency resonant converter driver.
357///
358/// This implementation of a resonsant converter is appropriate for a half or full bridge,
359/// but does not include secondary rectification, which is appropriate for applications
360/// with a low-voltage on the secondary side.
361pub struct ResonantConverter<T: Instance, C: AdvancedChannel<T>> {
362    timer: PhantomData<T>,
363    channel: PhantomData<C>,
364    min_period: u16,
365    max_period: u16,
366}
367
368impl<T: Instance, C: AdvancedChannel<T>> ResonantConverter<T, C> {
369    /// Create a new variable-frequency resonant converter driver.
370    pub fn new(_channel: C, min_frequency: Hertz, max_frequency: Hertz) -> Self {
371        T::set_channel_frequency(C::raw(), min_frequency);
372
373        // Always enable preload
374        T::regs().tim(C::raw()).cr().modify(|w| {
375            w.set_preen(true);
376            w.set_repu(true);
377
378            w.set_cont(true);
379            w.set_half(true);
380        });
381
382        // Enable timer outputs
383        T::regs().oenr().modify(|w| {
384            w.set_t1oen(C::raw(), true);
385            w.set_t2oen(C::raw(), true);
386        });
387
388        // Dead-time generator can be used in this case because the primary fets
389        // of a resonant converter are always complementary
390        T::regs().tim(C::raw()).outr().modify(|w| w.set_dten(true));
391
392        let max_period = T::regs().tim(C::raw()).per().read().per();
393        let min_period = max_period * (min_frequency.0 / max_frequency.0) as u16;
394
395        Self {
396            timer: PhantomData,
397            channel: PhantomData,
398            min_period: min_period,
399            max_period: max_period,
400        }
401    }
402
403    /// Set the dead time as a proportion of the maximum compare value
404    pub fn set_dead_time(&mut self, value: u16) {
405        T::set_channel_dead_time(C::raw(), value);
406    }
407
408    /// Set the timer period.
409    pub fn set_period(&mut self, period: u16) {
410        assert!(period < self.max_period);
411        assert!(period > self.min_period);
412
413        T::regs().tim(C::raw()).per().modify(|w| w.set_per(period));
414    }
415
416    /// Get the minimum compare value of a duty cycle
417    pub fn get_min_period(&mut self) -> u16 {
418        self.min_period
419    }
420
421    /// Get the maximum compare value of a duty cycle
422    pub fn get_max_period(&mut self) -> u16 {
423        self.max_period
424    }
425}
426
427pin_trait!(ChannelAPin, Instance);
428pin_trait!(ChannelAComplementaryPin, Instance);
429pin_trait!(ChannelBPin, Instance);
430pin_trait!(ChannelBComplementaryPin, Instance);
431pin_trait!(ChannelCPin, Instance);
432pin_trait!(ChannelCComplementaryPin, Instance);
433pin_trait!(ChannelDPin, Instance);
434pin_trait!(ChannelDComplementaryPin, Instance);
435pin_trait!(ChannelEPin, Instance);
436pin_trait!(ChannelEComplementaryPin, Instance);
437#[cfg(hrtim_v2)]
438pin_trait!(ChannelFPin, Instance);
439#[cfg(hrtim_v2)]
440pin_trait!(ChannelFComplementaryPin, Instance);