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}