py32_hal/timer/
complementary_pwm.rs

1//! PWM driver with complementary output support.
2
3// The following code is modified from embassy-stm32
4// https://github.com/embassy-rs/embassy/tree/main/embassy-stm32
5// Special thanks to the Embassy Project and its contributors for their work!
6
7use core::marker::PhantomData;
8
9use crate::pac::timer::vals::Ckd;
10use embassy_hal_internal::{into_ref, PeripheralRef};
11
12use super::low_level::{CountingMode, OutputPolarity, Timer};
13use super::simple_pwm::{Ch1, Ch2, Ch3, Ch4, PwmPin};
14use super::{
15    AdvancedInstance4Channel, Channel, Channel1ComplementaryPin, Channel2ComplementaryPin,
16    Channel3ComplementaryPin, Channel4ComplementaryPin,
17};
18use crate::gpio::{AnyPin, OutputType};
19use crate::time::Hertz;
20use crate::timer::low_level::OutputCompareMode;
21use crate::Peripheral;
22
23/// Complementary PWM pin wrapper.
24///
25/// This wraps a pin to make it usable with PWM.
26pub struct ComplementaryPwmPin<'d, T, C> {
27    _pin: PeripheralRef<'d, AnyPin>,
28    phantom: PhantomData<(T, C)>,
29}
30
31macro_rules! complementary_channel_impl {
32    ($new_chx:ident, $channel:ident, $pin_trait:ident) => {
33        impl<'d, T: AdvancedInstance4Channel> ComplementaryPwmPin<'d, T, $channel> {
34            #[doc = concat!("Create a new ", stringify!($channel), " complementary PWM pin instance.")]
35            pub fn $new_chx(pin: impl Peripheral<P = impl $pin_trait<T>> + 'd, output_type: OutputType) -> Self {
36                into_ref!(pin);
37                critical_section::with(|_| {
38                    pin.set_low();
39                    pin.set_as_af(
40                        pin.af_num(),
41                        crate::gpio::AfType::output(output_type, crate::gpio::Speed::VeryHigh),
42                    );
43                });
44                ComplementaryPwmPin {
45                    _pin: pin.map_into(),
46                    phantom: PhantomData,
47                }
48            }
49        }
50    };
51}
52
53complementary_channel_impl!(new_ch1, Ch1, Channel1ComplementaryPin);
54complementary_channel_impl!(new_ch2, Ch2, Channel2ComplementaryPin);
55complementary_channel_impl!(new_ch3, Ch3, Channel3ComplementaryPin);
56complementary_channel_impl!(new_ch4, Ch4, Channel4ComplementaryPin);
57
58/// PWM driver with support for standard and complementary outputs.
59pub struct ComplementaryPwm<'d, T: AdvancedInstance4Channel> {
60    inner: Timer<'d, T>,
61}
62
63impl<'d, T: AdvancedInstance4Channel> ComplementaryPwm<'d, T> {
64    /// Create a new complementary PWM driver.
65    #[allow(clippy::too_many_arguments)]
66    pub fn new(
67        tim: impl Peripheral<P = T> + 'd,
68        _ch1: Option<PwmPin<'d, T, Ch1>>,
69        _ch1n: Option<ComplementaryPwmPin<'d, T, Ch1>>,
70        _ch2: Option<PwmPin<'d, T, Ch2>>,
71        _ch2n: Option<ComplementaryPwmPin<'d, T, Ch2>>,
72        _ch3: Option<PwmPin<'d, T, Ch3>>,
73        _ch3n: Option<ComplementaryPwmPin<'d, T, Ch3>>,
74        _ch4: Option<PwmPin<'d, T, Ch4>>,
75        _ch4n: Option<ComplementaryPwmPin<'d, T, Ch4>>,
76        freq: Hertz,
77        counting_mode: CountingMode,
78    ) -> Self {
79        Self::new_inner(tim, freq, counting_mode)
80    }
81
82    fn new_inner(
83        tim: impl Peripheral<P = T> + 'd,
84        freq: Hertz,
85        counting_mode: CountingMode,
86    ) -> Self {
87        let mut this = Self {
88            inner: Timer::new(tim),
89        };
90
91        this.inner.set_counting_mode(counting_mode);
92        this.set_frequency(freq);
93        this.inner.start();
94
95        this.inner.enable_outputs();
96
97        [Channel::Ch1, Channel::Ch2, Channel::Ch3, Channel::Ch4]
98            .iter()
99            .for_each(|&channel| {
100                this.inner
101                    .set_output_compare_mode(channel, OutputCompareMode::PwmMode1);
102                this.inner.set_output_compare_preload(channel, true);
103            });
104
105        this
106    }
107
108    /// Enable the given channel.
109    pub fn enable(&mut self, channel: Channel) {
110        self.inner.enable_channel(channel, true);
111        self.inner.enable_complementary_channel(channel, true);
112    }
113
114    /// Disable the given channel.
115    pub fn disable(&mut self, channel: Channel) {
116        self.inner.enable_complementary_channel(channel, false);
117        self.inner.enable_channel(channel, false);
118    }
119
120    /// Set PWM frequency.
121    ///
122    /// Note: when you call this, the max duty value changes, so you will have to
123    /// call `set_duty` on all channels with the duty calculated based on the new max duty.
124    pub fn set_frequency(&mut self, freq: Hertz) {
125        let multiplier = if self.inner.get_counting_mode().is_center_aligned() {
126            2u8
127        } else {
128            1u8
129        };
130        self.inner.set_frequency(freq * multiplier);
131    }
132
133    /// Get max duty value.
134    ///
135    /// This value depends on the configured frequency and the timer's clock rate from RCC.
136    pub fn get_max_duty(&self) -> u16 {
137        self.inner.get_max_compare_value() as u16 + 1
138    }
139
140    /// Set the duty for a given channel.
141    ///
142    /// The value ranges from 0 for 0% duty, to [`get_max_duty`](Self::get_max_duty) for 100% duty, both included.
143    pub fn set_duty(&mut self, channel: Channel, duty: u16) {
144        assert!(duty <= self.get_max_duty());
145        self.inner.set_compare_value(channel, duty as _)
146    }
147
148    /// Set the output polarity for a given channel.
149    pub fn set_polarity(&mut self, channel: Channel, polarity: OutputPolarity) {
150        self.inner.set_output_polarity(channel, polarity);
151        self.inner
152            .set_complementary_output_polarity(channel, polarity);
153    }
154
155    /// Set the dead time as a proportion of max_duty
156    pub fn set_dead_time(&mut self, value: u16) {
157        let (ckd, value) = compute_dead_time_value(value);
158
159        self.inner.set_dead_time_clock_division(ckd);
160        self.inner.set_dead_time_value(value);
161    }
162}
163
164impl<'d, T: AdvancedInstance4Channel> embedded_hal_02::Pwm for ComplementaryPwm<'d, T> {
165    type Channel = Channel;
166    type Time = Hertz;
167    type Duty = u16;
168
169    fn disable(&mut self, channel: Self::Channel) {
170        self.inner.enable_complementary_channel(channel, false);
171        self.inner.enable_channel(channel, false);
172    }
173
174    fn enable(&mut self, channel: Self::Channel) {
175        self.inner.enable_channel(channel, true);
176        self.inner.enable_complementary_channel(channel, true);
177    }
178
179    fn get_period(&self) -> Self::Time {
180        self.inner.get_frequency()
181    }
182
183    fn get_duty(&self, channel: Self::Channel) -> Self::Duty {
184        self.inner.get_compare_value(channel) as u16
185    }
186
187    fn get_max_duty(&self) -> Self::Duty {
188        self.inner.get_max_compare_value() as u16 + 1
189    }
190
191    fn set_duty(&mut self, channel: Self::Channel, duty: Self::Duty) {
192        assert!(duty <= self.get_max_duty());
193        self.inner.set_compare_value(channel, duty as u32)
194    }
195
196    fn set_period<P>(&mut self, period: P)
197    where
198        P: Into<Self::Time>,
199    {
200        self.inner.set_frequency(period.into());
201    }
202}
203
204fn compute_dead_time_value(value: u16) -> (Ckd, u8) {
205    /*
206        Dead-time = T_clk * T_dts * T_dtg
207
208        T_dts:
209        This bit-field indicates the division ratio between the timer clock (CK_INT) frequency and the
210        dead-time and sampling clock (tDTS)used by the dead-time generators and the digital filters
211        (ETR, TIx),
212        00: tDTS=tCK_INT
213        01: tDTS=2*tCK_INT
214        10: tDTS=4*tCK_INT
215
216        T_dtg:
217        This bit-field defines the duration of the dead-time inserted between the complementary
218        outputs. DT correspond to this duration.
219        DTG[7:5]=0xx => DT=DTG[7:0]x tdtg with tdtg=tDTS.
220        DTG[7:5]=10x => DT=(64+DTG[5:0])xtdtg with Tdtg=2xtDTS.
221        DTG[7:5]=110 => DT=(32+DTG[4:0])xtdtg with Tdtg=8xtDTS.
222        DTG[7:5]=111 => DT=(32+DTG[4:0])xtdtg with Tdtg=16xtDTS.
223        Example if TDTS=125ns (8MHz), dead-time possible values are:
224        0 to 15875 ns by 125 ns steps,
225        16 us to 31750 ns by 250 ns steps,
226        32 us to 63us by 1 us steps,
227        64 us to 126 us by 2 us steps
228    */
229
230    let mut error = u16::MAX;
231    let mut ckd = Ckd::DIV1;
232    let mut bits = 0u8;
233
234    for this_ckd in [Ckd::DIV1, Ckd::DIV2, Ckd::DIV4] {
235        let outdiv = match this_ckd {
236            Ckd::DIV1 => 1,
237            Ckd::DIV2 => 2,
238            Ckd::DIV4 => 4,
239            _ => unreachable!(),
240        };
241
242        // 127
243        // 128
244        // ..
245        // 254
246        // 256
247        // ..
248        // 504
249        // 512
250        // ..
251        // 1008
252
253        let target = value / outdiv;
254        let (these_bits, result) = if target < 128 {
255            (target as u8, target)
256        } else if target < 255 {
257            (64 + (target / 2) as u8, (target - target % 2))
258        } else if target < 508 {
259            (32 + (target / 8) as u8, (target - target % 8))
260        } else if target < 1008 {
261            (32 + (target / 16) as u8, (target - target % 16))
262        } else {
263            (u8::MAX, 1008)
264        };
265
266        let this_error = value.abs_diff(result * outdiv);
267        if error > this_error {
268            ckd = this_ckd;
269            bits = these_bits;
270            error = this_error;
271        }
272
273        if error == 0 {
274            break;
275        }
276    }
277
278    (ckd, bits)
279}
280
281#[cfg(test)]
282mod tests {
283    use super::{compute_dead_time_value, Ckd};
284
285    #[test]
286    fn test_compute_dead_time_value() {
287        struct TestRun {
288            value: u16,
289            ckd: Ckd,
290            bits: u8,
291        }
292
293        let fn_results = [
294            TestRun {
295                value: 1,
296                ckd: Ckd::DIV1,
297                bits: 1,
298            },
299            TestRun {
300                value: 125,
301                ckd: Ckd::DIV1,
302                bits: 125,
303            },
304            TestRun {
305                value: 245,
306                ckd: Ckd::DIV1,
307                bits: 64 + 245 / 2,
308            },
309            TestRun {
310                value: 255,
311                ckd: Ckd::DIV2,
312                bits: 127,
313            },
314            TestRun {
315                value: 400,
316                ckd: Ckd::DIV1,
317                bits: 32 + (400u16 / 8) as u8,
318            },
319            TestRun {
320                value: 600,
321                ckd: Ckd::DIV4,
322                bits: 64 + (600u16 / 8) as u8,
323            },
324        ];
325
326        for test_run in fn_results {
327            let (ckd, bits) = compute_dead_time_value(test_run.value);
328
329            assert_eq!(ckd.to_bits(), test_run.ckd.to_bits());
330            assert_eq!(bits, test_run.bits);
331        }
332    }
333}