Skip to content

Commit c46719e

Browse files
authored
Merge pull request #255 from dimpolo/mcpwm
MCPWM MVP implementation
2 parents 5d48e77 + 0a75d3c commit c46719e

File tree

10 files changed

+1116
-1
lines changed

10 files changed

+1116
-1
lines changed

esp-hal-common/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ esp32 = { version = "0.16.0", features = ["critical-section"], optional = true
5454
esp32c2 = { version = "0.5.1", features = ["critical-section"], optional = true }
5555
esp32c3 = { version = "0.8.1", features = ["critical-section"], optional = true }
5656
esp32s2 = { version = "0.6.0", features = ["critical-section"], optional = true }
57-
esp32s3 = { version = "0.9.0", features = ["critical-section"], optional = true }
57+
esp32s3 = { version = "0.10.0", features = ["critical-section"], optional = true }
5858

5959
[features]
6060
esp32 = ["esp32/rt" , "xtensa", "xtensa-lx/esp32", "xtensa-lx-rt/esp32", "lock_api"]

esp-hal-common/src/clock.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,10 @@ pub struct Clocks {
113113
pub apb_clock: HertzU32,
114114
pub xtal_clock: HertzU32,
115115
pub i2c_clock: HertzU32,
116+
#[cfg(esp32)]
117+
pub pwm_clock: HertzU32,
118+
#[cfg(esp32s3)]
119+
pub crypto_pwm_clock: HertzU32,
116120
// TODO chip specific additional ones as needed
117121
}
118122

@@ -129,6 +133,10 @@ impl Clocks {
129133
apb_clock: raw_clocks.apb_clock,
130134
xtal_clock: raw_clocks.xtal_clock,
131135
i2c_clock: raw_clocks.i2c_clock,
136+
#[cfg(esp32)]
137+
pwm_clock: raw_clocks.pwm_clock,
138+
#[cfg(esp32s3)]
139+
crypto_pwm_clock: raw_clocks.crypto_pwm_clock,
132140
}
133141
}
134142
}
@@ -139,6 +147,10 @@ pub struct RawClocks {
139147
pub apb_clock: HertzU32,
140148
pub xtal_clock: HertzU32,
141149
pub i2c_clock: HertzU32,
150+
#[cfg(esp32)]
151+
pub pwm_clock: HertzU32,
152+
#[cfg(esp32s3)]
153+
pub crypto_pwm_clock: HertzU32,
142154
// TODO chip specific additional ones as needed
143155
}
144156

@@ -172,6 +184,7 @@ impl ClockControl {
172184
apb_clock: HertzU32::MHz(80),
173185
xtal_clock: HertzU32::MHz(40),
174186
i2c_clock: HertzU32::MHz(80),
187+
pwm_clock: HertzU32::MHz(160),
175188
},
176189
}
177190
}
@@ -200,6 +213,10 @@ impl ClockControl {
200213
apb_clock: HertzU32::MHz(80),
201214
xtal_clock: HertzU32::MHz(40),
202215
i2c_clock: HertzU32::MHz(40),
216+
// The docs are unclear here. pwm_clock seems to be tied to clocks.apb_clock
217+
// while simultaneously being fixed at 160 MHz.
218+
// Testing showed 160 MHz to be correct for current clock configurations.
219+
pwm_clock: HertzU32::MHz(160),
203220
},
204221
}
205222
}
@@ -344,6 +361,7 @@ impl ClockControl {
344361
apb_clock: HertzU32::MHz(80),
345362
xtal_clock: HertzU32::MHz(40),
346363
i2c_clock: HertzU32::MHz(40),
364+
crypto_pwm_clock: HertzU32::MHz(160),
347365
},
348366
}
349367
}
@@ -360,6 +378,7 @@ impl ClockControl {
360378
apb_clock: HertzU32::MHz(80),
361379
xtal_clock: HertzU32::MHz(40),
362380
i2c_clock: HertzU32::MHz(40),
381+
crypto_pwm_clock: HertzU32::MHz(160),
363382
},
364383
}
365384
}

esp-hal-common/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ pub mod i2c;
6262
pub mod i2s;
6363

6464
pub mod ledc;
65+
#[cfg(any(esp32, esp32s3))]
66+
pub mod mcpwm;
6567
#[cfg(usb_otg)]
6668
pub mod otg_fs;
6769
pub mod prelude;

esp-hal-common/src/mcpwm/mod.rs

Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
//! MCPWM (Motor Control Pulse Width Modulator) peripheral
2+
//!
3+
//! # Peripheral capabilities:
4+
//! * PWM Timers 0, 1 and 2
5+
//! * Every PWM timer has a dedicated 8-bit clock prescaler.
6+
//! * The 16-bit counter in the PWM timer can work in count-up mode,
7+
//! count-down mode or count-up-down mode.
8+
//! * A hardware sync or software sync can trigger a reload on the PWM timer
9+
//! with a phase register (Not yet implemented)
10+
//! * PWM Operators 0, 1 and 2
11+
//! * Every PWM operator has two PWM outputs: PWMxA and PWMxB. They can work
12+
//! independently, in symmetric and asymmetric configuration.
13+
//! * Software, asynchronously override control of PWM signals.
14+
//! * Configurable dead-time on rising and falling edges; each set up
15+
//! independently. (Not yet implemented)
16+
//! * All events can trigger CPU interrupts. (Not yet implemented)
17+
//! * Modulating of PWM output by high-frequency carrier signals, useful
18+
//! when gate drivers are insulated with a transformer. (Not yet
19+
//! implemented)
20+
//! * Period, time stamps and important control registers have shadow
21+
//! registers with flexible updating methods.
22+
//! * Fault Detection Module (Not yet implemented)
23+
//! * Capture Module (Not yet implemented)
24+
//!
25+
//! # Example
26+
//! Uses timer0 and operator0 of the MCPWM0 peripheral to output a 50% duty
27+
//! signal at 20 kHz. The signal will be output to the pin assigned to `pin`.
28+
//!
29+
//! ```
30+
//! # use esp_hal_common::{mcpwm, prelude::*};
31+
//! use mcpwm::{operator::PwmPinConfig, timer::PwmWorkingMode, PeripheralClockConfig, MCPWM};
32+
//!
33+
//! // initialize peripheral
34+
//! let clock_cfg = PeripheralClockConfig::with_frequency(&clocks, 40u32.MHz()).unwrap();
35+
//! let mut mcpwm = MCPWM::new(
36+
//! peripherals.PWM0,
37+
//! clock_cfg,
38+
//! &mut system.peripheral_clock_control,
39+
//! );
40+
//!
41+
//! // connect operator0 to timer0
42+
//! mcpwm.operator0.set_timer(&mcpwm.timer0);
43+
//! // connect operator0 to pin
44+
//! let mut pwm_pin = mcpwm
45+
//! .operator0
46+
//! .with_pin_a(pin, PwmPinConfig::UP_ACTIVE_HIGH);
47+
//!
48+
//! // start timer with timestamp values in the range of 0..=99 and a frequency of 20 kHz
49+
//! let timer_clock_cfg = clock_cfg
50+
//! .timer_clock_with_frequency(99, PwmWorkingMode::Increase, 20u32.kHz())
51+
//! .unwrap();
52+
//! mcpwm.timer0.start(timer_clock_cfg);
53+
//!
54+
//! // pin will be high 50% of the time
55+
//! pwm_pin.set_timestamp(50);
56+
//! ```
57+
58+
#![deny(missing_docs)]
59+
60+
use core::{marker::PhantomData, ops::Deref};
61+
62+
use fugit::HertzU32;
63+
use operator::Operator;
64+
use timer::Timer;
65+
66+
use crate::{
67+
clock::Clocks,
68+
system::{Peripheral, PeripheralClockControl},
69+
types::OutputSignal,
70+
};
71+
72+
/// MCPWM operators
73+
pub mod operator;
74+
/// MCPWM timers
75+
pub mod timer;
76+
77+
/// The MCPWM peripheral
78+
#[non_exhaustive]
79+
pub struct MCPWM<PWM> {
80+
/// Timer0
81+
pub timer0: Timer<0, PWM>,
82+
/// Timer1
83+
pub timer1: Timer<1, PWM>,
84+
/// Timer2
85+
pub timer2: Timer<2, PWM>,
86+
/// Operator0
87+
pub operator0: Operator<0, PWM>,
88+
/// Operator1
89+
pub operator1: Operator<1, PWM>,
90+
/// Operator2
91+
pub operator2: Operator<2, PWM>,
92+
}
93+
94+
impl<PWM: PwmPeripheral> MCPWM<PWM> {
95+
/// `pwm_clk = clocks.crypto_pwm_clock / (prescaler + 1)`
96+
// clocks.crypto_pwm_clock normally is 160 MHz
97+
pub fn new(
98+
peripheral: PWM,
99+
peripheral_clock: PeripheralClockConfig,
100+
system: &mut PeripheralClockControl,
101+
) -> Self {
102+
let _ = peripheral;
103+
104+
PWM::enable(system);
105+
106+
// set prescaler
107+
peripheral
108+
.clk_cfg
109+
.write(|w| w.clk_prescale().variant(peripheral_clock.prescaler));
110+
// enable clock
111+
peripheral.clk.write(|w| w.en().set_bit());
112+
113+
MCPWM {
114+
timer0: Timer::new(),
115+
timer1: Timer::new(),
116+
timer2: Timer::new(),
117+
operator0: Operator::new(),
118+
operator1: Operator::new(),
119+
operator2: Operator::new(),
120+
}
121+
}
122+
}
123+
124+
/// Clock configuration of the MCPWM peripheral
125+
#[derive(Copy, Clone)]
126+
pub struct PeripheralClockConfig<'a> {
127+
frequency: HertzU32,
128+
prescaler: u8,
129+
phantom: PhantomData<&'a Clocks>,
130+
}
131+
132+
impl<'a> PeripheralClockConfig<'a> {
133+
/// Get a clock configuration with the given prescaler.
134+
///
135+
/// With standard system clock configurations the input clock to the MCPWM
136+
/// peripheral is `160 MHz`.
137+
///
138+
/// The peripheral clock frequency is calculated as:
139+
/// `peripheral_clock = input_clock / (prescaler + 1)`
140+
pub fn with_prescaler(clocks: &'a Clocks, prescaler: u8) -> Self {
141+
#[cfg(esp32)]
142+
let source_clock = clocks.pwm_clock;
143+
#[cfg(esp32s3)]
144+
let source_clock = clocks.crypto_pwm_clock;
145+
146+
PeripheralClockConfig {
147+
frequency: source_clock / (prescaler as u32 + 1),
148+
prescaler,
149+
phantom: PhantomData,
150+
}
151+
}
152+
153+
/// Get a clock configuration with the given frequency.
154+
///
155+
/// ### Note:
156+
/// This will try to select an appropriate prescaler for the
157+
/// [`PeripheralClockConfig::with_prescaler`] method.
158+
/// If the calculated prescaler is not in the range `0..u8::MAX`
159+
/// [`FrequencyError`] will be returned.
160+
///
161+
/// With standard system clock configurations the input clock to the MCPWM
162+
/// peripheral is `160 MHz`.
163+
///
164+
/// Only divisors of the input clock (`160 Mhz / 1`, `160 Mhz / 2`, ...,
165+
/// `160 Mhz / 256`) are representable exactly. Other target frequencies
166+
/// will be rounded up to the next divisor.
167+
pub fn with_frequency(
168+
clocks: &'a Clocks,
169+
target_freq: HertzU32,
170+
) -> Result<Self, FrequencyError> {
171+
#[cfg(esp32)]
172+
let source_clock = clocks.pwm_clock;
173+
#[cfg(esp32s3)]
174+
let source_clock = clocks.crypto_pwm_clock;
175+
176+
if target_freq.raw() == 0 || target_freq > source_clock {
177+
return Err(FrequencyError);
178+
}
179+
let prescaler = source_clock / target_freq - 1;
180+
if prescaler > u8::MAX as u32 {
181+
return Err(FrequencyError);
182+
}
183+
Ok(Self::with_prescaler(clocks, prescaler as u8))
184+
}
185+
186+
/// Get the peripheral clock frequency.
187+
///
188+
/// ### Note:
189+
/// The actual value is rounded down to the nearest `u32` value
190+
pub fn frequency(&self) -> HertzU32 {
191+
self.frequency
192+
}
193+
194+
/// Get a timer clock configuration with the given prescaler.
195+
///
196+
/// The resulting timer frequency depends of the chosen
197+
/// [`timer::PwmWorkingMode`].
198+
///
199+
/// #### `PwmWorkingMode::Increase` or `PwmWorkingMode::Decrease`
200+
/// `timer_frequency = peripheral_clock / (prescaler + 1) / (period + 1)`
201+
/// #### `PwmWorkingMode::UpDown`
202+
/// `timer_frequency = peripheral_clock / (prescaler + 1) / (2 * period)`
203+
pub fn timer_clock_with_prescaler(
204+
&self,
205+
period: u16,
206+
mode: timer::PwmWorkingMode,
207+
prescaler: u8,
208+
) -> timer::TimerClockConfig<'a> {
209+
timer::TimerClockConfig::with_prescaler(self, period, mode, prescaler)
210+
}
211+
212+
/// Get a timer clock configuration with the given frequency.
213+
///
214+
/// ### Note:
215+
/// This will try to select an appropriate prescaler for the timer.
216+
/// If the calculated prescaler is not in the range `0..u8::MAX`
217+
/// [`FrequencyError`] will be returned.
218+
///
219+
/// See [`PeripheralClockConfig::timer_clock_with_prescaler`] for how the
220+
/// frequency is calculated.
221+
pub fn timer_clock_with_frequency(
222+
&self,
223+
period: u16,
224+
mode: timer::PwmWorkingMode,
225+
target_freq: HertzU32,
226+
) -> Result<timer::TimerClockConfig<'a>, FrequencyError> {
227+
timer::TimerClockConfig::with_frequency(self, period, mode, target_freq)
228+
}
229+
}
230+
231+
/// Target frequency could not be set.
232+
/// Check how the frequency is calculated in the corresponding method docs.
233+
#[derive(Copy, Clone, Debug)]
234+
pub struct FrequencyError;
235+
236+
/// A MCPWM peripheral
237+
pub unsafe trait PwmPeripheral: Deref<Target = crate::pac::pwm0::RegisterBlock> {
238+
/// Enable peripheral
239+
fn enable(system: &mut PeripheralClockControl);
240+
/// Get a pointer to the peripheral RegisterBlock
241+
fn block() -> *const crate::pac::pwm0::RegisterBlock;
242+
/// Get operator GPIO mux output signal
243+
fn output_signal<const OP: u8, const IS_A: bool>() -> OutputSignal;
244+
}
245+
246+
unsafe impl PwmPeripheral for crate::pac::PWM0 {
247+
fn enable(system: &mut PeripheralClockControl) {
248+
system.enable(Peripheral::Mcpwm0)
249+
}
250+
251+
fn block() -> *const crate::pac::pwm0::RegisterBlock {
252+
Self::ptr()
253+
}
254+
255+
fn output_signal<const OP: u8, const IS_A: bool>() -> OutputSignal {
256+
match (OP, IS_A) {
257+
(0, true) => OutputSignal::PWM0_0A,
258+
(1, true) => OutputSignal::PWM0_1A,
259+
(2, true) => OutputSignal::PWM0_1A,
260+
(0, false) => OutputSignal::PWM0_0B,
261+
(1, false) => OutputSignal::PWM0_1B,
262+
(2, false) => OutputSignal::PWM0_1B,
263+
_ => unreachable!(),
264+
}
265+
}
266+
}
267+
268+
unsafe impl PwmPeripheral for crate::pac::PWM1 {
269+
fn enable(system: &mut PeripheralClockControl) {
270+
system.enable(Peripheral::Mcpwm1)
271+
}
272+
273+
fn block() -> *const crate::pac::pwm0::RegisterBlock {
274+
Self::ptr()
275+
}
276+
277+
fn output_signal<const OP: u8, const IS_A: bool>() -> OutputSignal {
278+
match (OP, IS_A) {
279+
(0, true) => OutputSignal::PWM1_0A,
280+
(1, true) => OutputSignal::PWM1_1A,
281+
(2, true) => OutputSignal::PWM1_1A,
282+
(0, false) => OutputSignal::PWM1_0B,
283+
(1, false) => OutputSignal::PWM1_1B,
284+
(2, false) => OutputSignal::PWM1_1B,
285+
_ => unreachable!(),
286+
}
287+
}
288+
}

0 commit comments

Comments
 (0)