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, 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}
25
26impl<const TIM: u8, PWM: PwmPeripheral> Timer<TIM, PWM> {
27    pub(super) fn new() -> Self {
28        let guard = PeripheralGuard::new(PWM::peripheral());
29        Timer {
30            phantom: PhantomData,
31            _guard: guard,
32        }
33    }
34
35    /// Apply the given timer configuration.
36    ///
37    /// ### Note:
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 you might want to call [`Timer::stop`]
41    /// and/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    /// Stop 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    /// Set 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    /// Read 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 CFG0 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/// Clock 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 - 1;
171        if prescaler > u8::MAX as u32 {
172            return Err(FrequencyError);
173        }
174        let frequency = clock.frequency / (prescaler + 1) / cycle_period;
175
176        Ok(TimerClockConfig {
177            frequency,
178            prescaler: prescaler as u8,
179            period,
180            period_updating_method: PeriodUpdatingMethod::Immediately,
181            mode,
182        })
183    }
184
185    /// Set the method for updating the PWM period
186    pub fn with_period_updating_method(self, method: PeriodUpdatingMethod) -> Self {
187        Self {
188            period_updating_method: method,
189            ..self
190        }
191    }
192
193    /// Get the timer clock frequency.
194    ///
195    /// ### Note:
196    /// The actual value is rounded down to the nearest `u32` value
197    pub fn frequency(&self) -> Rate {
198        self.frequency
199    }
200}
201
202/// Method for updating the PWM period
203#[derive(Clone, Copy)]
204#[repr(u8)]
205pub enum PeriodUpdatingMethod {
206    /// The period is updated immediately.
207    Immediately           = 0,
208    /// The period is updated when the timer equals zero.
209    TimerEqualsZero       = 1,
210    /// The period is updated on a synchronization event.
211    Sync                  = 2,
212    /// The period is updated either when the timer equals zero or on a
213    /// synchronization event.
214    TimerEqualsZeroOrSync = 3,
215}
216
217/// PWM working mode
218#[derive(Copy, Clone)]
219#[repr(u8)]
220pub enum PwmWorkingMode {
221    /// In this mode, the PWM timer increments from zero until reaching the
222    /// value configured in the period field. Once done, the PWM timer
223    /// returns to zero and starts increasing again. PWM period is equal to the
224    /// value of the period field + 1.
225    Increase = 1,
226    /// The PWM timer decrements to zero, starting from the value configured in
227    /// the period field. After reaching zero, it is set back to the period
228    /// value. Then it starts to decrement again. In this case, the PWM period
229    /// is also equal to the value of period field + 1.
230    Decrease = 2,
231    /// This is a combination of the two modes mentioned above. The PWM timer
232    /// starts increasing from zero until the period value is reached. Then,
233    /// the timer decreases back to zero. This pattern is then repeated. The
234    /// PWM period is the result of the value of the period field × 2.
235    UpDown   = 3,
236}
237
238/// The direction the timer counter is changing
239#[derive(Debug)]
240#[repr(u8)]
241pub enum CounterDirection {
242    /// The timer counter is increasing
243    Increasing = 0,
244    /// The timer counter is decreasing
245    Decreasing = 1,
246}
247
248impl From<bool> for CounterDirection {
249    fn from(bit: bool) -> Self {
250        match bit {
251            false => CounterDirection::Increasing,
252            true => CounterDirection::Decreasing,
253        }
254    }
255}