|
| 1 | +use crate::{ |
| 2 | + time::Hertz, |
| 3 | + timer::{PinC1, Timer}, |
| 4 | +}; |
| 5 | +use cast::u16; |
| 6 | + |
| 7 | +pub trait Pins<TIM> {} |
| 8 | + |
| 9 | +// implement the `Pins` trait wherever PC1 implements PinC1 |
| 10 | +impl<TIM, PC1> Pins<TIM> for PC1 where PC1: PinC1<TIM> {} |
| 11 | + |
| 12 | +/// Represents a TIMer configured as a PWM input. |
| 13 | +/// This peripheral will emit an interrupt on CC2 events, which occurs at two times in this mode: |
| 14 | +/// 1. When a new cycle is started: the duty cycle will be `1.00` |
| 15 | +/// 2. When the period is captured. the duty cycle will be an observable value. |
| 16 | +/// An example interrupt handler is provided: |
| 17 | +/// ``` |
| 18 | +/// use stm32f4xx_hal::stm32::TIM8; |
| 19 | +/// use stm32f4xx_hal::timer::Timer; |
| 20 | +/// use stm32f4xx_hal::pwm_input::PwmInput; |
| 21 | +/// use stm32f4xx_hal::gpio::gpioc::PC6; |
| 22 | +/// use stm32f4xx_hal::gpio::Alternate; |
| 23 | +/// |
| 24 | +/// type Monitor = PwmInput<stm32f4xx_hal::stm32::TIM8, PC6<Alternate<3>>>; |
| 25 | +/// |
| 26 | +/// fn tim8_cc2(monitor: &Monitor) { |
| 27 | +/// let duty_clocks = monitor.get_duty_cycle_clocks(); |
| 28 | +/// let period_clocks = monitor.get_period_clocks(); |
| 29 | +/// // check if this interrupt was caused by a capture at the wrong CC2, |
| 30 | +/// // peripheral limitation. |
| 31 | +/// if !monitor.is_valid_capture(){ |
| 32 | +/// return; |
| 33 | +/// } |
| 34 | +/// let duty = monitor.get_duty_cycle(); |
| 35 | +/// } |
| 36 | +/// ``` |
| 37 | +pub struct PwmInput<TIM, PINS: Pins<TIM>> { |
| 38 | + tim: TIM, |
| 39 | + clk: Hertz, |
| 40 | + pins: PINS, |
| 41 | +} |
| 42 | + |
| 43 | +macro_rules! hal { |
| 44 | + ($($TIM:ident: ($bits:ident),)+) => { |
| 45 | + $( |
| 46 | + // Drag the associated TIM object into scope. |
| 47 | + // Note: its drawn in via the macro to avoid duplicating the feature gate this macro is |
| 48 | + // expecting to be guarded by. |
| 49 | + use crate::stm32::$TIM; |
| 50 | + |
| 51 | + impl Timer<$TIM> { |
| 52 | + /// Configures this timer for PWM input. Accepts the `best_guess` frequency of the signal |
| 53 | + /// Note: this should be as close as possible to the frequency of the PWM waveform for best |
| 54 | + /// accuracy. |
| 55 | + /// |
| 56 | + /// This device will emit an interrupt on CC1, which occurs at two times in this mode: |
| 57 | + /// 1. When a new cycle is started: the duty cycle will be `1.00` |
| 58 | + /// 2. When the period is captured. the duty cycle will be an observable value. |
| 59 | + /// See the pwm input example for an suitable interrupt handler. |
| 60 | + #[allow(unused_unsafe)] //for some chips the operations are considered safe. |
| 61 | + pub fn pwm_input<T, PINS>(self, best_guess: T, pins: PINS) -> PwmInput<$TIM, PINS> |
| 62 | + where |
| 63 | + T: Into<Hertz>, |
| 64 | + PINS: Pins<$TIM>, |
| 65 | + { |
| 66 | + /* |
| 67 | + Borrowed from PWM implementation. |
| 68 | + Sets the TIMer's prescaler such that the TIMer that it ticks at about the best-guess |
| 69 | + frequency. |
| 70 | + */ |
| 71 | + let ticks = self.clk.0 / best_guess.into().0; |
| 72 | + let psc = u16((ticks - 1) / (1 << 16)).unwrap(); |
| 73 | + self.tim.psc.write(|w| w.psc().bits(psc)); |
| 74 | + |
| 75 | + // Seemingly this needs to be written to |
| 76 | + // self.tim.arr.write(|w| w.arr().bits(u16::MAX)); |
| 77 | + |
| 78 | + /* |
| 79 | + For example, one can measure the period (in TIMx_CCR1 register) and the duty cycle (in |
| 80 | + TIMx_CCR2 register) of the PWM applied on TI1 using the following procedure (depending |
| 81 | + on CK_INT frequency and prescaler value): |
| 82 | +
|
| 83 | + from RM0390 16.3.7 |
| 84 | + */ |
| 85 | + |
| 86 | + // Select the active input for TIMx_CCR1: write the CC1S bits to 01 in the TIMx_CCMR1 |
| 87 | + // register (TI1 selected). |
| 88 | + self.tim |
| 89 | + .ccmr1_input() |
| 90 | + .modify(|_, w| unsafe { w.cc1s().bits(0b01) }); |
| 91 | + |
| 92 | + // Select the active polarity for TI1FP1 (used both for capture in TIMx_CCR1 and counter |
| 93 | + // clear): write the CC1P and CC1NP bits to ‘0’ (active on rising edge). |
| 94 | + |
| 95 | + self.tim |
| 96 | + .ccer |
| 97 | + .modify(|_, w| w.cc1p().clear_bit().cc2p().clear_bit()); |
| 98 | + |
| 99 | + // disable filters and disable the input capture prescalers. |
| 100 | + self.tim.ccmr1_input().modify(|_, w| unsafe { |
| 101 | + w.ic1f() |
| 102 | + .bits(0) |
| 103 | + .ic2f() |
| 104 | + .bits(0) |
| 105 | + .ic1psc() |
| 106 | + .bits(0) |
| 107 | + .ic2psc() |
| 108 | + .bits(0) |
| 109 | + }); |
| 110 | + |
| 111 | + // Select the active input for TIMx_CCR2: write the CC2S bits to 10 in the TIMx_CCMR1 |
| 112 | + // register (TI1 selected) |
| 113 | + self.tim |
| 114 | + .ccmr1_input() |
| 115 | + .modify(|_, w| unsafe { w.cc2s().bits(0b10) }); |
| 116 | + |
| 117 | + // Select the active polarity for TI1FP2 (used for capture in TIMx_CCR2): write the CC2P |
| 118 | + // and CC2NP bits to ‘1’ (active on falling edge). |
| 119 | + self.tim |
| 120 | + .ccer |
| 121 | + .modify(|_, w| w.cc2p().set_bit().cc2np().set_bit()); |
| 122 | + |
| 123 | + // Select the valid trigger input: write the TS bits to 101 in the TIMx_SMCR register |
| 124 | + // (TI1FP1 selected). |
| 125 | + self.tim.smcr.modify(|_, w| unsafe { w.ts().bits(0b101) }); |
| 126 | + |
| 127 | + // Configure the slave mode controller in reset mode: write the SMS bits to 100 in the |
| 128 | + // TIMx_SMCR register. |
| 129 | + self.tim.smcr.modify(|_, w| unsafe { w.sms().bits(0b100) }); |
| 130 | + |
| 131 | + // Enable the captures: write the CC1E and CC2E bits to ‘1’ in the TIMx_CCER register. |
| 132 | + self.tim |
| 133 | + .ccer |
| 134 | + .modify(|_, w| w.cc1e().set_bit().cc2e().set_bit()); |
| 135 | + |
| 136 | + // enable interrupts. |
| 137 | + self.tim.dier.modify(|_, w| w.cc2ie().set_bit()); |
| 138 | + // enable the counter. |
| 139 | + self.tim.cr1.modify(|_, w| w.cen().enabled()); |
| 140 | + |
| 141 | + let Self { tim, clk } = self; |
| 142 | + |
| 143 | + PwmInput { tim, clk, pins } |
| 144 | + } |
| 145 | + } |
| 146 | + |
| 147 | + impl<PINS> PwmInput<$TIM, PINS> |
| 148 | + where |
| 149 | + PINS: Pins<$TIM>, |
| 150 | + { |
| 151 | + pub fn reclaim(self) -> (Timer<$TIM>, PINS) { |
| 152 | + // disable timer |
| 153 | + self.tim.cr1.modify(|_, w| w.cen().disabled()); |
| 154 | + // decompose elements |
| 155 | + let Self { tim, clk, pins } = self; |
| 156 | + // and return them to the caller |
| 157 | + (Timer { tim, clk }, pins) |
| 158 | + } |
| 159 | + /// Period of PWM signal in terms of clock cycles |
| 160 | + pub fn get_period_clocks(&self) -> $bits { |
| 161 | + self.tim.ccr1.read().ccr().bits() |
| 162 | + } |
| 163 | + /// Duty cycle in terms of clock cycles |
| 164 | + pub fn get_duty_cycle_clocks(&self) -> $bits { |
| 165 | + self.tim.ccr2.read().ccr().bits() |
| 166 | + } |
| 167 | + /// Observed duty cycle as a float in range [0.00, 1.00] |
| 168 | + pub fn get_duty_cycle(&self) -> f32 { |
| 169 | + let period_clocks = self.get_period_clocks(); |
| 170 | + if period_clocks == 0 { |
| 171 | + return 0.0; |
| 172 | + }; |
| 173 | + return (self.get_duty_cycle_clocks() as f32 / period_clocks as f32) * 100f32; |
| 174 | + } |
| 175 | + /// Returns whether the timer's duty cycle is a valid observation |
| 176 | + /// (Limitation of how the captures work is extra CC2 interrupts are generated when the |
| 177 | + /// PWM cycle enters a new period). |
| 178 | + pub fn is_valid_capture(&self) -> bool { |
| 179 | + self.get_duty_cycle_clocks() != self.get_period_clocks() |
| 180 | + } |
| 181 | + } |
| 182 | + )+ |
| 183 | +}} |
| 184 | + |
| 185 | +#[cfg(any(feature = "stm32f411",))] |
| 186 | +/* red group */ |
| 187 | +hal! { |
| 188 | + TIM4: (u16), |
| 189 | + TIM3: (u16), |
| 190 | + TIM2: (u32), |
| 191 | +} |
| 192 | + |
| 193 | +/* orange group */ |
| 194 | +#[cfg(any( |
| 195 | + feature = "stm32f401", |
| 196 | + feature = "stm32f405", |
| 197 | + feature = "stm32f407", |
| 198 | + feature = "stm32f412", |
| 199 | + feature = "stm32f413", |
| 200 | + feature = "stm32f415", |
| 201 | + feature = "stm32f417", |
| 202 | + feature = "stm32f423", |
| 203 | + feature = "stm32f427", |
| 204 | + feature = "stm32f429", |
| 205 | + feature = "stm32f437", |
| 206 | + feature = "stm32f439", |
| 207 | + feature = "stm32f446", |
| 208 | + feature = "stm32f469", |
| 209 | + feature = "stm32f479", |
| 210 | +))] |
| 211 | +hal! { |
| 212 | + TIM8: (u16), |
| 213 | + TIM2: (u32), |
| 214 | + TIM3: (u16), |
| 215 | + TIM4: (u16), |
| 216 | +} |
| 217 | +/* green group */ |
| 218 | +#[cfg(any( |
| 219 | + feature = "stm32f405", |
| 220 | + feature = "stm32f407", |
| 221 | + feature = "stm32f412", |
| 222 | + feature = "stm32f413", |
| 223 | + feature = "stm32f415", |
| 224 | + feature = "stm32f417", |
| 225 | + feature = "stm32f423", |
| 226 | + feature = "stm32f427", |
| 227 | + feature = "stm32f429", |
| 228 | + feature = "stm32f437", |
| 229 | + feature = "stm32f439", |
| 230 | + feature = "stm32f446", |
| 231 | + feature = "stm32f469", |
| 232 | + feature = "stm32f479", |
| 233 | +))] |
| 234 | +hal! { |
| 235 | + TIM12: (u16), |
| 236 | +} |
| 237 | + |
| 238 | +/* every chip across the series have these timers with support for this feature. |
| 239 | +.. except for the 410 which, while the timers support this feature, has a different configuration |
| 240 | + than the rest of the series. |
| 241 | +*/ |
| 242 | +/* yellow group */ |
| 243 | +#[cfg(not(feature = "stm32f410"))] |
| 244 | +hal! { |
| 245 | + TIM1: (u16), |
| 246 | + TIM5: (u32), |
| 247 | + TIM9: (u16), |
| 248 | +} |
0 commit comments