mirror of
https://github.com/esp-rs/esp-hal.git
synced 2025-10-02 14:44:42 +00:00
commit
c46719e112
@ -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"]
|
||||
|
@ -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),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
288
esp-hal-common/src/mcpwm/mod.rs
Normal file
288
esp-hal-common/src/mcpwm/mod.rs
Normal 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!(),
|
||||
}
|
||||
}
|
||||
}
|
380
esp-hal-common/src/mcpwm/operator.rs
Normal file
380
esp-hal-common/src/mcpwm/operator.rs
Normal 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
|
||||
}
|
||||
}
|
296
esp-hal-common/src/mcpwm/timer.rs
Normal file
296
esp-hal-common/src/mcpwm/timer.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
64
esp32-hal/examples/mcpwm.rs
Normal file
64
esp32-hal/examples/mcpwm.rs
Normal 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 {}
|
||||
}
|
@ -16,6 +16,7 @@ pub use esp_hal_common::{
|
||||
interrupt,
|
||||
ledc,
|
||||
macros,
|
||||
mcpwm,
|
||||
pac,
|
||||
prelude,
|
||||
pulse_control,
|
||||
|
64
esp32s3-hal/examples/mcpwm.rs
Normal file
64
esp32s3-hal/examples/mcpwm.rs
Normal 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 {}
|
||||
}
|
@ -16,6 +16,7 @@ pub use esp_hal_common::{
|
||||
interrupt,
|
||||
ledc,
|
||||
macros,
|
||||
mcpwm,
|
||||
otg_fs,
|
||||
pac,
|
||||
prelude,
|
||||
|
Loading…
x
Reference in New Issue
Block a user