Skip to content

Commit 657b265

Browse files
Merge #271
271: PWM Input abstraction r=burrbull a=theunkn0wn1 It also presently depends on side effects, due to my limited experience writing at the HAL layer. There is also one unsafe block when configuring the prescalers, as it seems (or my IDE is failing me) that the prescaler's don't have safe abstractions for configuring them. Thnkgs that need be done: - [x] get it working for one MCU - [x] use macros to generate the definition - [x] get it working with all compatible timers across all compatible f4 devices - [x] configure the timer's frequency internally instead of depending on side effects Feedback and suggestions are welcome. - [x] rework as per #337 Co-authored-by: Joshua Salzedo <joshuasalzedo@gmail.com>
2 parents 01e8155 + ae32d5b commit 657b265

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)