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}
322