Skip to main content

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