|
| 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