embassy_stm32/timer/
complementary_pwm.rs

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