esp_hal/mcpwm/timer.rs
1//! # MCPWM Timer Module
2//!
3//! ## Overview
4//! The `timer` module provides an interface to configure and use timers for
5//! generating `PWM` signals used in motor control and other applications.
6
7use core::marker::PhantomData;
8
9use super::PeripheralGuard;
10use crate::{
11 mcpwm::{FrequencyError, PeripheralClockConfig, PwmClockGuard, PwmPeripheral},
12 pac,
13 time::Rate,
14};
15
16/// A MCPWM timer.
17///
18/// Every timer of a particular [`MCPWM`](super::McPwm) peripheral can be used
19/// as a timing reference for every
20/// [`Operator`](super::operator::Operator) of that peripheral
21pub struct Timer<const TIM: u8, PWM> {
22 pub(super) phantom: PhantomData<PWM>,
23 _guard: PeripheralGuard,
24 _pwm_clock_guard: PwmClockGuard,
25}
26
27impl<const TIM: u8, PWM: PwmPeripheral> Timer<TIM, PWM> {
28 pub(super) fn new(guard: PeripheralGuard) -> Self {
29 Timer {
30 phantom: PhantomData,
31 _guard: guard,
32 _pwm_clock_guard: PwmClockGuard::new::<PWM>(),
33 }
34 }
35
36 /// Applies the given timer configuration.
37 ///
38 /// The prescaler and period configuration will be applied immediately by
39 /// default and before setting the [`PwmWorkingMode`].
40 /// If the timer is already running, call [`Timer::stop`]
41 /// or [`Timer::set_counter`] first
42 /// (if the new period is larger than the current counter value this will
43 /// cause weird behavior).
44 ///
45 /// If configured via [`TimerClockConfig::with_period_updating_method`],
46 /// another behavior can be applied. Currently, only
47 /// [`PeriodUpdatingMethod::Immediately`]
48 /// and [`PeriodUpdatingMethod::TimerEqualsZero`] are useful as the sync
49 /// method is not yet implemented.
50 ///
51 /// The hardware supports writing these settings in sync with certain timer
52 /// events but this HAL does not expose these for now.
53 pub fn start(&mut self, timer_config: TimerClockConfig) {
54 // write prescaler and period with immediate update method
55 self.cfg0().write(|w| unsafe {
56 w.prescale().bits(timer_config.prescaler);
57 w.period().bits(timer_config.period);
58 w.period_upmethod()
59 .bits(timer_config.period_updating_method as u8)
60 });
61
62 // set timer to continuously run and set the timer working mode
63 self.cfg1().write(|w| unsafe {
64 w.start().bits(2);
65 w.mod_().bits(timer_config.mode as u8)
66 });
67 }
68
69 /// Stops the timer in its current state.
70 pub fn stop(&mut self) {
71 // freeze the timer
72 self.cfg1().write(|w| unsafe { w.mod_().bits(0) });
73 }
74
75 /// Sets the timer counter to the provided value.
76 pub fn set_counter(&mut self, phase: u16, direction: CounterDirection) {
77 // SAFETY:
78 // We only write to our TIMERx_SYNC register
79 let tmr = unsafe { Self::tmr() };
80 let sw = tmr.sync().read().sw().bit_is_set();
81 tmr.sync().write(|w| {
82 w.phase_direction().bit(direction as u8 != 0);
83 unsafe {
84 w.phase().bits(phase);
85 }
86 w.sw().bit(!sw)
87 });
88 }
89
90 /// Reads the counter value and counter direction of the timer.
91 pub fn status(&self) -> (u16, CounterDirection) {
92 // SAFETY:
93 // We only read from our TIMERx_STATUS register
94 let reg = unsafe { Self::tmr() }.status().read();
95 (reg.value().bits(), reg.direction().bit_is_set().into())
96 }
97
98 fn cfg0(&mut self) -> &pac::mcpwm0::timer::CFG0 {
99 // SAFETY:
100 // We only grant access to our CFG0 register with the lifetime of &mut self
101 unsafe { Self::tmr() }.cfg0()
102 }
103
104 fn cfg1(&mut self) -> &pac::mcpwm0::timer::CFG1 {
105 // SAFETY:
106 // We only grant access to our CFG1 register with the lifetime of &mut self
107 unsafe { Self::tmr() }.cfg1()
108 }
109
110 unsafe fn tmr() -> &'static pac::mcpwm0::TIMER {
111 let block = unsafe { &*PWM::block() };
112 block.timer(TIM as usize)
113 }
114}
115
116/// Clocks configuration of a MCPWM timer.
117///
118/// Use [`PeripheralClockConfig::timer_clock_with_prescaler`](super::PeripheralClockConfig::timer_clock_with_prescaler) or
119/// [`PeripheralClockConfig::timer_clock_with_frequency`](super::PeripheralClockConfig::timer_clock_with_frequency) to it.
120#[derive(Copy, Clone)]
121pub struct TimerClockConfig {
122 frequency: Rate,
123 period: u16,
124 period_updating_method: PeriodUpdatingMethod,
125 prescaler: u8,
126 mode: PwmWorkingMode,
127}
128
129impl TimerClockConfig {
130 pub(super) fn with_prescaler(
131 clock: &PeripheralClockConfig,
132 period: u16,
133 mode: PwmWorkingMode,
134 prescaler: u8,
135 ) -> Self {
136 let cycle_period = match mode {
137 PwmWorkingMode::Increase | PwmWorkingMode::Decrease => period as u32 + 1,
138 // The reference manual seems to provide an incorrect formula for UpDown
139 PwmWorkingMode::UpDown => period as u32 * 2,
140 };
141 let frequency = clock.frequency / (prescaler as u32 + 1) / cycle_period;
142
143 TimerClockConfig {
144 frequency,
145 prescaler,
146 period,
147 period_updating_method: PeriodUpdatingMethod::Immediately,
148 mode,
149 }
150 }
151
152 pub(super) fn with_frequency(
153 clock: &PeripheralClockConfig,
154 period: u16,
155 mode: PwmWorkingMode,
156 target_freq: Rate,
157 ) -> Result<Self, FrequencyError> {
158 let cycle_period = match mode {
159 PwmWorkingMode::Increase | PwmWorkingMode::Decrease => period as u32 + 1,
160 // The reference manual seems to provide an incorrect formula for UpDown
161 PwmWorkingMode::UpDown => period as u32 * 2,
162 };
163 let target_timer_frequency = target_freq
164 .as_hz()
165 .checked_mul(cycle_period)
166 .ok_or(FrequencyError)?;
167 if target_timer_frequency == 0 || target_freq > clock.frequency {
168 return Err(FrequencyError);
169 }
170 let prescaler = (clock.frequency.as_hz() / target_timer_frequency)
171 .checked_sub(1)
172 .ok_or(FrequencyError)?;
173 if prescaler > u8::MAX as u32 {
174 return Err(FrequencyError);
175 }
176 let frequency = clock.frequency / (prescaler + 1) / cycle_period;
177
178 Ok(TimerClockConfig {
179 frequency,
180 prescaler: prescaler as u8,
181 period,
182 period_updating_method: PeriodUpdatingMethod::Immediately,
183 mode,
184 })
185 }
186
187 /// Sets the method for updating the PWM period.
188 pub fn with_period_updating_method(self, method: PeriodUpdatingMethod) -> Self {
189 Self {
190 period_updating_method: method,
191 ..self
192 }
193 }
194
195 /// Returns the timer clock frequency.
196 ///
197 /// The actual value is rounded down to the nearest `u32` value.
198 pub fn frequency(&self) -> Rate {
199 self.frequency
200 }
201}
202
203/// Method for updating the PWM period
204#[derive(Clone, Copy)]
205#[repr(u8)]
206pub enum PeriodUpdatingMethod {
207 /// The period is updated immediately.
208 Immediately = 0,
209 /// The period is updated when the timer equals zero.
210 TimerEqualsZero = 1,
211 /// The period is updated on a synchronization event.
212 Sync = 2,
213 /// The period is updated either when the timer equals zero or on a
214 /// synchronization event.
215 TimerEqualsZeroOrSync = 3,
216}
217
218/// PWM working mode
219#[derive(Copy, Clone)]
220#[repr(u8)]
221pub enum PwmWorkingMode {
222 /// In this mode, the PWM timer increments from zero until reaching the
223 /// value configured in the period field. Once done, the PWM timer
224 /// returns to zero and starts increasing again. PWM period is equal to the
225 /// value of the period field + 1.
226 Increase = 1,
227 /// The PWM timer decrements to zero, starting from the value configured in
228 /// the period field. After reaching zero, it is set back to the period
229 /// value. Then it starts to decrement again. In this case, the PWM period
230 /// is also equal to the value of period field + 1.
231 Decrease = 2,
232 /// This is a combination of the two modes mentioned above. The PWM timer
233 /// starts increasing from zero until the period value is reached. Then,
234 /// the timer decreases back to zero. This pattern is then repeated. The
235 /// PWM period is the result of the value of the period field × 2.
236 UpDown = 3,
237}
238
239/// The direction the timer counter is changing.
240#[derive(Debug)]
241#[repr(u8)]
242pub enum CounterDirection {
243 /// The timer counter is increasing.
244 Increasing = 0,
245 /// The timer counter is decreasing.
246 Decreasing = 1,
247}
248
249impl From<bool> for CounterDirection {
250 fn from(bit: bool) -> Self {
251 match bit {
252 false => CounterDirection::Increasing,
253 true => CounterDirection::Decreasing,
254 }
255 }
256}