Merge pull request #255 from dimpolo/mcpwm

MCPWM MVP implementation
This commit is contained in:
Björn Quentin 2022-12-01 08:34:16 +01:00 committed by GitHub
commit c46719e112
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 1116 additions and 1 deletions

View File

@ -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"]

View File

@ -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
}
@ -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,
}
}
}
@ -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
}
@ -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),
},
}
}
@ -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),
},
}
}
@ -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),
},
}
}
@ -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),
},
}
}

View File

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

View File

@ -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!(),
}
}
}

View File

@ -0,0 +1,380 @@
use core::marker::PhantomData;
use crate::{
mcpwm::{timer::Timer, PwmPeripheral},
OutputPin,
};
/// A MCPWM operator
///
/// The PWM Operator submodule has the following functions:
/// * Generates a PWM signal pair, based on timing references obtained from the
/// corresponding PWM timer.
/// * Each signal out of the PWM signal pair includes a specific pattern of dead
/// time. (Not yet implemented)
/// * Superimposes a carrier on the PWM signal, if configured to do so. (Not yet
/// implemented)
/// * Handles response under fault conditions. (Not yet implemented)
pub struct Operator<const OP: u8, PWM> {
phantom: PhantomData<PWM>,
}
impl<const OP: u8, PWM: PwmPeripheral> Operator<OP, PWM> {
pub(super) fn new() -> Self {
// Side note:
// It would have been nice to deselect any timer reference on peripheral
// initialization.
// However experimentation (ESP32-S3) showed that writing `3` to timersel
// will not disable the timer reference but instead act as though `2` was
// written.
Operator {
phantom: PhantomData,
}
}
/// Select a [`Timer`] to be the timing reference for this operator
///
/// ### Note:
/// By default TIMER0 is used
pub fn set_timer<const TIM: u8>(&mut self, timer: &Timer<TIM, PWM>) {
let _ = timer;
// SAFETY:
// We only write to our OPERATORx_TIMERSEL register
let block = unsafe { &*PWM::block() };
block.operator_timersel.modify(|_, w| match OP {
0 => w.operator0_timersel().variant(TIM),
1 => w.operator1_timersel().variant(TIM),
2 => w.operator2_timersel().variant(TIM),
_ => {
unreachable!()
}
});
}
/// Use the A output with the given pin and configuration
pub fn with_pin_a<Pin: OutputPin>(
self,
pin: Pin,
config: PwmPinConfig<true>,
) -> PwmPin<Pin, PWM, OP, true> {
PwmPin::new(pin, config)
}
/// Use the B output with the given pin and configuration
pub fn with_pin_b<Pin: OutputPin>(
self,
pin: Pin,
config: PwmPinConfig<false>,
) -> PwmPin<Pin, PWM, OP, false> {
PwmPin::new(pin, config)
}
/// Use both the A and the B output with the given pins and configurations
pub fn with_pins<PinA: OutputPin, PinB: OutputPin>(
self,
pin_a: PinA,
config_a: PwmPinConfig<true>,
pin_b: PinB,
config_b: PwmPinConfig<false>,
) -> (PwmPin<PinA, PWM, OP, true>, PwmPin<PinB, PWM, OP, false>) {
(PwmPin::new(pin_a, config_a), PwmPin::new(pin_b, config_b))
}
}
/// Configuration describing how the operator generates a signal on a connected
/// pin
pub struct PwmPinConfig<const IS_A: bool> {
actions: PwmActions<IS_A>,
update_method: PwmUpdateMethod,
}
impl<const IS_A: bool> PwmPinConfig<IS_A> {
/// A configuration using [`PwmActions::UP_ACTIVE_HIGH`] and
/// [`PwmUpdateMethod::SYNC_ON_ZERO`]
pub const UP_ACTIVE_HIGH: Self =
Self::new(PwmActions::UP_ACTIVE_HIGH, PwmUpdateMethod::SYNC_ON_ZERO);
/// A configuration using [`PwmActions::UP_DOWN_ACTIVE_HIGH`] and
/// [`PwmUpdateMethod::SYNC_ON_ZERO`]
pub const UP_DOWN_ACTIVE_HIGH: Self = Self::new(
PwmActions::UP_DOWN_ACTIVE_HIGH,
PwmUpdateMethod::SYNC_ON_ZERO,
);
/// Get a configuration using the given `PwmActions` and `PwmUpdateMethod`
pub const fn new(actions: PwmActions<IS_A>, update_method: PwmUpdateMethod) -> Self {
PwmPinConfig {
actions,
update_method,
}
}
}
/// A pin driven by an MCPWM operator
pub struct PwmPin<Pin, PWM, const OP: u8, const IS_A: bool> {
_pin: Pin,
phantom: PhantomData<PWM>,
}
impl<Pin: OutputPin, PWM: PwmPeripheral, const OP: u8, const IS_A: bool>
PwmPin<Pin, PWM, OP, IS_A>
{
fn new(mut pin: Pin, config: PwmPinConfig<IS_A>) -> Self {
let output_signal = PWM::output_signal::<OP, IS_A>();
pin.enable_output(true)
.connect_peripheral_to_output(output_signal);
let mut pin = PwmPin {
_pin: pin,
phantom: PhantomData,
};
pin.set_actions(config.actions);
pin.set_update_method(config.update_method);
pin
}
/// Configure what actions should be taken on timing events
pub fn set_actions(&mut self, value: PwmActions<IS_A>) {
// SAFETY:
// We only write to our GENx_x register
let block = unsafe { &*PWM::block() };
let bits = value.0;
// SAFETY:
// `bits` is a valid bit pattern
unsafe {
match (OP, IS_A) {
(0, true) => block.gen0_a.write(|w| w.bits(bits)),
(1, true) => block.gen1_a.write(|w| w.bits(bits)),
(2, true) => block.gen2_a.write(|w| w.bits(bits)),
(0, false) => block.gen0_b.write(|w| w.bits(bits)),
(1, false) => block.gen1_b.write(|w| w.bits(bits)),
(2, false) => block.gen2_b.write(|w| w.bits(bits)),
_ => unreachable!(),
}
}
}
/// Set how a new timestamp syncs with the timer
#[cfg(esp32)]
pub fn set_update_method(&mut self, update_method: PwmUpdateMethod) {
// SAFETY:
// We only write to our GENx_x_UPMETHOD register
let block = unsafe { &*PWM::block() };
let bits = update_method.0;
match (OP, IS_A) {
(0, true) => block
.gen0_stmp_cfg
.modify(|_, w| w.gen0_a_upmethod().variant(bits)),
(1, true) => block
.gen1_stmp_cfg
.modify(|_, w| w.gen1_a_upmethod().variant(bits)),
(2, true) => block
.gen2_stmp_cfg
.modify(|_, w| w.gen2_a_upmethod().variant(bits)),
(0, false) => block
.gen0_stmp_cfg
.modify(|_, w| w.gen0_b_upmethod().variant(bits)),
(1, false) => block
.gen1_stmp_cfg
.modify(|_, w| w.gen1_b_upmethod().variant(bits)),
(2, false) => block
.gen2_stmp_cfg
.modify(|_, w| w.gen2_b_upmethod().variant(bits)),
_ => {
unreachable!()
}
}
}
/// Set how a new timestamp syncs with the timer
#[cfg(esp32s3)]
pub fn set_update_method(&mut self, update_method: PwmUpdateMethod) {
// SAFETY:
// We only write to our GENx_x_UPMETHOD register
let block = unsafe { &*PWM::block() };
let bits = update_method.0;
match (OP, IS_A) {
(0, true) => block
.cmpr0_cfg
.modify(|_, w| w.cmpr0_a_upmethod().variant(bits)),
(1, true) => block
.cmpr1_cfg
.modify(|_, w| w.cmpr1_a_upmethod().variant(bits)),
(2, true) => block
.cmpr2_cfg
.modify(|_, w| w.cmpr2_a_upmethod().variant(bits)),
(0, false) => block
.cmpr0_cfg
.modify(|_, w| w.cmpr0_b_upmethod().variant(bits)),
(1, false) => block
.cmpr1_cfg
.modify(|_, w| w.cmpr1_b_upmethod().variant(bits)),
(2, false) => block
.cmpr2_cfg
.modify(|_, w| w.cmpr2_b_upmethod().variant(bits)),
_ => {
unreachable!()
}
}
}
/// Set how a new timestamp syncs with the timer.
/// The written value will take effect according to the set
/// [`PwmUpdateMethod`].
#[cfg(esp32)]
pub fn set_timestamp(&mut self, value: u16) {
// SAFETY:
// We only write to our GENx_TSTMP_x register
let block = unsafe { &*PWM::block() };
match (OP, IS_A) {
(0, true) => block.gen0_tstmp_a.write(|w| w.gen0_a().variant(value)),
(1, true) => block.gen1_tstmp_a.write(|w| w.gen1_a().variant(value)),
(2, true) => block.gen2_tstmp_a.write(|w| w.gen2_a().variant(value)),
(0, false) => block.gen0_tstmp_b.write(|w| w.gen0_b().variant(value)),
(1, false) => block.gen1_tstmp_b.write(|w| w.gen1_b().variant(value)),
(2, false) => block.gen2_tstmp_b.write(|w| w.gen2_b().variant(value)),
_ => {
unreachable!()
}
}
}
/// Write a new timestamp.
/// The written value will take effect according to the set
/// [`PwmUpdateMethod`].
#[cfg(esp32s3)]
pub fn set_timestamp(&mut self, value: u16) {
// SAFETY:
// We only write to our GENx_TSTMP_x register
let block = unsafe { &*PWM::block() };
match (OP, IS_A) {
(0, true) => block.cmpr0_value0.write(|w| w.cmpr0_a().variant(value)),
(1, true) => block.cmpr1_value0.write(|w| w.cmpr1_a().variant(value)),
(2, true) => block.cmpr2_value0.write(|w| w.cmpr2_a().variant(value)),
(0, false) => block.cmpr0_value1.write(|w| w.cmpr0_b().variant(value)),
(1, false) => block.cmpr1_value1.write(|w| w.cmpr1_b().variant(value)),
(2, false) => block.cmpr2_value1.write(|w| w.cmpr2_b().variant(value)),
_ => {
unreachable!()
}
}
}
}
/// An action the operator applies to an output
#[non_exhaustive]
#[repr(u32)]
pub enum UpdateAction {
/// Clear the output by setting it to a low level.
SetLow = 1,
/// Set the to a high level.
SetHigh = 2,
/// Change the current output level to the opposite value.
/// If it is currently pulled high, pull it low, or vice versa.
Toggle = 3,
}
/// Settings for what actions should be taken on timing events
///
/// ### Note:
/// The hardware supports using a timestamp A event to trigger an action on
/// output B or vice versa. For clearer ownership semantics this HAL does not
/// support such configurations.
pub struct PwmActions<const IS_A: bool>(u32);
impl<const IS_A: bool> PwmActions<IS_A> {
/// Using this setting together with a timer configured with
/// [`PwmWorkingMode::Increase`](super::timer::PwmWorkingMode::Increase)
/// will set the output high for a duration proportional to the set
/// timestamp.
pub const UP_ACTIVE_HIGH: Self = Self::empty()
.on_up_counting_timer_equals_zero(UpdateAction::SetHigh)
.on_up_counting_timer_equals_timestamp(UpdateAction::SetLow);
/// Using this setting together with a timer configured with
/// [`PwmWorkingMode::UpDown`](super::timer::PwmWorkingMode::UpDown) will
/// set the output high for a duration proportional to the set
/// timestamp.
pub const UP_DOWN_ACTIVE_HIGH: Self = Self::empty()
.on_down_counting_timer_equals_timestamp(UpdateAction::SetHigh)
.on_up_counting_timer_equals_timestamp(UpdateAction::SetLow);
/// `PwmActions` with no `UpdateAction`s set
pub const fn empty() -> Self {
PwmActions(0)
}
/// Choose an `UpdateAction` for an `UTEZ` event
pub const fn on_up_counting_timer_equals_zero(self, action: UpdateAction) -> Self {
self.with_value_at_offset(action as u32, 0)
}
/// Choose an `UpdateAction` for an `UTEP` event
pub const fn on_up_counting_timer_equals_period(self, action: UpdateAction) -> Self {
self.with_value_at_offset(action as u32, 2)
}
/// Choose an `UpdateAction` for an `UTEA`/`UTEB` event
pub const fn on_up_counting_timer_equals_timestamp(self, action: UpdateAction) -> Self {
match IS_A {
true => self.with_value_at_offset(action as u32, 4),
false => self.with_value_at_offset(action as u32, 6),
}
}
/// Choose an `UpdateAction` for an `DTEZ` event
pub const fn on_down_counting_timer_equals_zero(self, action: UpdateAction) -> Self {
self.with_value_at_offset(action as u32, 12)
}
/// Choose an `UpdateAction` for an `DTEP` event
pub const fn on_down_counting_timer_equals_period(self, action: UpdateAction) -> Self {
self.with_value_at_offset(action as u32, 14)
}
/// Choose an `UpdateAction` for an `DTEA`/`DTEB` event
pub const fn on_down_counting_timer_equals_timestamp(self, action: UpdateAction) -> Self {
match IS_A {
true => self.with_value_at_offset(action as u32, 16),
false => self.with_value_at_offset(action as u32, 18),
}
}
const fn with_value_at_offset(self, value: u32, offset: u32) -> Self {
let mask = !(0b11 << offset);
let value = (self.0 & mask) | (value << offset);
PwmActions(value)
}
}
/// Settings for when [`PwmPin::set_timestamp`] takes effect
///
/// Multiple syncing triggers can be set.
pub struct PwmUpdateMethod(u8);
impl PwmUpdateMethod {
/// New timestamp will be applied immediately
pub const SYNC_IMMEDIATLY: Self = Self::empty();
/// New timestamp will be applied when timer is equal to zero
pub const SYNC_ON_ZERO: Self = Self::empty().sync_on_timer_equals_zero();
/// New timestamp will be applied when timer is equal to period
pub const SYNC_ON_PERIOD: Self = Self::empty().sync_on_timer_equals_period();
/// `PwmUpdateMethod` with no sync triggers.
/// Corresponds to syncing immediately
pub const fn empty() -> Self {
PwmUpdateMethod(0)
}
/// Enable syncing new timestamp values when timer is equal to zero
pub const fn sync_on_timer_equals_zero(mut self) -> Self {
self.0 |= 0b0001;
self
}
/// Enable syncing new timestamp values when timer is equal to period
pub const fn sync_on_timer_equals_period(mut self) -> Self {
self.0 |= 0b0010;
self
}
}

View File

@ -0,0 +1,296 @@
use core::marker::PhantomData;
use fugit::HertzU32;
use crate::{
clock::Clocks,
mcpwm::{FrequencyError, PeripheralClockConfig, PwmPeripheral},
};
/// A MCPWM timer
///
/// Every timer of a particular [`MCPWM`](super::MCPWM) peripheral can be used
/// as a timing reference for every
/// [`Operator`](super::operator::Operator) of that peripheral
pub struct Timer<const TIM: u8, PWM> {
pub(super) phantom: PhantomData<PWM>,
}
impl<const TIM: u8, PWM: PwmPeripheral> Timer<TIM, PWM> {
pub(super) fn new() -> Self {
Timer {
phantom: PhantomData,
}
}
/// Apply the given timer configuration.
///
/// ### Note:
/// The prescalar and period configuration will be applied immediately and
/// before setting the [`PwmWorkingMode`].
/// If the timer is already running you might want to call [`Timer::stop`]
/// and/or [`Timer::set_counter`] first
/// (if the new period is larger than the current counter value this will
/// cause weird behavior).
///
/// The hardware supports writing these settings in sync with certain timer
/// events but this HAL does not expose these for now.
pub fn start(&mut self, timer_config: TimerClockConfig) {
// write prescaler and period with immediate update method
self.cfg0().write(|w| {
w.timer0_prescale()
.variant(timer_config.prescaler)
.timer0_period()
.variant(timer_config.period)
.timer0_period_upmethod()
.variant(0)
});
// set timer to continuously run and set the timer working mode
self.cfg1().write(|w| {
w.timer0_start()
.variant(2)
.timer0_mod()
.variant(timer_config.mode as u8)
});
}
/// Stop the timer in its current state
pub fn stop(&mut self) {
// freeze the timer
self.cfg1().write(|w| w.timer0_mod().variant(0));
}
/// Set the timer counter to the provided value
pub fn set_counter(&mut self, phase: u16, direction: CounterDirection) {
// SAFETY:
// We only write to our TIMERx_SYNC register
let block = unsafe { &*PWM::block() };
match TIM {
0 => {
let sw = block.timer0_sync.read().sw().bit_is_set();
block.timer0_sync.write(|w| {
w.timer0_phase_direction()
.variant(direction as u8 != 0)
.timer0_phase()
.variant(phase)
.sw()
.variant(!sw)
});
}
1 => {
let sw = block.timer1_sync.read().sw().bit_is_set();
block.timer1_sync.write(|w| {
w.timer1_phase_direction()
.variant(direction as u8 != 0)
.timer1_phase()
.variant(phase)
.sw()
.variant(!sw)
});
}
2 => {
let sw = block.timer2_sync.read().sw().bit_is_set();
block.timer2_sync.write(|w| {
w.timer2_phase_direction()
.variant(direction as u8 != 0)
.timer2_phase()
.variant(phase)
.sw()
.variant(!sw)
});
}
_ => {
unreachable!()
}
}
}
/// Read the counter value and counter direction of the timer
pub fn status(&self) -> (u16, CounterDirection) {
// SAFETY:
// We only read from our TIMERx_STATUS register
let block = unsafe { &*PWM::block() };
match TIM {
0 => {
let reg = block.timer0_status.read();
(
reg.timer0_value().bits(),
reg.timer0_direction().bit_is_set().into(),
)
}
1 => {
let reg = block.timer1_status.read();
(
reg.timer1_value().bits(),
reg.timer1_direction().bit_is_set().into(),
)
}
2 => {
let reg = block.timer2_status.read();
(
reg.timer2_value().bits(),
reg.timer2_direction().bit_is_set().into(),
)
}
_ => {
unreachable!()
}
}
}
fn cfg0(&mut self) -> &crate::pac::pwm0::TIMER0_CFG0 {
// SAFETY:
// We only grant access to our CFG0 register with the lifetime of &mut self
let block = unsafe { &*PWM::block() };
// SAFETY:
// The CFG0 registers are identical for all timers so we can pretend they're
// TIMER0_CFG0
match TIM {
0 => &block.timer0_cfg0,
1 => unsafe { &*(&block.timer1_cfg0 as *const _ as *const _) },
2 => unsafe { &*(&block.timer2_cfg0 as *const _ as *const _) },
_ => {
unreachable!()
}
}
}
fn cfg1(&mut self) -> &crate::pac::pwm0::TIMER0_CFG1 {
// SAFETY:
// We only grant access to our CFG1 register with the lifetime of &mut self
let block = unsafe { &*PWM::block() };
// SAFETY:
// The CFG1 registers are identical for all timers so we can pretend they're
// TIMER0_CFG1
match TIM {
0 => &block.timer0_cfg1,
1 => unsafe { &*(&block.timer1_cfg1 as *const _ as *const _) },
2 => unsafe { &*(&block.timer2_cfg1 as *const _ as *const _) },
_ => {
unreachable!()
}
}
}
}
/// Clock configuration of a MCPWM timer
///
/// Use [`PeripheralClockConfig::timer_clock_with_prescaler`](super::PeripheralClockConfig::timer_clock_with_prescaler) or
/// [`PeripheralClockConfig::timer_clock_with_frequency`](super::PeripheralClockConfig::timer_clock_with_frequency) to it.
#[derive(Copy, Clone)]
pub struct TimerClockConfig<'a> {
frequency: HertzU32,
period: u16,
prescaler: u8,
mode: PwmWorkingMode,
phantom: PhantomData<&'a Clocks>,
}
impl<'a> TimerClockConfig<'a> {
pub(super) fn with_prescaler(
clock: &PeripheralClockConfig<'a>,
period: u16,
mode: PwmWorkingMode,
prescaler: u8,
) -> Self {
let cycle_period = match mode {
PwmWorkingMode::Increase | PwmWorkingMode::Decrease => period as u32 + 1,
// The reference manual seems to provide an incorrect formula for UpDown
PwmWorkingMode::UpDown => period as u32 * 2,
};
let frequency = clock.frequency / (prescaler as u32 + 1) / cycle_period;
TimerClockConfig {
frequency,
prescaler,
period,
mode,
phantom: PhantomData,
}
}
pub(super) fn with_frequency(
clock: &PeripheralClockConfig<'a>,
period: u16,
mode: PwmWorkingMode,
target_freq: HertzU32,
) -> Result<Self, FrequencyError> {
let cycle_period = match mode {
PwmWorkingMode::Increase | PwmWorkingMode::Decrease => period as u32 + 1,
// The reference manual seems to provide an incorrect formula for UpDown
PwmWorkingMode::UpDown => period as u32 * 2,
};
let target_timer_frequency = target_freq
.raw()
.checked_mul(cycle_period)
.ok_or(FrequencyError)?;
if target_timer_frequency == 0 || target_freq > clock.frequency {
return Err(FrequencyError);
}
let prescaler = clock.frequency.raw() / target_timer_frequency - 1;
if prescaler > u8::MAX as u32 {
return Err(FrequencyError);
}
let frequency = clock.frequency / (prescaler + 1) / cycle_period;
Ok(TimerClockConfig {
frequency,
prescaler: prescaler as u8,
period,
mode,
phantom: PhantomData,
})
}
/// Get the timer clock frequency.
///
/// ### Note:
/// The actual value is rounded down to the nearest `u32` value
pub fn frequency(&self) -> HertzU32 {
self.frequency
}
}
/// PWM working mode
#[derive(Copy, Clone)]
#[repr(u8)]
pub enum PwmWorkingMode {
/// In this mode, the PWM timer increments from zero until reaching the
/// value configured in the period field. Once done, the PWM timer
/// returns to zero and starts increasing again. PWM period is equal to the
/// value of the period field + 1.
Increase = 1,
/// The PWM timer decrements to zero, starting from the value configured in
/// the period field. After reaching zero, it is set back to the period
/// value. Then it starts to decrement again. In this case, the PWM period
/// is also equal to the value of period field + 1.
Decrease = 2,
/// This is a combination of the two modes mentioned above. The PWM timer
/// starts increasing from zero until the period value is reached. Then,
/// the timer decreases back to zero. This pattern is then repeated. The
/// PWM period is the result of the value of the period field × 2.
UpDown = 3,
}
/// The direction the timer counter is changing
#[derive(Debug)]
#[repr(u8)]
pub enum CounterDirection {
/// The timer counter is increasing
Increasing = 0,
/// The timer counter is decreasing
Decreasing = 1,
}
impl From<bool> for CounterDirection {
fn from(bit: bool) -> Self {
match bit {
false => CounterDirection::Increasing,
true => CounterDirection::Decreasing,
}
}
}

View File

@ -0,0 +1,64 @@
//! 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`. (GPIO4)
#![no_std]
#![no_main]
use esp32_hal::{
clock::ClockControl,
gpio::IO,
mcpwm::{
{MCPWM, PeripheralClockConfig},
operator::PwmPinConfig,
timer::PwmWorkingMode,
},
pac::Peripherals,
prelude::*,
timer::TimerGroup,
Rtc,
};
use esp_backtrace as _;
use xtensa_lx_rt::entry;
#[entry]
fn main() -> ! {
let peripherals = Peripherals::take().unwrap();
let mut system = peripherals.DPORT.split();
let clocks = ClockControl::boot_defaults(system.clock_control).freeze();
let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks);
let mut wdt = timer_group0.wdt;
let mut rtc = Rtc::new(peripherals.RTC_CNTL);
// Disable watchdog timer
wdt.disable();
rtc.rwdt.disable();
let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);
let pin = io.pins.gpio4;
// 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);
loop {}
}

View File

@ -16,6 +16,7 @@ pub use esp_hal_common::{
interrupt,
ledc,
macros,
mcpwm,
pac,
prelude,
pulse_control,

View File

@ -0,0 +1,64 @@
//! 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`. (GPIO4)
#![no_std]
#![no_main]
use esp32s3_hal::{
clock::ClockControl,
gpio::IO,
mcpwm::{
{MCPWM, PeripheralClockConfig},
operator::PwmPinConfig,
timer::PwmWorkingMode,
},
pac::Peripherals,
prelude::*,
timer::TimerGroup,
Rtc,
};
use esp_backtrace as _;
use xtensa_lx_rt::entry;
#[entry]
fn main() -> ! {
let peripherals = Peripherals::take().unwrap();
let mut system = peripherals.SYSTEM.split();
let clocks = ClockControl::boot_defaults(system.clock_control).freeze();
let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks);
let mut wdt = timer_group0.wdt;
let mut rtc = Rtc::new(peripherals.RTC_CNTL);
// Disable watchdog timer
wdt.disable();
rtc.rwdt.disable();
let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);
let pin = io.pins.gpio4;
// 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);
loop {}
}

View File

@ -16,6 +16,7 @@ pub use esp_hal_common::{
interrupt,
ledc,
macros,
mcpwm,
otg_fs,
pac,
prelude,