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}