stm32f4xx_hal/timer/
pwm_input.rs

1use super::{CPin, General, Instance, Timer, WithPwm};
2use crate::gpio::PushPull;
3use crate::pac;
4use core::convert::TryFrom;
5use core::ops::{Deref, DerefMut};
6use fugit::HertzU32 as Hertz;
7
8/// Represents a TIMer configured as a PWM input.
9/// This peripheral will emit an interrupt on CC2 events, which occurs at two times in this mode:
10/// 1. When a new cycle is started: the duty cycle will be `1.00`
11/// 2. When the period is captured. the duty cycle will be an observable value.
12///
13/// An example interrupt handler is provided:
14/// ```
15/// use stm32f4xx_hal::{pac::TIM8, pwm_input::PwmInput};
16///
17/// type Monitor = PwmInput<TIM8>;
18///
19/// fn tim8_cc2(monitor: &Monitor) {
20///     let duty_clocks = monitor.get_duty_cycle_clocks();
21///     let period_clocks = monitor.get_period_clocks();
22///     // check if this interrupt was caused by a capture at the wrong CC2,
23///     // peripheral limitation.
24///     if !monitor.is_valid_capture() {
25///         return;
26///     }
27///     let duty = monitor.get_duty_cycle();
28/// }
29/// ```
30pub struct PwmInput<TIM>
31where
32    TIM: Instance + WithPwm + CPin<0>,
33{
34    timer: Timer<TIM>,
35    _pins: TIM::Ch<PushPull>,
36}
37
38impl<TIM> Deref for PwmInput<TIM>
39where
40    TIM: Instance + WithPwm + CPin<0>,
41{
42    type Target = Timer<TIM>;
43    fn deref(&self) -> &Self::Target {
44        &self.timer
45    }
46}
47
48impl<TIM> DerefMut for PwmInput<TIM>
49where
50    TIM: Instance + WithPwm + CPin<0>,
51{
52    fn deref_mut(&mut self) -> &mut Self::Target {
53        &mut self.timer
54    }
55}
56
57impl<TIM> PwmInput<TIM>
58where
59    TIM: Instance + WithPwm + CPin<0>,
60{
61    pub fn release(mut self) -> Timer<TIM> {
62        self.tim.cr1_reset();
63        self.timer
64    }
65}
66
67#[cfg(not(feature = "gpio-f410"))]
68macro_rules! hal {
69    ($TIM:ty) => {
70        impl Timer<$TIM> {
71            /// Configures this timer for PWM input. Accepts the `best_guess` frequency of the signal
72            /// Note: this should be as close as possible to the frequency of the PWM waveform for best
73            /// accuracy.
74            ///
75            /// This device will emit an interrupt on CC1, which occurs at two times in this mode:
76            /// 1. When a new cycle is started: the duty cycle will be `1.00`
77            /// 2. When the period is captured. the duty cycle will be an observable value.
78            ///
79            /// See the pwm input example for an suitable interrupt handler.
80            pub fn pwm_input(
81                mut self,
82                best_guess: Hertz,
83                pins: impl Into<<$TIM as CPin<0>>::Ch<PushPull>>,
84            ) -> PwmInput<$TIM> {
85                let pins = pins.into();
86
87                /*
88                Borrowed from PWM implementation.
89                Sets the TIMer's prescaler such that the TIMer that it ticks at about the best-guess
90                 frequency.
91                */
92                let ticks = self.clk.raw() / best_guess.raw();
93                let psc = u16::try_from((ticks - 1) / (1 << 16)).unwrap();
94                self.tim.set_prescaler(psc);
95
96                // Seemingly this needs to be written to
97                // self.tim.arr().write(|w| w.arr().bits(u16::MAX));
98
99                /*
100                For example, one can measure the period (in TIMx_CCR1 register) and the duty cycle (in
101                TIMx_CCR2 register) of the PWM applied on TI1 using the following procedure (depending
102                on CK_INT frequency and prescaler value):
103
104                from RM0390 16.3.7
105                 */
106
107                // Select the active input for TIMx_CCR1: write the CC1S bits to 01 in the TIMx_CCMR1
108                // register (TI1 selected).
109                self.tim
110                    .ccmr1_input()
111                    .modify(|_, w| unsafe { w.cc1s().bits(0b01) });
112
113                // Select the active polarity for TI1FP1 (used both for capture in TIMx_CCR1 and counter
114                // clear): write the CC1P and CC1NP bits to ‘0’ (active on rising edge).
115
116                self.tim
117                    .ccer()
118                    .modify(|_, w| w.cc1p().clear_bit().cc2p().clear_bit());
119
120                // disable filters and disable the input capture prescalers.
121                self.tim.ccmr1_input().modify(|_, w| unsafe {
122                    w.ic1f().bits(0).ic2f().bits(0);
123                    w.ic1psc().bits(0).ic2psc().bits(0)
124                });
125
126                // Select the active input for TIMx_CCR2: write the CC2S bits to 10 in the TIMx_CCMR1
127                // register (TI1 selected)
128                self.tim
129                    .ccmr1_input()
130                    .modify(|_, w| unsafe { w.cc2s().bits(0b10) });
131
132                // Select the active polarity for TI1FP2 (used for capture in TIMx_CCR2): write the CC2P
133                // and CC2NP bits to ‘1’ (active on falling edge).
134                self.tim
135                    .ccer()
136                    .modify(|_, w| w.cc2p().set_bit().cc2np().set_bit());
137
138                // Select the valid trigger input: write the TS bits to 101 in the TIMx_SMCR register
139                // (TI1FP1 selected).
140                self.tim.smcr().modify(|_, w| unsafe { w.ts().bits(0b101) });
141
142                // Configure the slave mode controller in reset mode: write the SMS bits to 100 in the
143                // TIMx_SMCR register.
144                self.tim
145                    .smcr()
146                    .modify(|_, w| unsafe { w.sms().bits(0b100) });
147
148                // Enable the captures: write the CC1E and CC2E bits to ‘1’ in the TIMx_CCER register.
149                self.tim
150                    .ccer()
151                    .modify(|_, w| w.cc1e().set_bit().cc2e().set_bit());
152
153                // enable interrupts.
154                self.tim.dier().modify(|_, w| w.cc2ie().set_bit());
155                // enable the counter.
156                self.tim.enable_counter(true);
157
158                PwmInput {
159                    timer: self,
160                    _pins: pins,
161                }
162            }
163        }
164
165        impl PwmInput<$TIM> {
166            /// Period of PWM signal in terms of clock cycles
167            pub fn get_period_clocks(&self) -> <$TIM as General>::Width {
168                self.tim.ccr1().read().ccr().bits()
169            }
170            /// Duty cycle in terms of clock cycles
171            pub fn get_duty_cycle_clocks(&self) -> <$TIM as General>::Width {
172                self.tim.ccr2().read().ccr().bits()
173            }
174            /// Observed duty cycle as a float in range [0.00, 1.00]
175            pub fn get_duty_cycle(&self) -> f32 {
176                let period_clocks = self.get_period_clocks();
177                if period_clocks == 0 {
178                    return 0.;
179                };
180                (self.get_duty_cycle_clocks() as f32 / period_clocks as f32) * 100f32
181            }
182            /// Returns whether the timer's duty cycle is a valid observation
183            /// (Limitation of how the captures work is extra CC2 interrupts are generated when the
184            /// PWM cycle enters a new period).
185            pub fn is_valid_capture(&self) -> bool {
186                self.get_duty_cycle_clocks() != self.get_period_clocks()
187            }
188        }
189    };
190}
191
192#[cfg(feature = "tim1")]
193hal! { pac::TIM1 }
194#[cfg(feature = "tim2")]
195hal! { pac::TIM2 }
196#[cfg(feature = "tim3")]
197hal! { pac::TIM3 }
198#[cfg(feature = "tim4")]
199hal! { pac::TIM4 }
200#[cfg(feature = "tim5")]
201hal! { pac::TIM5 }
202#[cfg(feature = "tim8")]
203hal! { pac::TIM8 }
204#[cfg(feature = "tim9")]
205hal! { pac::TIM9 }
206#[cfg(feature = "tim12")]
207hal! { pac::TIM12 }