Skip to content

Commit

Permalink
Merge pull request #255 from dimpolo/mcpwm
Browse files Browse the repository at this point in the history
MCPWM MVP implementation
  • Loading branch information
bjoernQ authored Dec 1, 2022
2 parents 5d48e77 + 0a75d3c commit c46719e
Show file tree
Hide file tree
Showing 10 changed files with 1,116 additions and 1 deletion.
2 changes: 1 addition & 1 deletion esp-hal-common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ esp32 = { version = "0.16.0", features = ["critical-section"], optional = true
esp32c2 = { version = "0.5.1", features = ["critical-section"], optional = true }
esp32c3 = { version = "0.8.1", features = ["critical-section"], optional = true }
esp32s2 = { version = "0.6.0", features = ["critical-section"], optional = true }
esp32s3 = { version = "0.9.0", features = ["critical-section"], optional = true }
esp32s3 = { version = "0.10.0", features = ["critical-section"], optional = true }

[features]
esp32 = ["esp32/rt" , "xtensa", "xtensa-lx/esp32", "xtensa-lx-rt/esp32", "lock_api"]
Expand Down
19 changes: 19 additions & 0 deletions esp-hal-common/src/clock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ pub struct Clocks {
pub apb_clock: HertzU32,
pub xtal_clock: HertzU32,
pub i2c_clock: HertzU32,
#[cfg(esp32)]
pub pwm_clock: HertzU32,
#[cfg(esp32s3)]
pub crypto_pwm_clock: HertzU32,
// TODO chip specific additional ones as needed
}

Expand All @@ -129,6 +133,10 @@ impl Clocks {
apb_clock: raw_clocks.apb_clock,
xtal_clock: raw_clocks.xtal_clock,
i2c_clock: raw_clocks.i2c_clock,
#[cfg(esp32)]
pwm_clock: raw_clocks.pwm_clock,
#[cfg(esp32s3)]
crypto_pwm_clock: raw_clocks.crypto_pwm_clock,
}
}
}
Expand All @@ -139,6 +147,10 @@ pub struct RawClocks {
pub apb_clock: HertzU32,
pub xtal_clock: HertzU32,
pub i2c_clock: HertzU32,
#[cfg(esp32)]
pub pwm_clock: HertzU32,
#[cfg(esp32s3)]
pub crypto_pwm_clock: HertzU32,
// TODO chip specific additional ones as needed
}

Expand Down Expand Up @@ -172,6 +184,7 @@ impl ClockControl {
apb_clock: HertzU32::MHz(80),
xtal_clock: HertzU32::MHz(40),
i2c_clock: HertzU32::MHz(80),
pwm_clock: HertzU32::MHz(160),
},
}
}
Expand Down Expand Up @@ -200,6 +213,10 @@ impl ClockControl {
apb_clock: HertzU32::MHz(80),
xtal_clock: HertzU32::MHz(40),
i2c_clock: HertzU32::MHz(40),
// The docs are unclear here. pwm_clock seems to be tied to clocks.apb_clock
// while simultaneously being fixed at 160 MHz.
// Testing showed 160 MHz to be correct for current clock configurations.
pwm_clock: HertzU32::MHz(160),
},
}
}
Expand Down Expand Up @@ -344,6 +361,7 @@ impl ClockControl {
apb_clock: HertzU32::MHz(80),
xtal_clock: HertzU32::MHz(40),
i2c_clock: HertzU32::MHz(40),
crypto_pwm_clock: HertzU32::MHz(160),
},
}
}
Expand All @@ -360,6 +378,7 @@ impl ClockControl {
apb_clock: HertzU32::MHz(80),
xtal_clock: HertzU32::MHz(40),
i2c_clock: HertzU32::MHz(40),
crypto_pwm_clock: HertzU32::MHz(160),
},
}
}
Expand Down
2 changes: 2 additions & 0 deletions esp-hal-common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ pub mod i2c;
pub mod i2s;

pub mod ledc;
#[cfg(any(esp32, esp32s3))]
pub mod mcpwm;
#[cfg(usb_otg)]
pub mod otg_fs;
pub mod prelude;
Expand Down
288 changes: 288 additions & 0 deletions esp-hal-common/src/mcpwm/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
//! MCPWM (Motor Control Pulse Width Modulator) peripheral
//!
//! # Peripheral capabilities:
//! * PWM Timers 0, 1 and 2
//! * Every PWM timer has a dedicated 8-bit clock prescaler.
//! * The 16-bit counter in the PWM timer can work in count-up mode,
//! count-down mode or count-up-down mode.
//! * A hardware sync or software sync can trigger a reload on the PWM timer
//! with a phase register (Not yet implemented)
//! * PWM Operators 0, 1 and 2
//! * Every PWM operator has two PWM outputs: PWMxA and PWMxB. They can work
//! independently, in symmetric and asymmetric configuration.
//! * Software, asynchronously override control of PWM signals.
//! * Configurable dead-time on rising and falling edges; each set up
//! independently. (Not yet implemented)
//! * All events can trigger CPU interrupts. (Not yet implemented)
//! * Modulating of PWM output by high-frequency carrier signals, useful
//! when gate drivers are insulated with a transformer. (Not yet
//! implemented)
//! * Period, time stamps and important control registers have shadow
//! registers with flexible updating methods.
//! * Fault Detection Module (Not yet implemented)
//! * Capture Module (Not yet implemented)
//!
//! # Example
//! Uses timer0 and operator0 of the MCPWM0 peripheral to output a 50% duty
//! signal at 20 kHz. The signal will be output to the pin assigned to `pin`.
//!
//! ```
//! # use esp_hal_common::{mcpwm, prelude::*};
//! use mcpwm::{operator::PwmPinConfig, timer::PwmWorkingMode, PeripheralClockConfig, MCPWM};
//!
//! // initialize peripheral
//! let clock_cfg = PeripheralClockConfig::with_frequency(&clocks, 40u32.MHz()).unwrap();
//! let mut mcpwm = MCPWM::new(
//! peripherals.PWM0,
//! clock_cfg,
//! &mut system.peripheral_clock_control,
//! );
//!
//! // connect operator0 to timer0
//! mcpwm.operator0.set_timer(&mcpwm.timer0);
//! // connect operator0 to pin
//! let mut pwm_pin = mcpwm
//! .operator0
//! .with_pin_a(pin, PwmPinConfig::UP_ACTIVE_HIGH);
//!
//! // start timer with timestamp values in the range of 0..=99 and a frequency of 20 kHz
//! let timer_clock_cfg = clock_cfg
//! .timer_clock_with_frequency(99, PwmWorkingMode::Increase, 20u32.kHz())
//! .unwrap();
//! mcpwm.timer0.start(timer_clock_cfg);
//!
//! // pin will be high 50% of the time
//! pwm_pin.set_timestamp(50);
//! ```
#![deny(missing_docs)]

use core::{marker::PhantomData, ops::Deref};

use fugit::HertzU32;
use operator::Operator;
use timer::Timer;

use crate::{
clock::Clocks,
system::{Peripheral, PeripheralClockControl},
types::OutputSignal,
};

/// MCPWM operators
pub mod operator;
/// MCPWM timers
pub mod timer;

/// The MCPWM peripheral
#[non_exhaustive]
pub struct MCPWM<PWM> {
/// Timer0
pub timer0: Timer<0, PWM>,
/// Timer1
pub timer1: Timer<1, PWM>,
/// Timer2
pub timer2: Timer<2, PWM>,
/// Operator0
pub operator0: Operator<0, PWM>,
/// Operator1
pub operator1: Operator<1, PWM>,
/// Operator2
pub operator2: Operator<2, PWM>,
}

impl<PWM: PwmPeripheral> MCPWM<PWM> {
/// `pwm_clk = clocks.crypto_pwm_clock / (prescaler + 1)`
// clocks.crypto_pwm_clock normally is 160 MHz
pub fn new(
peripheral: PWM,
peripheral_clock: PeripheralClockConfig,
system: &mut PeripheralClockControl,
) -> Self {
let _ = peripheral;

PWM::enable(system);

// set prescaler
peripheral
.clk_cfg
.write(|w| w.clk_prescale().variant(peripheral_clock.prescaler));
// enable clock
peripheral.clk.write(|w| w.en().set_bit());

MCPWM {
timer0: Timer::new(),
timer1: Timer::new(),
timer2: Timer::new(),
operator0: Operator::new(),
operator1: Operator::new(),
operator2: Operator::new(),
}
}
}

/// Clock configuration of the MCPWM peripheral
#[derive(Copy, Clone)]
pub struct PeripheralClockConfig<'a> {
frequency: HertzU32,
prescaler: u8,
phantom: PhantomData<&'a Clocks>,
}

impl<'a> PeripheralClockConfig<'a> {
/// Get a clock configuration with the given prescaler.
///
/// With standard system clock configurations the input clock to the MCPWM
/// peripheral is `160 MHz`.
///
/// The peripheral clock frequency is calculated as:
/// `peripheral_clock = input_clock / (prescaler + 1)`
pub fn with_prescaler(clocks: &'a Clocks, prescaler: u8) -> Self {
#[cfg(esp32)]
let source_clock = clocks.pwm_clock;
#[cfg(esp32s3)]
let source_clock = clocks.crypto_pwm_clock;

PeripheralClockConfig {
frequency: source_clock / (prescaler as u32 + 1),
prescaler,
phantom: PhantomData,
}
}

/// Get a clock configuration with the given frequency.
///
/// ### Note:
/// This will try to select an appropriate prescaler for the
/// [`PeripheralClockConfig::with_prescaler`] method.
/// If the calculated prescaler is not in the range `0..u8::MAX`
/// [`FrequencyError`] will be returned.
///
/// With standard system clock configurations the input clock to the MCPWM
/// peripheral is `160 MHz`.
///
/// Only divisors of the input clock (`160 Mhz / 1`, `160 Mhz / 2`, ...,
/// `160 Mhz / 256`) are representable exactly. Other target frequencies
/// will be rounded up to the next divisor.
pub fn with_frequency(
clocks: &'a Clocks,
target_freq: HertzU32,
) -> Result<Self, FrequencyError> {
#[cfg(esp32)]
let source_clock = clocks.pwm_clock;
#[cfg(esp32s3)]
let source_clock = clocks.crypto_pwm_clock;

if target_freq.raw() == 0 || target_freq > source_clock {
return Err(FrequencyError);
}
let prescaler = source_clock / target_freq - 1;
if prescaler > u8::MAX as u32 {
return Err(FrequencyError);
}
Ok(Self::with_prescaler(clocks, prescaler as u8))
}

/// Get the peripheral clock frequency.
///
/// ### Note:
/// The actual value is rounded down to the nearest `u32` value
pub fn frequency(&self) -> HertzU32 {
self.frequency
}

/// Get a timer clock configuration with the given prescaler.
///
/// The resulting timer frequency depends of the chosen
/// [`timer::PwmWorkingMode`].
///
/// #### `PwmWorkingMode::Increase` or `PwmWorkingMode::Decrease`
/// `timer_frequency = peripheral_clock / (prescaler + 1) / (period + 1)`
/// #### `PwmWorkingMode::UpDown`
/// `timer_frequency = peripheral_clock / (prescaler + 1) / (2 * period)`
pub fn timer_clock_with_prescaler(
&self,
period: u16,
mode: timer::PwmWorkingMode,
prescaler: u8,
) -> timer::TimerClockConfig<'a> {
timer::TimerClockConfig::with_prescaler(self, period, mode, prescaler)
}

/// Get a timer clock configuration with the given frequency.
///
/// ### Note:
/// This will try to select an appropriate prescaler for the timer.
/// If the calculated prescaler is not in the range `0..u8::MAX`
/// [`FrequencyError`] will be returned.
///
/// See [`PeripheralClockConfig::timer_clock_with_prescaler`] for how the
/// frequency is calculated.
pub fn timer_clock_with_frequency(
&self,
period: u16,
mode: timer::PwmWorkingMode,
target_freq: HertzU32,
) -> Result<timer::TimerClockConfig<'a>, FrequencyError> {
timer::TimerClockConfig::with_frequency(self, period, mode, target_freq)
}
}

/// Target frequency could not be set.
/// Check how the frequency is calculated in the corresponding method docs.
#[derive(Copy, Clone, Debug)]
pub struct FrequencyError;

/// A MCPWM peripheral
pub unsafe trait PwmPeripheral: Deref<Target = crate::pac::pwm0::RegisterBlock> {
/// Enable peripheral
fn enable(system: &mut PeripheralClockControl);
/// Get a pointer to the peripheral RegisterBlock
fn block() -> *const crate::pac::pwm0::RegisterBlock;
/// Get operator GPIO mux output signal
fn output_signal<const OP: u8, const IS_A: bool>() -> OutputSignal;
}

unsafe impl PwmPeripheral for crate::pac::PWM0 {
fn enable(system: &mut PeripheralClockControl) {
system.enable(Peripheral::Mcpwm0)
}

fn block() -> *const crate::pac::pwm0::RegisterBlock {
Self::ptr()
}

fn output_signal<const OP: u8, const IS_A: bool>() -> OutputSignal {
match (OP, IS_A) {
(0, true) => OutputSignal::PWM0_0A,
(1, true) => OutputSignal::PWM0_1A,
(2, true) => OutputSignal::PWM0_1A,
(0, false) => OutputSignal::PWM0_0B,
(1, false) => OutputSignal::PWM0_1B,
(2, false) => OutputSignal::PWM0_1B,
_ => unreachable!(),
}
}
}

unsafe impl PwmPeripheral for crate::pac::PWM1 {
fn enable(system: &mut PeripheralClockControl) {
system.enable(Peripheral::Mcpwm1)
}

fn block() -> *const crate::pac::pwm0::RegisterBlock {
Self::ptr()
}

fn output_signal<const OP: u8, const IS_A: bool>() -> OutputSignal {
match (OP, IS_A) {
(0, true) => OutputSignal::PWM1_0A,
(1, true) => OutputSignal::PWM1_1A,
(2, true) => OutputSignal::PWM1_1A,
(0, false) => OutputSignal::PWM1_0B,
(1, false) => OutputSignal::PWM1_1B,
(2, false) => OutputSignal::PWM1_1B,
_ => unreachable!(),
}
}
}
Loading

0 comments on commit c46719e

Please sign in to comment.