Skip to content

Commit ae32d5b

Browse files
committed
Implement PWM input
1 parent 01e8155 commit ae32d5b

File tree

5 files changed

+299
-1
lines changed

5 files changed

+299
-1
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
3232
- Derive `Eq`, `PartialEq`, `Copy` and `Clone` for error types
3333
- Added open-drain pin mode support for PWM output [#313]
3434
- Added missing error flags for dma streams [#318]
35+
- Added PWM input capability to all compatable timers [#271]
3536

3637
[#265]: https://github.com/stm32-rs/stm32f4xx-hal/pull/265
38+
[#271] https://github.com/stm32-rs/stm32f4xx-hal/pull/271
3739
[#297]: https://github.com/stm32-rs/stm32f4xx-hal/pull/297
3840
[#302]: https://github.com/stm32-rs/stm32f4xx-hal/pull/302
3941
[#309]: https://github.com/stm32-rs/stm32f4xx-hal/pull/309
@@ -60,7 +62,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
6062
[#299]: https://github.com/stm32-rs/stm32f4xx-hal/pull/299
6163
[#258]: https://github.com/stm32-rs/stm32f4xx-hal/pull/258
6264
[#257]: https://github.com/stm32-rs/stm32f4xx-hal/pull/257
63-
6465
### Fixed
6566

6667
- Corrected pin definitions for the Flexible Static Memory Controller / Flexible Memory Controller

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,3 +398,7 @@ required-features = ["rt", "stm32f412", "fsmc_lcd"]
398398
[[example]]
399399
name = "f413disco_lcd_ferris"
400400
required-features = ["rt", "stm32f413", "fsmc_lcd"]
401+
402+
[[example]]
403+
name= "pwm-input"
404+
required-features = ["stm32f446"]

examples/pwm-input.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#![deny(unsafe_code)]
2+
#![no_main]
3+
#![no_std]
4+
5+
// Halt on panic
6+
use panic_halt as _;
7+
8+
use cortex_m;
9+
use cortex_m_rt::entry;
10+
use stm32f4xx_hal::{prelude::*, stm32, timer::Timer};
11+
12+
#[entry]
13+
fn main() -> ! {
14+
if let Some(dp) = stm32::Peripherals::take() {
15+
// Set up the system clock.
16+
let rcc = dp.RCC.constrain();
17+
let clocks = rcc.cfgr.freeze();
18+
19+
let gpioa = dp.GPIOA.split();
20+
let gpioc = dp.GPIOC.split();
21+
22+
let channels = (gpioa.pa8.into_alternate(), gpioa.pa9.into_alternate());
23+
// configure tim1 as a PWM output of known frequency.
24+
let pwm = Timer::new(dp.TIM1, &clocks).pwm(channels, 501u32.hz());
25+
let (mut ch1, _ch2) = pwm;
26+
let max_duty = ch1.get_max_duty();
27+
ch1.set_duty(max_duty / 2);
28+
ch1.enable();
29+
30+
// Configure a pin into TIM8_CH1 mode, which will be used to observe an input PWM signal.
31+
let pwm_reader_ch1 = gpioc.pc6.into_alternate();
32+
33+
// configure tim8 as a PWM input, using the best-guess frequency of the input signal.
34+
let monitor = Timer::new(dp.TIM8, &clocks).pwm_input(500u32.hz(), pwm_reader_ch1);
35+
36+
// NOTE: this value may only be accurately observed at the CC2 interrupt.
37+
let _duty = monitor.get_duty_cycle();
38+
}
39+
40+
loop {
41+
cortex_m::asm::nop();
42+
}
43+
}

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,8 @@ pub mod prelude;
152152
#[cfg(feature = "device-selected")]
153153
pub mod pwm;
154154
#[cfg(feature = "device-selected")]
155+
pub mod pwm_input;
156+
#[cfg(feature = "device-selected")]
155157
pub mod qei;
156158
#[cfg(feature = "device-selected")]
157159
pub mod rcc;

src/pwm_input.rs

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
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

Comments
 (0)