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    peripheral::{Peripheral, PeripheralRef},
95    system::{self, PeripheralGuard},
96    time::Rate,
97};
98
99/// MCPWM operators
100pub mod operator;
101/// MCPWM timers
102pub mod timer;
103
104type RegisterBlock = pac::mcpwm0::RegisterBlock;
105
106/// The MCPWM peripheral
107#[non_exhaustive]
108pub struct McPwm<'d, PWM> {
109    _inner: PeripheralRef<'d, PWM>,
110    /// Timer0
111    pub timer0: Timer<0, PWM>,
112    /// Timer1
113    pub timer1: Timer<1, PWM>,
114    /// Timer2
115    pub timer2: Timer<2, PWM>,
116    /// Operator0
117    pub operator0: Operator<'d, 0, PWM>,
118    /// Operator1
119    pub operator1: Operator<'d, 1, PWM>,
120    /// Operator2
121    pub operator2: Operator<'d, 2, PWM>,
122    _guard: PeripheralGuard,
123}
124
125impl<'d, PWM: PwmPeripheral> McPwm<'d, PWM> {
126    /// `pwm_clk = clocks.crypto_pwm_clock / (prescaler + 1)`
127    // clocks.crypto_pwm_clock normally is 160 MHz
128    pub fn new(
129        peripheral: impl Peripheral<P = PWM> + 'd,
130        peripheral_clock: PeripheralClockConfig,
131    ) -> Self {
132        crate::into_ref!(peripheral);
133
134        let guard = PeripheralGuard::new(PWM::peripheral());
135
136        #[cfg(not(esp32c6))]
137        {
138            let register_block = unsafe { &*PWM::block() };
139
140            // set prescaler
141            register_block
142                .clk_cfg()
143                .write(|w| unsafe { w.clk_prescale().bits(peripheral_clock.prescaler) });
144
145            // enable clock
146            register_block.clk().write(|w| w.en().set_bit());
147        }
148
149        #[cfg(esp32c6)]
150        {
151            crate::peripherals::PCR::regs()
152                .pwm_clk_conf()
153                .modify(|_, w| unsafe {
154                    w.pwm_div_num()
155                        .bits(peripheral_clock.prescaler)
156                        .pwm_clkm_en()
157                        .set_bit()
158                        .pwm_clkm_sel()
159                        .bits(1)
160                });
161        }
162
163        #[cfg(esp32h2)]
164        {
165            crate::peripherals::PCR::regs()
166                .pwm_clk_conf()
167                .modify(|_, w| unsafe {
168                    w.pwm_div_num()
169                        .bits(peripheral_clock.prescaler)
170                        .pwm_clkm_en()
171                        .set_bit()
172                        .pwm_clkm_sel()
173                        .bits(0)
174                });
175        }
176
177        Self {
178            _inner: peripheral,
179            timer0: Timer::new(),
180            timer1: Timer::new(),
181            timer2: Timer::new(),
182            operator0: Operator::new(),
183            operator1: Operator::new(),
184            operator2: Operator::new(),
185            _guard: guard,
186        }
187    }
188}
189
190/// Clock configuration of the MCPWM peripheral
191#[derive(Copy, Clone)]
192pub struct PeripheralClockConfig {
193    frequency: Rate,
194    prescaler: u8,
195}
196
197impl PeripheralClockConfig {
198    /// Get a clock configuration with the given prescaler.
199    ///
200    /// With standard system clock configurations the input clock to the MCPWM
201    /// peripheral is `160 MHz`.
202    ///
203    /// The peripheral clock frequency is calculated as:
204    /// `peripheral_clock = input_clock / (prescaler + 1)`
205    pub fn with_prescaler(prescaler: u8) -> Self {
206        let clocks = Clocks::get();
207        cfg_if::cfg_if! {
208            if #[cfg(esp32)] {
209                let source_clock = clocks.pwm_clock;
210            } else if #[cfg(esp32c6)] {
211                let source_clock = clocks.crypto_clock;
212            } else if #[cfg(esp32s3)] {
213                let source_clock = clocks.crypto_pwm_clock;
214            } else if #[cfg(esp32h2)] {
215                let source_clock = clocks.xtal_clock;
216            }
217        }
218
219        Self {
220            frequency: source_clock / (prescaler as u32 + 1),
221            prescaler,
222        }
223    }
224
225    /// Get a clock configuration with the given frequency.
226    ///
227    /// ### Note:
228    /// This will try to select an appropriate prescaler for the
229    /// [`PeripheralClockConfig::with_prescaler`] method.
230    /// If the calculated prescaler is not in the range `0..u8::MAX`
231    /// [`FrequencyError`] will be returned.
232    ///
233    /// With standard system clock configurations the input clock to the MCPWM
234    /// peripheral is `160 MHz`.
235    ///
236    /// Only divisors of the input clock (`160 Mhz / 1`, `160 Mhz / 2`, ...,
237    /// `160 Mhz / 256`) are representable exactly. Other target frequencies
238    /// will be rounded up to the next divisor.
239    pub fn with_frequency(target_freq: Rate) -> Result<Self, FrequencyError> {
240        let clocks = Clocks::get();
241        cfg_if::cfg_if! {
242            if #[cfg(esp32)] {
243                let source_clock = clocks.pwm_clock;
244            } else if #[cfg(esp32c6)] {
245                let source_clock = clocks.crypto_clock;
246            } else if #[cfg(esp32s3)] {
247                let source_clock = clocks.crypto_pwm_clock;
248            } else if #[cfg(esp32h2)] {
249                let source_clock = clocks.xtal_clock;
250            }
251        }
252
253        if target_freq.as_hz() == 0 || target_freq > source_clock {
254            return Err(FrequencyError);
255        }
256
257        let prescaler = source_clock / target_freq - 1;
258        if prescaler > u8::MAX as u32 {
259            return Err(FrequencyError);
260        }
261
262        Ok(Self::with_prescaler(prescaler as u8))
263    }
264
265    /// Get the peripheral clock frequency.
266    ///
267    /// ### Note:
268    /// The actual value is rounded down to the nearest `u32` value
269    pub fn frequency(&self) -> Rate {
270        self.frequency
271    }
272
273    /// Get a timer clock configuration with the given prescaler.
274    ///
275    /// The resulting timer frequency depends on the chosen
276    /// [`timer::PwmWorkingMode`].
277    ///
278    /// #### `PwmWorkingMode::Increase` or `PwmWorkingMode::Decrease`
279    /// `timer_frequency = peripheral_clock / (prescaler + 1) / (period + 1)`
280    /// #### `PwmWorkingMode::UpDown`
281    /// `timer_frequency = peripheral_clock / (prescaler + 1) / (2 * period)`
282    pub fn timer_clock_with_prescaler(
283        &self,
284        period: u16,
285        mode: timer::PwmWorkingMode,
286        prescaler: u8,
287    ) -> timer::TimerClockConfig {
288        timer::TimerClockConfig::with_prescaler(self, period, mode, prescaler)
289    }
290
291    /// Get a timer clock configuration with the given frequency.
292    ///
293    /// ### Note:
294    /// This will try to select an appropriate prescaler for the timer.
295    /// If the calculated prescaler is not in the range `0..u8::MAX`
296    /// [`FrequencyError`] will be returned.
297    ///
298    /// See [`PeripheralClockConfig::timer_clock_with_prescaler`] for how the
299    /// frequency is calculated.
300    pub fn timer_clock_with_frequency(
301        &self,
302        period: u16,
303        mode: timer::PwmWorkingMode,
304        target_freq: Rate,
305    ) -> Result<timer::TimerClockConfig, FrequencyError> {
306        timer::TimerClockConfig::with_frequency(self, period, mode, target_freq)
307    }
308}
309
310/// Target frequency could not be set.
311/// Check how the frequency is calculated in the corresponding method docs.
312#[derive(Copy, Clone, Debug)]
313#[cfg_attr(feature = "defmt", derive(defmt::Format))]
314pub struct FrequencyError;
315
316/// A MCPWM peripheral
317pub trait PwmPeripheral: crate::private::Sealed {
318    /// Get a pointer to the peripheral RegisterBlock
319    fn block() -> *const RegisterBlock;
320    /// Get operator GPIO mux output signal
321    fn output_signal<const OP: u8, const IS_A: bool>() -> OutputSignal;
322    /// Peripheral
323    fn peripheral() -> system::Peripheral;
324}
325
326#[cfg(mcpwm0)]
327impl PwmPeripheral for crate::peripherals::MCPWM0 {
328    fn block() -> *const RegisterBlock {
329        Self::regs()
330    }
331
332    fn output_signal<const OP: u8, const IS_A: bool>() -> OutputSignal {
333        match (OP, IS_A) {
334            (0, true) => OutputSignal::PWM0_0A,
335            (1, true) => OutputSignal::PWM0_1A,
336            (2, true) => OutputSignal::PWM0_2A,
337            (0, false) => OutputSignal::PWM0_0B,
338            (1, false) => OutputSignal::PWM0_1B,
339            (2, false) => OutputSignal::PWM0_2B,
340            _ => unreachable!(),
341        }
342    }
343
344    fn peripheral() -> system::Peripheral {
345        system::Peripheral::Mcpwm0
346    }
347}
348
349#[cfg(mcpwm1)]
350impl PwmPeripheral for crate::peripherals::MCPWM1 {
351    fn block() -> *const RegisterBlock {
352        Self::regs()
353    }
354
355    fn output_signal<const OP: u8, const IS_A: bool>() -> OutputSignal {
356        match (OP, IS_A) {
357            (0, true) => OutputSignal::PWM1_0A,
358            (1, true) => OutputSignal::PWM1_1A,
359            (2, true) => OutputSignal::PWM1_2A,
360            (0, false) => OutputSignal::PWM1_0B,
361            (1, false) => OutputSignal::PWM1_1B,
362            (2, false) => OutputSignal::PWM1_2B,
363            _ => unreachable!(),
364        }
365    }
366
367    fn peripheral() -> system::Peripheral {
368        system::Peripheral::Mcpwm1
369    }
370}