esp_hal/mcpwm/
mod.rs

1//! # Motor Control Pulse Width Modulator (MCPWM)
2//!
3//! ## Overview
4//!
5//! The MCPWM peripheral is a versatile PWM generator, which contains various
6//! submodules to make it a key element in power electronic applications like
7//! motor control, digital power, and so on. Typically, the MCPWM peripheral can
8//! be used in the following scenarios:
9//! - Digital motor control, e.g., brushed/brushless DC motor, RC servo motor
10//! - Switch mode-based digital power conversion
11//! - Power DAC, where the duty cycle is equivalent to a DAC analog value
12//! - Calculate external pulse width, and convert it into other analog values
13//!   like speed, distance
14//! - Generate Space Vector PWM (SVPWM) signals for Field Oriented Control (FOC)
15//!
16//! ## Configuration
17//!
18//! * PWM Timers 0, 1 and 2
19//!     * Every PWM timer has a dedicated 8-bit clock prescaler.
20//!     * The 16-bit counter in the PWM timer can work in count-up mode,
21//!       count-down mode or count-up-down mode.
22//!     * A hardware sync or software sync can trigger a reload on the PWM timer
23//!       with a phase register (Not yet implemented)
24//! * PWM Operators 0, 1 and 2
25//!     * Every PWM operator has two PWM outputs: PWMxA and PWMxB. They can work
26//!       independently, in symmetric and asymmetric configuration.
27//!     * Software, asynchronously override control of PWM signals.
28//!     * Configurable dead-time on rising and falling edges; each set up
29//!       independently. (Not yet implemented)
30//!     * All events can trigger CPU interrupts. (Not yet implemented)
31//!     * Modulating of PWM output by high-frequency carrier signals, useful
32//!       when gate drivers are insulated with a transformer. (Not yet
33//!       implemented)
34//!     * Period, time stamps and important control registers have shadow
35//!       registers with flexible updating methods.
36//! * Fault Detection Module (Not yet implemented)
37//! * Capture Module (Not yet implemented)
38#![doc = ""]
39#![cfg_attr(esp32, doc = "Clock source is PWM_CLOCK")]
40#![cfg_attr(esp32s3, doc = "Clock source is CRYPTO_PWM_CLOCK")]
41#![cfg_attr(esp32c6, doc = "Clock source is CRYPTO_CLOCK")]
42#![cfg_attr(esp32h2, doc = "Clock source is XTAL")]
43#![doc = ""]
44//! ## Examples
45//!
46//! ### Output a 20 kHz signal
47//!
48//! This example uses timer0 and operator0 of the MCPWM0 peripheral to output a
49//! 50% duty signal at 20 kHz. The signal will be output to the pin assigned to
50//! `pin`.
51//!
52//! ```rust, no_run
53#![doc = crate::before_snippet!()]
54//! # use esp_hal::mcpwm::{operator::{DeadTimeCfg, PWMStream, PwmPinConfig}, timer::PwmWorkingMode, McPwm, PeripheralClockConfig};
55//! # let pin = peripherals.GPIO0;
56//!
57//! // initialize peripheral
58#![cfg_attr(
59    esp32h2,
60    doc = "let clock_cfg = PeripheralClockConfig::with_frequency(Rate::from_mhz(40))?;"
61)]
62#![cfg_attr(
63    not(esp32h2),
64    doc = "let clock_cfg = PeripheralClockConfig::with_frequency(Rate::from_mhz(32))?;"
65)]
66//! let mut mcpwm = McPwm::new(peripherals.MCPWM0, clock_cfg);
67//!
68//! // connect operator0 to timer0
69//! mcpwm.operator0.set_timer(&mcpwm.timer0);
70//! // connect operator0 to pin
71//! let mut pwm_pin = mcpwm
72//!     .operator0
73//!     .with_pin_a(pin, PwmPinConfig::UP_ACTIVE_HIGH);
74//!
75//! // start timer with timestamp values in the range of 0..=99 and a frequency
76//! // of 20 kHz
77//! let timer_clock_cfg = clock_cfg
78//!     .timer_clock_with_frequency(99, PwmWorkingMode::Increase,
79//! Rate::from_khz(20))?; mcpwm.timer0.start(timer_clock_cfg);
80//!
81//! // pin will be high 50% of the time
82//! pwm_pin.set_timestamp(50);
83//! # Ok(())
84//! # }
85//! ```
86
87use operator::Operator;
88use timer::Timer;
89
90use crate::{
91    clock::Clocks,
92    gpio::OutputSignal,
93    pac,
94    system::{self, PeripheralGuard},
95    time::Rate,
96};
97
98/// MCPWM operators
99pub mod operator;
100/// MCPWM timers
101pub mod timer;
102
103type RegisterBlock = pac::mcpwm0::RegisterBlock;
104
105/// The MCPWM peripheral
106#[non_exhaustive]
107pub struct McPwm<'d, PWM> {
108    _inner: PWM,
109    /// Timer0
110    pub timer0: Timer<0, PWM>,
111    /// Timer1
112    pub timer1: Timer<1, PWM>,
113    /// Timer2
114    pub timer2: Timer<2, PWM>,
115    /// Operator0
116    pub operator0: Operator<'d, 0, PWM>,
117    /// Operator1
118    pub operator1: Operator<'d, 1, PWM>,
119    /// Operator2
120    pub operator2: Operator<'d, 2, PWM>,
121    _guard: PeripheralGuard,
122}
123
124impl<'d, PWM: PwmPeripheral + 'd> McPwm<'d, PWM> {
125    /// `pwm_clk = clocks.crypto_pwm_clock / (prescaler + 1)`
126    // clocks.crypto_pwm_clock normally is 160 MHz
127    pub fn new(peripheral: PWM, peripheral_clock: PeripheralClockConfig) -> Self {
128        let guard = PeripheralGuard::new(PWM::peripheral());
129
130        #[cfg(not(esp32c6))]
131        {
132            let register_block = unsafe { &*PWM::block() };
133
134            // set prescaler
135            register_block
136                .clk_cfg()
137                .write(|w| unsafe { w.clk_prescale().bits(peripheral_clock.prescaler) });
138
139            // enable clock
140            register_block.clk().write(|w| w.en().set_bit());
141        }
142
143        #[cfg(esp32c6)]
144        {
145            crate::peripherals::PCR::regs()
146                .pwm_clk_conf()
147                .modify(|_, w| unsafe {
148                    w.pwm_div_num()
149                        .bits(peripheral_clock.prescaler)
150                        .pwm_clkm_en()
151                        .set_bit()
152                        .pwm_clkm_sel()
153                        .bits(1)
154                });
155        }
156
157        #[cfg(esp32h2)]
158        {
159            crate::peripherals::PCR::regs()
160                .pwm_clk_conf()
161                .modify(|_, w| unsafe {
162                    w.pwm_div_num()
163                        .bits(peripheral_clock.prescaler)
164                        .pwm_clkm_en()
165                        .set_bit()
166                        .pwm_clkm_sel()
167                        .bits(0)
168                });
169        }
170
171        Self {
172            _inner: peripheral,
173            timer0: Timer::new(),
174            timer1: Timer::new(),
175            timer2: Timer::new(),
176            operator0: Operator::new(),
177            operator1: Operator::new(),
178            operator2: Operator::new(),
179            _guard: guard,
180        }
181    }
182}
183
184/// Clock configuration of the MCPWM peripheral
185#[derive(Copy, Clone)]
186pub struct PeripheralClockConfig {
187    frequency: Rate,
188    prescaler: u8,
189}
190
191impl PeripheralClockConfig {
192    /// Get a clock configuration with the given prescaler.
193    ///
194    /// With standard system clock configurations the input clock to the MCPWM
195    /// peripheral is `160 MHz`.
196    ///
197    /// The peripheral clock frequency is calculated as:
198    /// `peripheral_clock = input_clock / (prescaler + 1)`
199    pub fn with_prescaler(prescaler: u8) -> Self {
200        let clocks = Clocks::get();
201        cfg_if::cfg_if! {
202            if #[cfg(esp32)] {
203                let source_clock = clocks.pwm_clock;
204            } else if #[cfg(esp32c6)] {
205                let source_clock = clocks.crypto_clock;
206            } else if #[cfg(esp32s3)] {
207                let source_clock = clocks.crypto_pwm_clock;
208            } else if #[cfg(esp32h2)] {
209                let source_clock = clocks.xtal_clock;
210            }
211        }
212
213        Self {
214            frequency: source_clock / (prescaler as u32 + 1),
215            prescaler,
216        }
217    }
218
219    /// Get a clock configuration with the given frequency.
220    ///
221    /// ### Note:
222    /// This will try to select an appropriate prescaler for the
223    /// [`PeripheralClockConfig::with_prescaler`] method.
224    /// If the calculated prescaler is not in the range `0..u8::MAX`
225    /// [`FrequencyError`] will be returned.
226    ///
227    /// With standard system clock configurations the input clock to the MCPWM
228    /// peripheral is `160 MHz`.
229    ///
230    /// Only divisors of the input clock (`160 Mhz / 1`, `160 Mhz / 2`, ...,
231    /// `160 Mhz / 256`) are representable exactly. Other target frequencies
232    /// will be rounded up to the next divisor.
233    pub fn with_frequency(target_freq: Rate) -> Result<Self, FrequencyError> {
234        let clocks = Clocks::get();
235        cfg_if::cfg_if! {
236            if #[cfg(esp32)] {
237                let source_clock = clocks.pwm_clock;
238            } else if #[cfg(esp32c6)] {
239                let source_clock = clocks.crypto_clock;
240            } else if #[cfg(esp32s3)] {
241                let source_clock = clocks.crypto_pwm_clock;
242            } else if #[cfg(esp32h2)] {
243                let source_clock = clocks.xtal_clock;
244            }
245        }
246
247        if target_freq.as_hz() == 0 || target_freq > source_clock {
248            return Err(FrequencyError);
249        }
250
251        let prescaler = source_clock / target_freq - 1;
252        if prescaler > u8::MAX as u32 {
253            return Err(FrequencyError);
254        }
255
256        Ok(Self::with_prescaler(prescaler as u8))
257    }
258
259    /// Get the peripheral clock frequency.
260    ///
261    /// ### Note:
262    /// The actual value is rounded down to the nearest `u32` value
263    pub fn frequency(&self) -> Rate {
264        self.frequency
265    }
266
267    /// Get a timer clock configuration with the given prescaler.
268    ///
269    /// The resulting timer frequency depends on the chosen
270    /// [`timer::PwmWorkingMode`].
271    ///
272    /// #### `PwmWorkingMode::Increase` or `PwmWorkingMode::Decrease`
273    /// `timer_frequency = peripheral_clock / (prescaler + 1) / (period + 1)`
274    /// #### `PwmWorkingMode::UpDown`
275    /// `timer_frequency = peripheral_clock / (prescaler + 1) / (2 * period)`
276    pub fn timer_clock_with_prescaler(
277        &self,
278        period: u16,
279        mode: timer::PwmWorkingMode,
280        prescaler: u8,
281    ) -> timer::TimerClockConfig {
282        timer::TimerClockConfig::with_prescaler(self, period, mode, prescaler)
283    }
284
285    /// Get a timer clock configuration with the given frequency.
286    ///
287    /// ### Note:
288    /// This will try to select an appropriate prescaler for the timer.
289    /// If the calculated prescaler is not in the range `0..u8::MAX`
290    /// [`FrequencyError`] will be returned.
291    ///
292    /// See [`PeripheralClockConfig::timer_clock_with_prescaler`] for how the
293    /// frequency is calculated.
294    pub fn timer_clock_with_frequency(
295        &self,
296        period: u16,
297        mode: timer::PwmWorkingMode,
298        target_freq: Rate,
299    ) -> Result<timer::TimerClockConfig, FrequencyError> {
300        timer::TimerClockConfig::with_frequency(self, period, mode, target_freq)
301    }
302}
303
304/// Target frequency could not be set.
305/// Check how the frequency is calculated in the corresponding method docs.
306#[derive(Copy, Clone, Debug)]
307#[cfg_attr(feature = "defmt", derive(defmt::Format))]
308pub struct FrequencyError;
309
310/// A MCPWM peripheral
311pub trait PwmPeripheral: crate::private::Sealed {
312    /// Get a pointer to the peripheral RegisterBlock
313    fn block() -> *const RegisterBlock;
314    /// Get operator GPIO mux output signal
315    fn output_signal<const OP: u8, const IS_A: bool>() -> OutputSignal;
316    /// Peripheral
317    fn peripheral() -> system::Peripheral;
318}
319
320#[cfg(mcpwm0)]
321impl PwmPeripheral for crate::peripherals::MCPWM0<'_> {
322    fn block() -> *const RegisterBlock {
323        Self::regs()
324    }
325
326    fn output_signal<const OP: u8, const IS_A: bool>() -> OutputSignal {
327        match (OP, IS_A) {
328            (0, true) => OutputSignal::PWM0_0A,
329            (1, true) => OutputSignal::PWM0_1A,
330            (2, true) => OutputSignal::PWM0_2A,
331            (0, false) => OutputSignal::PWM0_0B,
332            (1, false) => OutputSignal::PWM0_1B,
333            (2, false) => OutputSignal::PWM0_2B,
334            _ => unreachable!(),
335        }
336    }
337
338    fn peripheral() -> system::Peripheral {
339        system::Peripheral::Mcpwm0
340    }
341}
342
343#[cfg(mcpwm1)]
344impl PwmPeripheral for crate::peripherals::MCPWM1<'_> {
345    fn block() -> *const RegisterBlock {
346        Self::regs()
347    }
348
349    fn output_signal<const OP: u8, const IS_A: bool>() -> OutputSignal {
350        match (OP, IS_A) {
351            (0, true) => OutputSignal::PWM1_0A,
352            (1, true) => OutputSignal::PWM1_1A,
353            (2, true) => OutputSignal::PWM1_2A,
354            (0, false) => OutputSignal::PWM1_0B,
355            (1, false) => OutputSignal::PWM1_1B,
356            (2, false) => OutputSignal::PWM1_2B,
357            _ => unreachable!(),
358        }
359    }
360
361    fn peripheral() -> system::Peripheral {
362        system::Peripheral::Mcpwm1
363    }
364}