From 18c7ef780266968c0e91d8309162d72b81903cea Mon Sep 17 00:00:00 2001 From: dimi Date: Mon, 7 Nov 2022 02:23:53 +0100 Subject: [PATCH 01/12] WIP MCPWM implementation --- esp-hal-common/src/clock.rs | 10 ++ esp-hal-common/src/lib.rs | 2 + esp-hal-common/src/mcpwm/mod.rs | 175 +++++++++++++++++++++++ esp-hal-common/src/mcpwm/operator.rs | 201 +++++++++++++++++++++++++++ esp-hal-common/src/mcpwm/timer.rs | 148 ++++++++++++++++++++ esp32-hal/examples/mcpwm.rs | 64 +++++++++ esp32-hal/src/lib.rs | 1 + esp32s3-hal/examples/mcpwm.rs | 63 +++++++++ esp32s3-hal/src/lib.rs | 1 + 9 files changed, 665 insertions(+) create mode 100644 esp-hal-common/src/mcpwm/mod.rs create mode 100644 esp-hal-common/src/mcpwm/operator.rs create mode 100644 esp-hal-common/src/mcpwm/timer.rs create mode 100644 esp32-hal/examples/mcpwm.rs create mode 100644 esp32s3-hal/examples/mcpwm.rs diff --git a/esp-hal-common/src/clock.rs b/esp-hal-common/src/clock.rs index 9031e092fd6..3cf8e11414e 100644 --- a/esp-hal-common/src/clock.rs +++ b/esp-hal-common/src/clock.rs @@ -113,6 +113,8 @@ pub struct Clocks { pub apb_clock: HertzU32, pub xtal_clock: HertzU32, pub i2c_clock: HertzU32, + #[cfg(esp32s3)] + pub crypto_pwm_clock: HertzU32, // TODO chip specific additional ones as needed } @@ -129,6 +131,8 @@ impl Clocks { apb_clock: raw_clocks.apb_clock, xtal_clock: raw_clocks.xtal_clock, i2c_clock: raw_clocks.i2c_clock, + #[cfg(esp32s3)] + crypto_pwm_clock: raw_clocks.crypto_pwm_clock, } } } @@ -139,6 +143,8 @@ pub struct RawClocks { pub apb_clock: HertzU32, pub xtal_clock: HertzU32, pub i2c_clock: HertzU32, + #[cfg(esp32s3)] + pub crypto_pwm_clock: HertzU32, // TODO chip specific additional ones as needed } @@ -344,6 +350,8 @@ impl ClockControl { apb_clock: HertzU32::MHz(80), xtal_clock: HertzU32::MHz(40), i2c_clock: HertzU32::MHz(40), + #[cfg(esp32s3)] + crypto_pwm_clock: HertzU32::MHz(160), }, } } @@ -360,6 +368,8 @@ impl ClockControl { apb_clock: HertzU32::MHz(80), xtal_clock: HertzU32::MHz(40), i2c_clock: HertzU32::MHz(40), + #[cfg(esp32s3)] + crypto_pwm_clock: HertzU32::MHz(160), }, } } diff --git a/esp-hal-common/src/lib.rs b/esp-hal-common/src/lib.rs index aedc9493879..40354fabc16 100644 --- a/esp-hal-common/src/lib.rs +++ b/esp-hal-common/src/lib.rs @@ -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; diff --git a/esp-hal-common/src/mcpwm/mod.rs b/esp-hal-common/src/mcpwm/mod.rs new file mode 100644 index 00000000000..5d0f2874219 --- /dev/null +++ b/esp-hal-common/src/mcpwm/mod.rs @@ -0,0 +1,175 @@ +use core::marker::PhantomData; + +use fugit::HertzU32; +use operator::Operator; +use timer::Timer; + +use crate::{ + clock::Clocks, + system::{Peripheral, PeripheralClockControl}, + types::OutputSignal, +}; + +pub mod operator; +pub mod timer; + +// TODO provide a getter +pub struct PwmClock(HertzU32); + +pub struct MCPWM<'a, PWM> { + pub timer0: Timer<0, PWM>, + pub timer1: Timer<1, PWM>, + pub timer2: Timer<2, PWM>, + pub operator0: Operator<0, PWM>, + pub operator1: Operator<1, PWM>, + pub operator2: Operator<2, PWM>, + pub pwm_clk: PwmClock, + phantom: PhantomData<(&'a Clocks, PWM)>, +} + +impl<'a, PWM: PwmPeripheral> MCPWM<'a, PWM> { + /// `pwm_clk = clocks.crypto_pwm_clock / (prescaler + 1)` + // clocks.crypto_pwm_clock normally is 160 MHz + pub fn new( + peripheral: PWM, + clocks: &'a Clocks, + prescaler: u8, + system: &mut PeripheralClockControl, + ) -> Self { + let _ = peripheral; + + PWM::enable(system); + + let block = unsafe { &*PWM::block() }; + // set prescaler + block.clk_cfg.write(|w| w.clk_prescale().variant(prescaler)); + // enable clock + block.clk.write(|w| w.en().set_bit()); + + // sync comparator updates when timer counter is equal to zero. + Self::set_comparator_update_method(block, 0b0001); + + #[cfg(esp32)] + // TODO Docs are unclear here, need to test this + let pwm_clk = PwmClock(clocks.apb_clock / (prescaler as u32 + 1)); + #[cfg(esp32s3)] + let pwm_clk = PwmClock(clocks.crypto_pwm_clock / (prescaler as u32 + 1)); + + MCPWM { + timer0: Timer::new(), + timer1: Timer::new(), + timer2: Timer::new(), + operator0: Operator::new(), + operator1: Operator::new(), + operator2: Operator::new(), + pwm_clk, + phantom: PhantomData, + } + } + + pub fn timer_freq(&self, mode: timer::PwmWorkingMode, prescaler: u8, period: u16) -> HertzU32 { + let period = match mode { + timer::PwmWorkingMode::Increase | timer::PwmWorkingMode::Decrease => period as u32 + 1, + timer::PwmWorkingMode::UpDown => period as u32 * 2, + }; + self.pwm_clk.0 / (prescaler as u32 + 1) / period + } + + #[cfg(esp32)] + fn set_comparator_update_method(block: &crate::pac::pwm0::RegisterBlock, bits: u8) { + block.gen0_stmp_cfg.write(|w| { + w.gen0_a_upmethod() + .variant(bits) + .gen0_b_upmethod() + .variant(bits) + }); + block.gen1_stmp_cfg.write(|w| { + w.gen1_a_upmethod() + .variant(bits) + .gen1_b_upmethod() + .variant(bits) + }); + block.gen2_stmp_cfg.write(|w| { + w.gen2_a_upmethod() + .variant(bits) + .gen2_b_upmethod() + .variant(bits) + }); + } + + #[cfg(esp32s3)] + fn set_comparator_update_method(block: &crate::pac::pwm0::RegisterBlock, bits: u8) { + block.cmpr0_cfg.write(|w| { + w.cmpr0_a_upmethod() + .variant(bits) + .cmpr0_b_upmethod() + .variant(bits) + }); + block.cmpr1_cfg.write(|w| { + w.cmpr1_a_upmethod() + .variant(bits) + .cmpr1_b_upmethod() + .variant(bits) + }); + block.cmpr2_cfg.write(|w| { + w.cmpr2_a_upmethod() + .variant(bits) + .cmpr2_b_upmethod() + .variant(bits) + }); + } +} + +/// A MCPWM peripheral +pub unsafe trait PwmPeripheral { + /// 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() -> 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() -> OutputSignal { + match (O, 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() -> OutputSignal { + match (O, 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!(), + } + } +} diff --git a/esp-hal-common/src/mcpwm/operator.rs b/esp-hal-common/src/mcpwm/operator.rs new file mode 100644 index 00000000000..e21c03918c0 --- /dev/null +++ b/esp-hal-common/src/mcpwm/operator.rs @@ -0,0 +1,201 @@ +use core::marker::PhantomData; + +use crate::{ + mcpwm::{timer::Timer, PwmPeripheral}, + OutputPin, +}; + +pub struct Operator { + phantom: PhantomData, +} + +impl Operator { + pub(super) fn new() -> Self { + // TODO maybe set timersel to 3 to disable? + + Operator { + phantom: PhantomData, + } + } + + /// Select which [`Timer`] is the timing reference for this operator + /// + /// ### Note: + /// By default TIMER0 is used + pub fn set_timer(&mut self, timer: &Timer) { + let _ = timer; + let block = unsafe { &*PWM::block() }; + block.operator_timersel.modify(|_, w| match O { + 0 => w.operator0_timersel().variant(T), + 1 => w.operator1_timersel().variant(T), + 2 => w.operator2_timersel().variant(T), + _ => { + unreachable!() + } + }); + } + + pub fn with_a_pin( + self, + pin: Pin, + actions: PwmActions, + ) -> PwmPin { + let mut pin = PwmPin::new(pin); + pin.set_actions(actions); + pin + } + pub fn with_b_pin( + self, + pin: Pin, + actions: PwmActions, + ) -> PwmPin { + let mut pin = PwmPin::new(pin); + pin.set_actions(actions); + pin + } + pub fn with_pins( + self, + pin_a: PinA, + actions_a: PwmActions, + pin_b: PinB, + actions_b: PwmActions, + ) -> (PwmPin, PwmPin) { + let self2 = Self::new(); + ( + self.with_a_pin(pin_a, actions_a), + self2.with_b_pin(pin_b, actions_b), + ) + } +} + +pub struct PwmPin { + _pin: Pin, + phantom: PhantomData, +} + +impl PwmPin { + fn new(mut pin: Pin) -> Self { + let output_signal = PWM::output_signal::(); + pin.enable_output(true) + .connect_peripheral_to_output(output_signal); + PwmPin { + _pin: pin, + phantom: PhantomData, + } + } + + // TODO mention that using A on B and vice versa is not supported + // TODO should this be public? + pub fn set_actions(&mut self, value: PwmActions) { + // SAFETY: + // We only grant access to our GENx_x register with the lifetime of &mut self + let block = unsafe { &*PWM::block() }; + + let bits = value.0; + + // SAFETY: + // `bits` is a valid bit pattern + unsafe { + match (O, 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!(), + } + } + } + + #[cfg(esp32)] + pub fn set_timestamp(&mut self, value: u16) { + let block = unsafe { &*PWM::block() }; + unsafe { + match (O, IS_A) { + (0, true) => &block.gen0_tstmp_a.write(|w| w.gen0_a().bits(value)), + (1, true) => &block.gen1_tstmp_a.write(|w| w.gen1_a().bits(value)), + (2, true) => &block.gen2_tstmp_a.write(|w| w.gen2_a().bits(value)), + (0, false) => &block.gen0_tstmp_b.write(|w| w.gen0_b().bits(value)), + (1, false) => &block.gen1_tstmp_b.write(|w| w.gen1_b().bits(value)), + (2, false) => &block.gen2_tstmp_b.write(|w| w.gen2_b().bits(value)), + _ => { + unreachable!() + } + } + }; + } + + #[cfg(esp32s3)] + pub fn set_timestamp(&mut self, value: u16) { + let block = unsafe { &*PWM::block() }; + unsafe { + match (O, IS_A) { + (0, true) => &block.cmpr0_value0.write(|w| w.cmpr0_a().bits(value)), + (1, true) => &block.cmpr1_value0.write(|w| w.cmpr1_a().bits(value)), + (2, true) => &block.cmpr2_value0.write(|w| w.cmpr2_a().bits(value)), + (0, false) => &block.cmpr0_value1.write(|w| w.cmpr0_b().bits(value)), + (1, false) => &block.cmpr1_value1.write(|w| w.cmpr1_b().bits(value)), + (2, false) => &block.cmpr2_value1.write(|w| w.cmpr2_b().bits(value)), + _ => { + unreachable!() + } + } + }; + } +} + +#[repr(u32)] +pub enum UpdateAction { + None = 0, + SetLow = 1, + SetHigh = 2, + Toggle = 3, +} + +pub struct PwmActions(u32); + +impl PwmActions { + pub const UP_ACTIVE_HIGH: Self = Self::empty() + .on_up_counting_timer_equals_zero(UpdateAction::SetHigh) + .on_up_counting_timer_equals_timestamp(UpdateAction::SetLow); + + 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); + + pub const fn empty() -> Self { + PwmActions(0) + } + pub const fn on_up_counting_timer_equals_zero(self, action: UpdateAction) -> Self { + self.with_value_at_offset(action as u32, 0) + } + pub const fn on_up_counting_timer_equals_period(self, action: UpdateAction) -> Self { + self.with_value_at_offset(action as u32, 2) + } + 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), + } + } + + pub const fn on_down_counting_timer_equals_zero(self, action: UpdateAction) -> Self { + self.with_value_at_offset(action as u32, 12) + } + pub const fn on_down_counting_timer_equals_period(self, action: UpdateAction) -> Self { + self.with_value_at_offset(action as u32, 14) + } + 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) + } +} diff --git a/esp-hal-common/src/mcpwm/timer.rs b/esp-hal-common/src/mcpwm/timer.rs new file mode 100644 index 00000000000..73d759caee3 --- /dev/null +++ b/esp-hal-common/src/mcpwm/timer.rs @@ -0,0 +1,148 @@ +use core::marker::PhantomData; + +use crate::mcpwm::PwmPeripheral; + +/// PWM working mode +#[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 + 1). + UpDown = 3, +} + +pub struct Timer { + pub(super) phantom: PhantomData, +} + +impl Timer { + pub(super) fn new() -> Self { + Timer { + phantom: PhantomData, + } + } + + /// Set the prescaler, period and then the mode + /// + /// ### Note: + /// `prescaler` and `period` will be applied immediately. + /// If the timer is already running you might want to call [`stop`] first. + /// + /// Note also that 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, prescaler: u8, period: u16, mode: PwmWorkingMode) { + // write prescaler and period with immediate update method + unsafe { + self.cfg0().write(|w| { + w.timer0_prescale() + .bits(prescaler) + .timer0_period() + .bits(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(mode as u8)); + } + + pub fn stop(&mut self) { + // freeze the timer + self.cfg1().write(|w| w.timer0_mod().variant(0)); + } + + pub fn status(&self) -> (u16, CounterDirection) { + let block = unsafe { &*PWM::block() }; + + match T { + 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 T { + 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 T { + 0 => &block.timer0_cfg1, + 1 => unsafe { &*(&block.timer1_cfg1 as *const _ as *const _) }, + 2 => unsafe { &*(&block.timer2_cfg1 as *const _ as *const _) }, + _ => { + unreachable!() + } + } + } +} + +#[derive(Debug)] +pub enum CounterDirection { + Increasing, + Decreasing, +} + +impl From for CounterDirection { + fn from(bit: bool) -> Self { + match bit { + false => CounterDirection::Increasing, + true => CounterDirection::Decreasing, + } + } +} diff --git a/esp32-hal/examples/mcpwm.rs b/esp32-hal/examples/mcpwm.rs new file mode 100644 index 00000000000..6a2e462b979 --- /dev/null +++ b/esp32-hal/examples/mcpwm.rs @@ -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, + operator::PwmActions, + 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; + + // TODO check timing + // clocks.crypto_pwm_clock is 160 MHz + // with `prescaler = 3` pwm_clk will run at `160 MHz / (3 + 1) = 40 MHz` + let mut mcpwm = MCPWM::new( + peripherals.PWM0, + &clocks, + 3, + &mut system.peripheral_clock_control, + ); + + // with `prescaler = 19` timer0 counter will run at `40 MHz / (19 + 1) = 2 MHz` + // with `period = 99` timer0 will run at `2 MHz / (99 + 1) = 20 kHz` + mcpwm.timer0.start(19, 99, PwmWorkingMode::Increase); + mcpwm.operator0.set_timer(&mcpwm.timer0); + + let mut pwm_pin = mcpwm + .operator0 + .with_a_pin(pin, PwmActions::UP_ACTIVE_HIGH); + + // pin will be high 50% of the time + pwm_pin.set_timestamp(50); + + loop {} +} diff --git a/esp32-hal/src/lib.rs b/esp32-hal/src/lib.rs index 70be3d5496a..d521b4f099f 100644 --- a/esp32-hal/src/lib.rs +++ b/esp32-hal/src/lib.rs @@ -14,6 +14,7 @@ pub use esp_hal_common::{ interrupt, ledc, macros, + mcpwm, pac, prelude, pulse_control, diff --git a/esp32s3-hal/examples/mcpwm.rs b/esp32s3-hal/examples/mcpwm.rs new file mode 100644 index 00000000000..bfc0f4c5534 --- /dev/null +++ b/esp32s3-hal/examples/mcpwm.rs @@ -0,0 +1,63 @@ +//! 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, + operator::PwmActions, + 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; + + // clocks.crypto_pwm_clock is 160 MHz + // with `prescaler = 3` pwm_clk will run at `160 MHz / (3 + 1) = 40 MHz` + let mut mcpwm = MCPWM::new( + peripherals.PWM0, + &clocks, + 3, + &mut system.peripheral_clock_control, + ); + + // with `prescaler = 19` timer0 counter will run at `40 MHz / (19 + 1) = 2 MHz` + // with `period = 99` timer0 will run at `2 MHz / (99 + 1) = 20 kHz` + mcpwm.timer0.start(19, 99, PwmWorkingMode::Increase); + mcpwm.operator0.set_timer(&mcpwm.timer0); + + let mut pwm_pin = mcpwm + .operator0 + .with_a_pin(pin, PwmActions::UP_ACTIVE_HIGH); + + // pin will be high 50% of the time + pwm_pin.set_timestamp(50); + + loop {} +} diff --git a/esp32s3-hal/src/lib.rs b/esp32s3-hal/src/lib.rs index fd161de7632..ecfb3a71028 100644 --- a/esp32s3-hal/src/lib.rs +++ b/esp32s3-hal/src/lib.rs @@ -15,6 +15,7 @@ pub use esp_hal_common::{ interrupt, ledc, macros, + mcpwm, otg_fs, pac, prelude, From 6d2b8a671819ea5952eb92ea4d9995b458c5adc4 Mon Sep 17 00:00:00 2001 From: dimi Date: Tue, 8 Nov 2022 00:32:50 +0100 Subject: [PATCH 02/12] PwmClock changes --- esp-hal-common/src/mcpwm/mod.rs | 67 +++++++++++++++++++++---------- esp-hal-common/src/mcpwm/timer.rs | 2 +- 2 files changed, 47 insertions(+), 22 deletions(-) diff --git a/esp-hal-common/src/mcpwm/mod.rs b/esp-hal-common/src/mcpwm/mod.rs index 5d0f2874219..5a539b10b6e 100644 --- a/esp-hal-common/src/mcpwm/mod.rs +++ b/esp-hal-common/src/mcpwm/mod.rs @@ -13,18 +13,15 @@ use crate::{ pub mod operator; pub mod timer; -// TODO provide a getter -pub struct PwmClock(HertzU32); - +#[non_exhaustive] pub struct MCPWM<'a, PWM> { + pub pwm_clk: PwmClock<'a>, pub timer0: Timer<0, PWM>, pub timer1: Timer<1, PWM>, pub timer2: Timer<2, PWM>, pub operator0: Operator<0, PWM>, pub operator1: Operator<1, PWM>, pub operator2: Operator<2, PWM>, - pub pwm_clk: PwmClock, - phantom: PhantomData<(&'a Clocks, PWM)>, } impl<'a, PWM: PwmPeripheral> MCPWM<'a, PWM> { @@ -49,32 +46,17 @@ impl<'a, PWM: PwmPeripheral> MCPWM<'a, PWM> { // sync comparator updates when timer counter is equal to zero. Self::set_comparator_update_method(block, 0b0001); - #[cfg(esp32)] - // TODO Docs are unclear here, need to test this - let pwm_clk = PwmClock(clocks.apb_clock / (prescaler as u32 + 1)); - #[cfg(esp32s3)] - let pwm_clk = PwmClock(clocks.crypto_pwm_clock / (prescaler as u32 + 1)); - MCPWM { + pwm_clk: PwmClock::new(clocks, prescaler), timer0: Timer::new(), timer1: Timer::new(), timer2: Timer::new(), operator0: Operator::new(), operator1: Operator::new(), operator2: Operator::new(), - pwm_clk, - phantom: PhantomData, } } - pub fn timer_freq(&self, mode: timer::PwmWorkingMode, prescaler: u8, period: u16) -> HertzU32 { - let period = match mode { - timer::PwmWorkingMode::Increase | timer::PwmWorkingMode::Decrease => period as u32 + 1, - timer::PwmWorkingMode::UpDown => period as u32 * 2, - }; - self.pwm_clk.0 / (prescaler as u32 + 1) / period - } - #[cfg(esp32)] fn set_comparator_update_method(block: &crate::pac::pwm0::RegisterBlock, bits: u8) { block.gen0_stmp_cfg.write(|w| { @@ -120,6 +102,49 @@ impl<'a, PWM: PwmPeripheral> MCPWM<'a, PWM> { } } +pub struct PwmClock<'a> { + pwm_clock: HertzU32, + phantom: PhantomData<&'a Clocks>, +} + +impl<'a> PwmClock<'a> { + pub fn clock_frequency(&self) -> HertzU32 { + self.pwm_clock + } + + pub fn timer_frequency( + &self, + mode: timer::PwmWorkingMode, + prescaler: u8, + period: u16, + ) -> HertzU32 { + let period = match mode { + timer::PwmWorkingMode::Increase | timer::PwmWorkingMode::Decrease => period as u32 + 1, + // The reference manual seems to provide an incorrect formula for UpDown + timer::PwmWorkingMode::UpDown => period as u32 * 2, + }; + self.pwm_clock / (prescaler as u32 + 1) / period + } + + #[cfg(esp32)] + fn new(clocks: &'a Clocks, prescaler: u8) -> Self { + // TODO Docs are unclear here, need to test this + + PwmClock { + pwm_clock: clocks.apb_clock / (prescaler as u32 + 1), + phantom: PhantomData, + } + } + + #[cfg(esp32s3)] + fn new(clocks: &'a Clocks, prescaler: u8) -> Self { + PwmClock { + pwm_clock: clocks.crypto_pwm_clock / (prescaler as u32 + 1), + phantom: PhantomData, + } + } +} + /// A MCPWM peripheral pub unsafe trait PwmPeripheral { /// Enable peripheral diff --git a/esp-hal-common/src/mcpwm/timer.rs b/esp-hal-common/src/mcpwm/timer.rs index 73d759caee3..f28aadd8061 100644 --- a/esp-hal-common/src/mcpwm/timer.rs +++ b/esp-hal-common/src/mcpwm/timer.rs @@ -18,7 +18,7 @@ pub enum PwmWorkingMode { /// 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 + 1). + /// PWM period is the result of the value of the period field × 2. UpDown = 3, } From 408243d7e155e104318ddf47ba5305ed85f380cc Mon Sep 17 00:00:00 2001 From: dimi Date: Tue, 8 Nov 2022 13:16:11 +0100 Subject: [PATCH 03/12] improve mcpwm::operator, add `PwmPinConfig` --- esp-hal-common/src/mcpwm/mod.rs | 47 -------- esp-hal-common/src/mcpwm/operator.rs | 155 +++++++++++++++++++++------ esp32-hal/examples/mcpwm.rs | 4 +- esp32s3-hal/examples/mcpwm.rs | 4 +- 4 files changed, 127 insertions(+), 83 deletions(-) diff --git a/esp-hal-common/src/mcpwm/mod.rs b/esp-hal-common/src/mcpwm/mod.rs index 5a539b10b6e..46194b62ec2 100644 --- a/esp-hal-common/src/mcpwm/mod.rs +++ b/esp-hal-common/src/mcpwm/mod.rs @@ -43,9 +43,6 @@ impl<'a, PWM: PwmPeripheral> MCPWM<'a, PWM> { // enable clock block.clk.write(|w| w.en().set_bit()); - // sync comparator updates when timer counter is equal to zero. - Self::set_comparator_update_method(block, 0b0001); - MCPWM { pwm_clk: PwmClock::new(clocks, prescaler), timer0: Timer::new(), @@ -56,50 +53,6 @@ impl<'a, PWM: PwmPeripheral> MCPWM<'a, PWM> { operator2: Operator::new(), } } - - #[cfg(esp32)] - fn set_comparator_update_method(block: &crate::pac::pwm0::RegisterBlock, bits: u8) { - block.gen0_stmp_cfg.write(|w| { - w.gen0_a_upmethod() - .variant(bits) - .gen0_b_upmethod() - .variant(bits) - }); - block.gen1_stmp_cfg.write(|w| { - w.gen1_a_upmethod() - .variant(bits) - .gen1_b_upmethod() - .variant(bits) - }); - block.gen2_stmp_cfg.write(|w| { - w.gen2_a_upmethod() - .variant(bits) - .gen2_b_upmethod() - .variant(bits) - }); - } - - #[cfg(esp32s3)] - fn set_comparator_update_method(block: &crate::pac::pwm0::RegisterBlock, bits: u8) { - block.cmpr0_cfg.write(|w| { - w.cmpr0_a_upmethod() - .variant(bits) - .cmpr0_b_upmethod() - .variant(bits) - }); - block.cmpr1_cfg.write(|w| { - w.cmpr1_a_upmethod() - .variant(bits) - .cmpr1_b_upmethod() - .variant(bits) - }); - block.cmpr2_cfg.write(|w| { - w.cmpr2_a_upmethod() - .variant(bits) - .cmpr2_b_upmethod() - .variant(bits) - }); - } } pub struct PwmClock<'a> { diff --git a/esp-hal-common/src/mcpwm/operator.rs b/esp-hal-common/src/mcpwm/operator.rs index e21c03918c0..ff6e5d1a5fd 100644 --- a/esp-hal-common/src/mcpwm/operator.rs +++ b/esp-hal-common/src/mcpwm/operator.rs @@ -35,36 +35,49 @@ impl Operator { }); } - pub fn with_a_pin( + pub fn with_pin_a( self, pin: Pin, - actions: PwmActions, + config: PwmPinConfig, ) -> PwmPin { - let mut pin = PwmPin::new(pin); - pin.set_actions(actions); - pin + PwmPin::new(pin, config) } - pub fn with_b_pin( + pub fn with_pin_b( self, pin: Pin, - actions: PwmActions, + config: PwmPinConfig, ) -> PwmPin { - let mut pin = PwmPin::new(pin); - pin.set_actions(actions); - pin + PwmPin::new(pin, config) } pub fn with_pins( self, pin_a: PinA, - actions_a: PwmActions, + config_a: PwmPinConfig, pin_b: PinB, - actions_b: PwmActions, + config_b: PwmPinConfig, ) -> (PwmPin, PwmPin) { - let self2 = Self::new(); - ( - self.with_a_pin(pin_a, actions_a), - self2.with_b_pin(pin_b, actions_b), - ) + (PwmPin::new(pin_a, config_a), PwmPin::new(pin_b, config_b)) + } +} + +pub struct PwmPinConfig { + actions: PwmActions, + update_method: PwmUpdateMethod, +} + +impl PwmPinConfig { + pub const UP_ACTIVE_HIGH: Self = + Self::new(PwmActions::UP_ACTIVE_HIGH, PwmUpdateMethod::SYNC_ON_ZERO); + pub const UP_DOWN_ACTIVE_HIGH: Self = Self::new( + PwmActions::UP_DOWN_ACTIVE_HIGH, + PwmUpdateMethod::SYNC_ON_ZERO, + ); + + pub const fn new(actions: PwmActions, update_method: PwmUpdateMethod) -> Self { + PwmPinConfig { + actions, + update_method, + } } } @@ -74,14 +87,17 @@ pub struct PwmPin { } impl PwmPin { - fn new(mut pin: Pin) -> Self { + fn new(mut pin: Pin, config: PwmPinConfig) -> Self { let output_signal = PWM::output_signal::(); pin.enable_output(true) .connect_peripheral_to_output(output_signal); - PwmPin { + let mut pin = PwmPin { _pin: pin, phantom: PhantomData, - } + }; + pin.set_actions(config.actions); + pin.set_update_method(config.update_method); + pin } // TODO mention that using A on B and vice versa is not supported @@ -108,22 +124,78 @@ impl PwmPin

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!() + } + } + } + + #[cfg(esp32s3)] + pub fn set_update_method(&mut self, update_method: PwmUpdateMethod) { + let block = unsafe { &*PWM::block() }; + let bits = update_method.0; + match (O, 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!() + } + } + } + #[cfg(esp32)] pub fn set_timestamp(&mut self, value: u16) { let block = unsafe { &*PWM::block() }; - unsafe { - match (O, IS_A) { - (0, true) => &block.gen0_tstmp_a.write(|w| w.gen0_a().bits(value)), - (1, true) => &block.gen1_tstmp_a.write(|w| w.gen1_a().bits(value)), - (2, true) => &block.gen2_tstmp_a.write(|w| w.gen2_a().bits(value)), - (0, false) => &block.gen0_tstmp_b.write(|w| w.gen0_b().bits(value)), - (1, false) => &block.gen1_tstmp_b.write(|w| w.gen1_b().bits(value)), - (2, false) => &block.gen2_tstmp_b.write(|w| w.gen2_b().bits(value)), - _ => { - unreachable!() - } + match (O, 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!() } - }; + } } #[cfg(esp32s3)] @@ -199,3 +271,22 @@ impl PwmActions { PwmActions(value) } } + +pub struct PwmUpdateMethod(u8); + +impl PwmUpdateMethod { + pub const SYNC_ON_ZERO: Self = Self::empty().sync_on_timer_equals_zero(); + pub const SYNC_ON_PERIOD: Self = Self::empty().sync_on_timer_equals_period(); + + pub const fn empty() -> Self { + PwmUpdateMethod(0) + } + pub const fn sync_on_timer_equals_zero(mut self) -> Self { + self.0 |= 0b0001; + self + } + pub const fn sync_on_timer_equals_period(mut self) -> Self { + self.0 |= 0b0010; + self + } +} diff --git a/esp32-hal/examples/mcpwm.rs b/esp32-hal/examples/mcpwm.rs index 6a2e462b979..41cc744b4a3 100644 --- a/esp32-hal/examples/mcpwm.rs +++ b/esp32-hal/examples/mcpwm.rs @@ -10,7 +10,7 @@ use esp32_hal::{ gpio::IO, mcpwm::{ MCPWM, - operator::PwmActions, + operator::PwmPinConfig, timer::PwmWorkingMode, }, pac::Peripherals, @@ -55,7 +55,7 @@ fn main() -> ! { let mut pwm_pin = mcpwm .operator0 - .with_a_pin(pin, PwmActions::UP_ACTIVE_HIGH); + .with_pin_a(pin, PwmPinConfig::UP_ACTIVE_HIGH); // pin will be high 50% of the time pwm_pin.set_timestamp(50); diff --git a/esp32s3-hal/examples/mcpwm.rs b/esp32s3-hal/examples/mcpwm.rs index bfc0f4c5534..ad379cd28c1 100644 --- a/esp32s3-hal/examples/mcpwm.rs +++ b/esp32s3-hal/examples/mcpwm.rs @@ -10,7 +10,7 @@ use esp32s3_hal::{ gpio::IO, mcpwm::{ MCPWM, - operator::PwmActions, + operator::PwmPinConfig, timer::PwmWorkingMode, }, pac::Peripherals, @@ -54,7 +54,7 @@ fn main() -> ! { let mut pwm_pin = mcpwm .operator0 - .with_a_pin(pin, PwmActions::UP_ACTIVE_HIGH); + .with_pin_a(pin, PwmPinConfig::UP_ACTIVE_HIGH); // pin will be high 50% of the time pwm_pin.set_timestamp(50); From 4327506bb29a2dd60dce33953d85cfba63136204 Mon Sep 17 00:00:00 2001 From: dimi Date: Tue, 8 Nov 2022 15:01:33 +0100 Subject: [PATCH 04/12] use Clocks.pwm_clock on ESP32 --- esp-hal-common/src/clock.rs | 13 +++++++++++-- esp-hal-common/src/mcpwm/mod.rs | 4 +--- esp32-hal/examples/mcpwm.rs | 3 +-- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/esp-hal-common/src/clock.rs b/esp-hal-common/src/clock.rs index 3cf8e11414e..6049990ffed 100644 --- a/esp-hal-common/src/clock.rs +++ b/esp-hal-common/src/clock.rs @@ -113,6 +113,8 @@ 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 @@ -131,6 +133,8 @@ 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, } @@ -143,6 +147,8 @@ 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 @@ -178,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), }, } } @@ -206,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), }, } } @@ -350,7 +361,6 @@ impl ClockControl { apb_clock: HertzU32::MHz(80), xtal_clock: HertzU32::MHz(40), i2c_clock: HertzU32::MHz(40), - #[cfg(esp32s3)] crypto_pwm_clock: HertzU32::MHz(160), }, } @@ -368,7 +378,6 @@ impl ClockControl { apb_clock: HertzU32::MHz(80), xtal_clock: HertzU32::MHz(40), i2c_clock: HertzU32::MHz(40), - #[cfg(esp32s3)] crypto_pwm_clock: HertzU32::MHz(160), }, } diff --git a/esp-hal-common/src/mcpwm/mod.rs b/esp-hal-common/src/mcpwm/mod.rs index 46194b62ec2..a2565902de1 100644 --- a/esp-hal-common/src/mcpwm/mod.rs +++ b/esp-hal-common/src/mcpwm/mod.rs @@ -81,10 +81,8 @@ impl<'a> PwmClock<'a> { #[cfg(esp32)] fn new(clocks: &'a Clocks, prescaler: u8) -> Self { - // TODO Docs are unclear here, need to test this - PwmClock { - pwm_clock: clocks.apb_clock / (prescaler as u32 + 1), + pwm_clock: clocks.pwm_clock / (prescaler as u32 + 1), phantom: PhantomData, } } diff --git a/esp32-hal/examples/mcpwm.rs b/esp32-hal/examples/mcpwm.rs index 41cc744b4a3..60ee4dfbc93 100644 --- a/esp32-hal/examples/mcpwm.rs +++ b/esp32-hal/examples/mcpwm.rs @@ -38,8 +38,7 @@ fn main() -> ! { let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); let pin = io.pins.gpio4; - // TODO check timing - // clocks.crypto_pwm_clock is 160 MHz + // clocks.pwm_clock is 160 MHz // with `prescaler = 3` pwm_clk will run at `160 MHz / (3 + 1) = 40 MHz` let mut mcpwm = MCPWM::new( peripherals.PWM0, From 2d813134751ab827bd0a70b885fe49ff4aa1e28c Mon Sep 17 00:00:00 2001 From: dimi Date: Tue, 8 Nov 2022 20:00:17 +0100 Subject: [PATCH 05/12] add PeripheralClockConfig and TimerClockConfig --- esp-hal-common/src/mcpwm/mod.rs | 92 ++++++++++++++++++++----------- esp-hal-common/src/mcpwm/timer.rs | 92 +++++++++++++++++++++++++++++-- esp32-hal/examples/mcpwm.rs | 19 ++++--- esp32s3-hal/examples/mcpwm.rs | 19 ++++--- 4 files changed, 165 insertions(+), 57 deletions(-) diff --git a/esp-hal-common/src/mcpwm/mod.rs b/esp-hal-common/src/mcpwm/mod.rs index a2565902de1..3c72f59b954 100644 --- a/esp-hal-common/src/mcpwm/mod.rs +++ b/esp-hal-common/src/mcpwm/mod.rs @@ -15,7 +15,7 @@ pub mod timer; #[non_exhaustive] pub struct MCPWM<'a, PWM> { - pub pwm_clk: PwmClock<'a>, + pub peripheral_clock: PeripheralClockConfig<'a>, pub timer0: Timer<0, PWM>, pub timer1: Timer<1, PWM>, pub timer2: Timer<2, PWM>, @@ -29,8 +29,7 @@ impl<'a, PWM: PwmPeripheral> MCPWM<'a, PWM> { // clocks.crypto_pwm_clock normally is 160 MHz pub fn new( peripheral: PWM, - clocks: &'a Clocks, - prescaler: u8, + peripheral_clock: PeripheralClockConfig<'a>, system: &mut PeripheralClockControl, ) -> Self { let _ = peripheral; @@ -39,12 +38,14 @@ impl<'a, PWM: PwmPeripheral> MCPWM<'a, PWM> { let block = unsafe { &*PWM::block() }; // set prescaler - block.clk_cfg.write(|w| w.clk_prescale().variant(prescaler)); + block + .clk_cfg + .write(|w| w.clk_prescale().variant(peripheral_clock.prescaler)); // enable clock block.clk.write(|w| w.en().set_bit()); MCPWM { - pwm_clk: PwmClock::new(clocks, prescaler), + peripheral_clock, timer0: Timer::new(), timer1: Timer::new(), timer2: Timer::new(), @@ -55,47 +56,72 @@ impl<'a, PWM: PwmPeripheral> MCPWM<'a, PWM> { } } -pub struct PwmClock<'a> { - pwm_clock: HertzU32, +#[derive(Copy, Clone)] +pub struct PeripheralClockConfig<'a> { + frequency: HertzU32, + prescaler: u8, phantom: PhantomData<&'a Clocks>, } -impl<'a> PwmClock<'a> { - pub fn clock_frequency(&self) -> HertzU32 { - self.pwm_clock +impl<'a> PeripheralClockConfig<'a> { + 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, + } + } + + pub fn with_frequency( + clocks: &'a Clocks, + target_freq: HertzU32, + ) -> Result { + #[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)) + } + + pub fn frequency(&self) -> HertzU32 { + self.frequency } - pub fn timer_frequency( + pub fn timer_clock_with_prescaler( &self, + period: u16, mode: timer::PwmWorkingMode, prescaler: u8, - period: u16, - ) -> HertzU32 { - let period = match mode { - timer::PwmWorkingMode::Increase | timer::PwmWorkingMode::Decrease => period as u32 + 1, - // The reference manual seems to provide an incorrect formula for UpDown - timer::PwmWorkingMode::UpDown => period as u32 * 2, - }; - self.pwm_clock / (prescaler as u32 + 1) / period - } - - #[cfg(esp32)] - fn new(clocks: &'a Clocks, prescaler: u8) -> Self { - PwmClock { - pwm_clock: clocks.pwm_clock / (prescaler as u32 + 1), - phantom: PhantomData, - } + ) -> timer::TimerClockConfig<'a> { + timer::TimerClockConfig::with_prescaler(self, period, mode, prescaler) } - #[cfg(esp32s3)] - fn new(clocks: &'a Clocks, prescaler: u8) -> Self { - PwmClock { - pwm_clock: clocks.crypto_pwm_clock / (prescaler as u32 + 1), - phantom: PhantomData, - } + pub fn timer_clock_with_frequency( + &self, + period: u16, + mode: timer::PwmWorkingMode, + target_freq: HertzU32, + ) -> Result, FrequencyError> { + timer::TimerClockConfig::with_frequency(self, period, mode, target_freq) } } +#[derive(Debug)] +pub struct FrequencyError; + /// A MCPWM peripheral pub unsafe trait PwmPeripheral { /// Enable peripheral diff --git a/esp-hal-common/src/mcpwm/timer.rs b/esp-hal-common/src/mcpwm/timer.rs index f28aadd8061..837d1349345 100644 --- a/esp-hal-common/src/mcpwm/timer.rs +++ b/esp-hal-common/src/mcpwm/timer.rs @@ -1,8 +1,14 @@ use core::marker::PhantomData; -use crate::mcpwm::PwmPeripheral; +use fugit::HertzU32; + +use crate::{ + clock::Clocks, + mcpwm::{FrequencyError, PeripheralClockConfig, PwmPeripheral}, +}; /// PWM working mode +#[derive(Copy, Clone)] #[repr(u8)] pub enum PwmWorkingMode { /// In this mode, the PWM timer increments from zero until reaching the @@ -42,22 +48,26 @@ impl Timer { /// Note also that 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, prescaler: u8, period: u16, mode: PwmWorkingMode) { + pub fn start(&mut self, timer_clock: TimerClockConfig) { // write prescaler and period with immediate update method unsafe { self.cfg0().write(|w| { w.timer0_prescale() - .bits(prescaler) + .bits(timer_clock.prescaler) .timer0_period() - .bits(period) + .bits(timer_clock.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(mode as u8)); + self.cfg1().write(|w| { + w.timer0_start() + .variant(2) + .timer0_mod() + .variant(timer_clock.mode as u8) + }); } pub fn stop(&mut self) { @@ -132,6 +142,76 @@ impl Timer { } } +#[derive(Copy, Clone)] +pub struct TimerClockConfig<'a> { + frequency: HertzU32, + period: u16, + prescaler: u8, + mode: PwmWorkingMode, + phantom: PhantomData<&'a Clocks>, +} + +impl<'a> TimerClockConfig<'a> { + pub 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 fn with_frequency( + clock: &PeripheralClockConfig<'a>, + period: u16, + mode: PwmWorkingMode, + target_freq: HertzU32, + ) -> Result { + 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, + }) + } + + pub fn frequency(&self) -> HertzU32 { + self.frequency + } +} + #[derive(Debug)] pub enum CounterDirection { Increasing, diff --git a/esp32-hal/examples/mcpwm.rs b/esp32-hal/examples/mcpwm.rs index 60ee4dfbc93..1bc4caf1857 100644 --- a/esp32-hal/examples/mcpwm.rs +++ b/esp32-hal/examples/mcpwm.rs @@ -9,7 +9,7 @@ use esp32_hal::{ clock::ClockControl, gpio::IO, mcpwm::{ - MCPWM, + {MCPWM, PeripheralClockConfig}, operator::PwmPinConfig, timer::PwmWorkingMode, }, @@ -38,24 +38,25 @@ fn main() -> ! { let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); let pin = io.pins.gpio4; - // clocks.pwm_clock is 160 MHz - // with `prescaler = 3` pwm_clk will run at `160 MHz / (3 + 1) = 40 MHz` + // initialize peripheral + let clock_cfg = PeripheralClockConfig::with_frequency(&clocks, 40u32.MHz()).unwrap(); let mut mcpwm = MCPWM::new( peripherals.PWM0, - &clocks, - 3, + clock_cfg, &mut system.peripheral_clock_control, ); - // with `prescaler = 19` timer0 counter will run at `40 MHz / (19 + 1) = 2 MHz` - // with `period = 99` timer0 will run at `2 MHz / (99 + 1) = 20 kHz` - mcpwm.timer0.start(19, 99, PwmWorkingMode::Increase); + // 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); diff --git a/esp32s3-hal/examples/mcpwm.rs b/esp32s3-hal/examples/mcpwm.rs index ad379cd28c1..43e62c6b5c1 100644 --- a/esp32s3-hal/examples/mcpwm.rs +++ b/esp32s3-hal/examples/mcpwm.rs @@ -9,7 +9,7 @@ use esp32s3_hal::{ clock::ClockControl, gpio::IO, mcpwm::{ - MCPWM, + {MCPWM, PeripheralClockConfig}, operator::PwmPinConfig, timer::PwmWorkingMode, }, @@ -38,24 +38,25 @@ fn main() -> ! { let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); let pin = io.pins.gpio4; - // clocks.crypto_pwm_clock is 160 MHz - // with `prescaler = 3` pwm_clk will run at `160 MHz / (3 + 1) = 40 MHz` + // initialize peripheral + let clock_cfg = PeripheralClockConfig::with_frequency(&clocks, 40u32.MHz()).unwrap(); let mut mcpwm = MCPWM::new( peripherals.PWM0, - &clocks, - 3, + clock_cfg, &mut system.peripheral_clock_control, ); - // with `prescaler = 19` timer0 counter will run at `40 MHz / (19 + 1) = 2 MHz` - // with `period = 99` timer0 will run at `2 MHz / (99 + 1) = 20 kHz` - mcpwm.timer0.start(19, 99, PwmWorkingMode::Increase); + // 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); From 89535a26dd274da0af3d86b0fb09add98b3aa580 Mon Sep 17 00:00:00 2001 From: dimi Date: Fri, 11 Nov 2022 18:26:59 +0100 Subject: [PATCH 06/12] WIP docs --- esp-hal-common/src/mcpwm/mod.rs | 124 +++++++++++++++++++++++++-- esp-hal-common/src/mcpwm/operator.rs | 104 ++++++++++++++++++---- esp-hal-common/src/mcpwm/timer.rs | 104 +++++++++++++--------- 3 files changed, 269 insertions(+), 63 deletions(-) diff --git a/esp-hal-common/src/mcpwm/mod.rs b/esp-hal-common/src/mcpwm/mod.rs index 3c72f59b954..124b66d3221 100644 --- a/esp-hal-common/src/mcpwm/mod.rs +++ b/esp-hal-common/src/mcpwm/mod.rs @@ -1,3 +1,62 @@ +//! 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; use fugit::HertzU32; @@ -10,26 +69,34 @@ use crate::{ types::OutputSignal, }; +/// MCPWM operators pub mod operator; +/// MCPWM timers pub mod timer; +/// The MCPWM peripheral #[non_exhaustive] -pub struct MCPWM<'a, PWM> { - pub peripheral_clock: PeripheralClockConfig<'a>, +pub struct MCPWM { + /// 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<'a, PWM: PwmPeripheral> MCPWM<'a, PWM> { +impl MCPWM { /// `pwm_clk = clocks.crypto_pwm_clock / (prescaler + 1)` // clocks.crypto_pwm_clock normally is 160 MHz pub fn new( peripheral: PWM, - peripheral_clock: PeripheralClockConfig<'a>, + peripheral_clock: PeripheralClockConfig, system: &mut PeripheralClockControl, ) -> Self { let _ = peripheral; @@ -45,7 +112,6 @@ impl<'a, PWM: PwmPeripheral> MCPWM<'a, PWM> { block.clk.write(|w| w.en().set_bit()); MCPWM { - peripheral_clock, timer0: Timer::new(), timer1: Timer::new(), timer2: Timer::new(), @@ -56,6 +122,7 @@ impl<'a, PWM: PwmPeripheral> MCPWM<'a, PWM> { } } +/// Clock configuration of the MCPWM peripheral #[derive(Copy, Clone)] pub struct PeripheralClockConfig<'a> { frequency: HertzU32, @@ -64,6 +131,13 @@ pub struct PeripheralClockConfig<'a> { } 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; @@ -77,6 +151,20 @@ impl<'a> PeripheralClockConfig<'a> { } } + /// 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, @@ -96,10 +184,23 @@ impl<'a> PeripheralClockConfig<'a> { 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, @@ -109,6 +210,15 @@ impl<'a> PeripheralClockConfig<'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, @@ -119,7 +229,9 @@ impl<'a> PeripheralClockConfig<'a> { } } -#[derive(Debug)] +/// 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 diff --git a/esp-hal-common/src/mcpwm/operator.rs b/esp-hal-common/src/mcpwm/operator.rs index ff6e5d1a5fd..39199bb24ea 100644 --- a/esp-hal-common/src/mcpwm/operator.rs +++ b/esp-hal-common/src/mcpwm/operator.rs @@ -5,6 +5,16 @@ use crate::{ 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 { phantom: PhantomData, } @@ -18,7 +28,7 @@ impl Operator { } } - /// Select which [`Timer`] is the timing reference for this operator + /// Select a [`Timer`] to be the timing reference for this operator /// /// ### Note: /// By default TIMER0 is used @@ -35,6 +45,7 @@ impl Operator { }); } + /// Use the A output with the given pin and configuration pub fn with_pin_a( self, pin: Pin, @@ -42,6 +53,8 @@ impl Operator { ) -> PwmPin { PwmPin::new(pin, config) } + + /// Use the B output with the given pin and configuration pub fn with_pin_b( self, pin: Pin, @@ -49,6 +62,8 @@ impl Operator { ) -> PwmPin { PwmPin::new(pin, config) } + + /// Use both the A and the B output with the given pins and configurations pub fn with_pins( self, pin_a: PinA, @@ -60,19 +75,26 @@ impl Operator { } } +/// Configuration describing how the operator generates a signal on a connected +/// pin pub struct PwmPinConfig { actions: PwmActions, update_method: PwmUpdateMethod, } impl PwmPinConfig { + /// 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, update_method: PwmUpdateMethod) -> Self { PwmPinConfig { actions, @@ -81,6 +103,7 @@ impl PwmPinConfig { } } +/// A pin driven by an MCPWM operator pub struct PwmPin { _pin: Pin, phantom: PhantomData, @@ -100,8 +123,7 @@ impl PwmPin

) { // SAFETY: // We only grant access to our GENx_x register with the lifetime of &mut self @@ -124,6 +146,7 @@ impl PwmPin

PwmPin

PwmPin

PwmPin

&block.cmpr0_value0.write(|w| w.cmpr0_a().bits(value)), - (1, true) => &block.cmpr1_value0.write(|w| w.cmpr1_a().bits(value)), - (2, true) => &block.cmpr2_value0.write(|w| w.cmpr2_a().bits(value)), - (0, false) => &block.cmpr0_value1.write(|w| w.cmpr0_b().bits(value)), - (1, false) => &block.cmpr1_value1.write(|w| w.cmpr1_b().bits(value)), - (2, false) => &block.cmpr2_value1.write(|w| w.cmpr2_b().bits(value)), - _ => { - unreachable!() - } + match (O, 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 { - None = 0, + /// 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(u32); impl PwmActions { + /// 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), @@ -252,12 +306,17 @@ impl PwmActions { } } + /// 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), @@ -272,19 +331,32 @@ impl PwmActions { } } +/// 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 diff --git a/esp-hal-common/src/mcpwm/timer.rs b/esp-hal-common/src/mcpwm/timer.rs index 837d1349345..a0a7f3dbe34 100644 --- a/esp-hal-common/src/mcpwm/timer.rs +++ b/esp-hal-common/src/mcpwm/timer.rs @@ -7,27 +7,11 @@ use crate::{ mcpwm::{FrequencyError, PeripheralClockConfig, PwmPeripheral}, }; -/// 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, -} - +/// 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 { pub(super) phantom: PhantomData, } @@ -39,42 +23,48 @@ impl Timer { } } - /// Set the prescaler, period and then the mode + /// Apply the given timer configuration. /// /// ### Note: - /// `prescaler` and `period` will be applied immediately. - /// If the timer is already running you might want to call [`stop`] first. + /// The configuration will be applied immediately. + /// If the timer is already running you might want to call [`Timer::stop`] + /// and [`Timer::reset_counter`] first. /// - /// Note also that 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_clock: TimerClockConfig) { + /// 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 - unsafe { - self.cfg0().write(|w| { - w.timer0_prescale() - .bits(timer_clock.prescaler) - .timer0_period() - .bits(timer_clock.period) - .timer0_period_upmethod() - .variant(0) - }); - } + + 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_clock.mode as u8) + .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 0 + pub fn reset_counter(&mut self) { + todo!() + } + + /// Read the counter value and counter direction of the timer pub fn status(&self) -> (u16, CounterDirection) { let block = unsafe { &*PWM::block() }; @@ -142,6 +132,10 @@ impl Timer { } } +/// 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, @@ -152,7 +146,7 @@ pub struct TimerClockConfig<'a> { } impl<'a> TimerClockConfig<'a> { - pub fn with_prescaler( + pub(super) fn with_prescaler( clock: &PeripheralClockConfig<'a>, period: u16, mode: PwmWorkingMode, @@ -174,7 +168,7 @@ impl<'a> TimerClockConfig<'a> { } } - pub fn with_frequency( + pub(super) fn with_frequency( clock: &PeripheralClockConfig<'a>, period: u16, mode: PwmWorkingMode, @@ -207,14 +201,42 @@ impl<'a> TimerClockConfig<'a> { }) } + /// 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)] pub enum CounterDirection { + /// The timer counter is increasing Increasing, + /// The timer counter is decreasing Decreasing, } From f0876952b7805e29aa770e276462cc1e0b3d6547 Mon Sep 17 00:00:00 2001 From: dimi Date: Thu, 24 Nov 2022 22:09:54 +0100 Subject: [PATCH 07/12] implement Timer::set_counter --- esp-hal-common/src/mcpwm/timer.rs | 41 +++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/esp-hal-common/src/mcpwm/timer.rs b/esp-hal-common/src/mcpwm/timer.rs index a0a7f3dbe34..f39d3599aa3 100644 --- a/esp-hal-common/src/mcpwm/timer.rs +++ b/esp-hal-common/src/mcpwm/timer.rs @@ -26,15 +26,15 @@ impl Timer { /// Apply the given timer configuration. /// /// ### Note: - /// The configuration will be applied immediately. + /// 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 [`Timer::reset_counter`] first. + /// and/or [`Timer::set_counter`] first. /// /// 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) @@ -59,9 +59,33 @@ impl Timer { self.cfg1().write(|w| w.timer0_mod().variant(0)); } - /// Set the timer counter to 0 - pub fn reset_counter(&mut self) { - todo!() + /// Set the timer counter to the provided value + pub fn set_counter(&mut self, phase: u16) { + // FIXME add phase_direction to SVD, then add CounterDirection as a parameter + let block = unsafe { &*PWM::block() }; + match T { + 0 => block.timer0_sync.modify(|r, w| { + w.timer0_phase() + .variant(phase as u32) + .sw() + .variant(!r.sw().bit_is_set()) + }), + 1 => block.timer1_sync.modify(|r, w| { + w.timer1_phase() + .variant(phase as u32) + .sw() + .variant(!r.sw().bit_is_set()) + }), + 2 => block.timer2_sync.modify(|r, w| { + w.timer2_phase() + .variant(phase as u32) + .sw() + .variant(!r.sw().bit_is_set()) + }), + _ => { + unreachable!() + } + } } /// Read the counter value and counter direction of the timer @@ -233,11 +257,12 @@ pub enum PwmWorkingMode { /// The direction the timer counter is changing #[derive(Debug)] +#[repr(u8)] pub enum CounterDirection { /// The timer counter is increasing - Increasing, + Increasing = 0, /// The timer counter is decreasing - Decreasing, + Decreasing = 1, } impl From for CounterDirection { From ab74fc47fdb11479669950f24357c9dfa7d4d140 Mon Sep 17 00:00:00 2001 From: dimi Date: Thu, 24 Nov 2022 22:12:19 +0100 Subject: [PATCH 08/12] rename const generics --- esp-hal-common/src/mcpwm/mod.rs | 10 ++++---- esp-hal-common/src/mcpwm/operator.rs | 36 ++++++++++++++-------------- esp-hal-common/src/mcpwm/timer.rs | 12 +++++----- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/esp-hal-common/src/mcpwm/mod.rs b/esp-hal-common/src/mcpwm/mod.rs index 124b66d3221..541efeadd9e 100644 --- a/esp-hal-common/src/mcpwm/mod.rs +++ b/esp-hal-common/src/mcpwm/mod.rs @@ -241,7 +241,7 @@ pub unsafe trait PwmPeripheral { /// Get a pointer to the peripheral RegisterBlock fn block() -> *const crate::pac::pwm0::RegisterBlock; /// Get operator GPIO mux output signal - fn output_signal() -> OutputSignal; + fn output_signal() -> OutputSignal; } unsafe impl PwmPeripheral for crate::pac::PWM0 { @@ -253,8 +253,8 @@ unsafe impl PwmPeripheral for crate::pac::PWM0 { Self::ptr() } - fn output_signal() -> OutputSignal { - match (O, IS_A) { + fn output_signal() -> OutputSignal { + match (OP, IS_A) { (0, true) => OutputSignal::PWM0_0A, (1, true) => OutputSignal::PWM0_1A, (2, true) => OutputSignal::PWM0_1A, @@ -275,8 +275,8 @@ unsafe impl PwmPeripheral for crate::pac::PWM1 { Self::ptr() } - fn output_signal() -> OutputSignal { - match (O, IS_A) { + fn output_signal() -> OutputSignal { + match (OP, IS_A) { (0, true) => OutputSignal::PWM1_0A, (1, true) => OutputSignal::PWM1_1A, (2, true) => OutputSignal::PWM1_1A, diff --git a/esp-hal-common/src/mcpwm/operator.rs b/esp-hal-common/src/mcpwm/operator.rs index 39199bb24ea..8fc025b1ad5 100644 --- a/esp-hal-common/src/mcpwm/operator.rs +++ b/esp-hal-common/src/mcpwm/operator.rs @@ -15,11 +15,11 @@ use crate::{ /// * 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 { +pub struct Operator { phantom: PhantomData, } -impl Operator { +impl Operator { pub(super) fn new() -> Self { // TODO maybe set timersel to 3 to disable? @@ -32,13 +32,13 @@ impl Operator { /// /// ### Note: /// By default TIMER0 is used - pub fn set_timer(&mut self, timer: &Timer) { + pub fn set_timer(&mut self, timer: &Timer) { let _ = timer; let block = unsafe { &*PWM::block() }; - block.operator_timersel.modify(|_, w| match O { - 0 => w.operator0_timersel().variant(T), - 1 => w.operator1_timersel().variant(T), - 2 => w.operator2_timersel().variant(T), + 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!() } @@ -50,7 +50,7 @@ impl Operator { self, pin: Pin, config: PwmPinConfig, - ) -> PwmPin { + ) -> PwmPin { PwmPin::new(pin, config) } @@ -59,7 +59,7 @@ impl Operator { self, pin: Pin, config: PwmPinConfig, - ) -> PwmPin { + ) -> PwmPin { PwmPin::new(pin, config) } @@ -70,7 +70,7 @@ impl Operator { config_a: PwmPinConfig, pin_b: PinB, config_b: PwmPinConfig, - ) -> (PwmPin, PwmPin) { + ) -> (PwmPin, PwmPin) { (PwmPin::new(pin_a, config_a), PwmPin::new(pin_b, config_b)) } } @@ -104,14 +104,14 @@ impl PwmPinConfig { } /// A pin driven by an MCPWM operator -pub struct PwmPin { +pub struct PwmPin { _pin: Pin, phantom: PhantomData, } -impl PwmPin { +impl PwmPin { fn new(mut pin: Pin, config: PwmPinConfig) -> Self { - let output_signal = PWM::output_signal::(); + let output_signal = PWM::output_signal::(); pin.enable_output(true) .connect_peripheral_to_output(output_signal); let mut pin = PwmPin { @@ -134,7 +134,7 @@ impl PwmPin

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)), @@ -151,7 +151,7 @@ impl PwmPin

block .gen0_stmp_cfg .modify(|_, w| w.gen0_a_upmethod().variant(bits)), @@ -181,7 +181,7 @@ impl PwmPin

block .cmpr0_cfg .modify(|_, w| w.cmpr0_a_upmethod().variant(bits)), @@ -212,7 +212,7 @@ impl PwmPin

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)), @@ -231,7 +231,7 @@ impl PwmPin

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)), diff --git a/esp-hal-common/src/mcpwm/timer.rs b/esp-hal-common/src/mcpwm/timer.rs index f39d3599aa3..74db11ae5bf 100644 --- a/esp-hal-common/src/mcpwm/timer.rs +++ b/esp-hal-common/src/mcpwm/timer.rs @@ -12,11 +12,11 @@ use crate::{ /// 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 { +pub struct Timer { pub(super) phantom: PhantomData, } -impl Timer { +impl Timer { pub(super) fn new() -> Self { Timer { phantom: PhantomData, @@ -63,7 +63,7 @@ impl Timer { pub fn set_counter(&mut self, phase: u16) { // FIXME add phase_direction to SVD, then add CounterDirection as a parameter let block = unsafe { &*PWM::block() }; - match T { + match TIM { 0 => block.timer0_sync.modify(|r, w| { w.timer0_phase() .variant(phase as u32) @@ -92,7 +92,7 @@ impl Timer { pub fn status(&self) -> (u16, CounterDirection) { let block = unsafe { &*PWM::block() }; - match T { + match TIM { 0 => { let reg = block.timer0_status.read(); ( @@ -128,7 +128,7 @@ impl Timer { // SAFETY: // The CFG0 registers are identical for all timers so we can pretend they're // TIMER0_CFG0 - match T { + match TIM { 0 => &block.timer0_cfg0, 1 => unsafe { &*(&block.timer1_cfg0 as *const _ as *const _) }, 2 => unsafe { &*(&block.timer2_cfg0 as *const _ as *const _) }, @@ -145,7 +145,7 @@ impl Timer { // SAFETY: // The CFG1 registers are identical for all timers so we can pretend they're // TIMER0_CFG1 - match T { + match TIM { 0 => &block.timer0_cfg1, 1 => unsafe { &*(&block.timer1_cfg1 as *const _ as *const _) }, 2 => unsafe { &*(&block.timer2_cfg1 as *const _ as *const _) }, From 257664da7425ced61fd8299b1052d265950e024c Mon Sep 17 00:00:00 2001 From: dimi Date: Fri, 25 Nov 2022 02:29:23 +0100 Subject: [PATCH 09/12] add direction to Timer::set_counter --- esp-hal-common/src/mcpwm/timer.rs | 42 +++++++++++++++---------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/esp-hal-common/src/mcpwm/timer.rs b/esp-hal-common/src/mcpwm/timer.rs index 74db11ae5bf..e9623849e89 100644 --- a/esp-hal-common/src/mcpwm/timer.rs +++ b/esp-hal-common/src/mcpwm/timer.rs @@ -29,7 +29,9 @@ impl Timer { /// 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. + /// 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. @@ -60,28 +62,26 @@ impl Timer { } /// Set the timer counter to the provided value - pub fn set_counter(&mut self, phase: u16) { - // FIXME add phase_direction to SVD, then add CounterDirection as a parameter + pub fn set_counter(&mut self, phase: u16, direction: CounterDirection) { let block = unsafe { &*PWM::block() }; + + // FIXME replace with safe API after https://github.com/esp-rs/esp-pacs/issues/57 match TIM { - 0 => block.timer0_sync.modify(|r, w| { - w.timer0_phase() - .variant(phase as u32) - .sw() - .variant(!r.sw().bit_is_set()) - }), - 1 => block.timer1_sync.modify(|r, w| { - w.timer1_phase() - .variant(phase as u32) - .sw() - .variant(!r.sw().bit_is_set()) - }), - 2 => block.timer2_sync.modify(|r, w| { - w.timer2_phase() - .variant(phase as u32) - .sw() - .variant(!r.sw().bit_is_set()) - }), + 0 => { + let sw = block.timer0_sync.read().sw().bit_is_set(); + let bits = ((direction as u32) << 20) + ((phase as u32) << 4) + ((!sw as u32) << 1); + unsafe { block.timer0_sync.write(|w| w.bits(bits)) } + } + 1 => { + let sw = block.timer1_sync.read().sw().bit_is_set(); + let bits = ((direction as u32) << 20) + ((phase as u32) << 4) + ((!sw as u32) << 1); + unsafe { block.timer1_sync.write(|w| w.bits(bits)) } + } + 2 => { + let sw = block.timer2_sync.read().sw().bit_is_set(); + let bits = ((direction as u32) << 20) + ((phase as u32) << 4) + ((!sw as u32) << 1); + unsafe { block.timer2_sync.write(|w| w.bits(bits)) } + } _ => { unreachable!() } From e1a812bc5954c746d1c3c1359ef059b9643722a5 Mon Sep 17 00:00:00 2001 From: dimi Date: Fri, 25 Nov 2022 02:31:02 +0100 Subject: [PATCH 10/12] add SAFETY comments --- esp-hal-common/src/mcpwm/mod.rs | 9 ++++----- esp-hal-common/src/mcpwm/operator.rs | 16 ++++++++++++++-- esp-hal-common/src/mcpwm/timer.rs | 4 ++++ 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/esp-hal-common/src/mcpwm/mod.rs b/esp-hal-common/src/mcpwm/mod.rs index 541efeadd9e..03f379f4229 100644 --- a/esp-hal-common/src/mcpwm/mod.rs +++ b/esp-hal-common/src/mcpwm/mod.rs @@ -57,7 +57,7 @@ #![deny(missing_docs)] -use core::marker::PhantomData; +use core::{marker::PhantomData, ops::Deref}; use fugit::HertzU32; use operator::Operator; @@ -103,13 +103,12 @@ impl MCPWM { PWM::enable(system); - let block = unsafe { &*PWM::block() }; // set prescaler - block + peripheral .clk_cfg .write(|w| w.clk_prescale().variant(peripheral_clock.prescaler)); // enable clock - block.clk.write(|w| w.en().set_bit()); + peripheral.clk.write(|w| w.en().set_bit()); MCPWM { timer0: Timer::new(), @@ -235,7 +234,7 @@ impl<'a> PeripheralClockConfig<'a> { pub struct FrequencyError; /// A MCPWM peripheral -pub unsafe trait PwmPeripheral { +pub unsafe trait PwmPeripheral: Deref { /// Enable peripheral fn enable(system: &mut PeripheralClockControl); /// Get a pointer to the peripheral RegisterBlock diff --git a/esp-hal-common/src/mcpwm/operator.rs b/esp-hal-common/src/mcpwm/operator.rs index 8fc025b1ad5..b86a2adefd1 100644 --- a/esp-hal-common/src/mcpwm/operator.rs +++ b/esp-hal-common/src/mcpwm/operator.rs @@ -34,6 +34,8 @@ impl Operator { /// By default TIMER0 is used pub fn set_timer(&mut self, timer: &Timer) { 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), @@ -109,7 +111,9 @@ pub struct PwmPin { phantom: PhantomData, } -impl PwmPin { +impl + PwmPin +{ fn new(mut pin: Pin, config: PwmPinConfig) -> Self { let output_signal = PWM::output_signal::(); pin.enable_output(true) @@ -126,7 +130,7 @@ impl PwmPin< /// Configure what actions should be taken on timing events pub fn set_actions(&mut self, value: PwmActions) { // SAFETY: - // We only grant access to our GENx_x register with the lifetime of &mut self + // We only write to our GENx_x register let block = unsafe { &*PWM::block() }; let bits = value.0; @@ -149,6 +153,8 @@ impl PwmPin< /// 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) { @@ -179,6 +185,8 @@ impl PwmPin< /// 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) { @@ -211,6 +219,8 @@ impl PwmPin< /// [`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)), @@ -230,6 +240,8 @@ impl PwmPin< /// [`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)), diff --git a/esp-hal-common/src/mcpwm/timer.rs b/esp-hal-common/src/mcpwm/timer.rs index e9623849e89..a15057721ad 100644 --- a/esp-hal-common/src/mcpwm/timer.rs +++ b/esp-hal-common/src/mcpwm/timer.rs @@ -63,6 +63,8 @@ impl Timer { /// 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() }; // FIXME replace with safe API after https://github.com/esp-rs/esp-pacs/issues/57 @@ -90,6 +92,8 @@ impl Timer { /// 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 { From 13699437103c80f25ac98cfffb3bcd074450f419 Mon Sep 17 00:00:00 2001 From: dimi Date: Fri, 25 Nov 2022 17:48:40 +0100 Subject: [PATCH 11/12] resolve TODO --- esp-hal-common/src/mcpwm/operator.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/esp-hal-common/src/mcpwm/operator.rs b/esp-hal-common/src/mcpwm/operator.rs index b86a2adefd1..6ac5bcdd8fe 100644 --- a/esp-hal-common/src/mcpwm/operator.rs +++ b/esp-hal-common/src/mcpwm/operator.rs @@ -21,8 +21,12 @@ pub struct Operator { impl Operator { pub(super) fn new() -> Self { - // TODO maybe set timersel to 3 to disable? - + // 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, } From 0a75d3c64aa8beecdcd98ee5c52009d0598f4606 Mon Sep 17 00:00:00 2001 From: dimi Date: Mon, 28 Nov 2022 18:04:54 +0100 Subject: [PATCH 12/12] resolve mcpwm FIXME --- esp-hal-common/Cargo.toml | 2 +- esp-hal-common/src/mcpwm/timer.rs | 31 ++++++++++++++++++++++++------- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/esp-hal-common/Cargo.toml b/esp-hal-common/Cargo.toml index 4c275c22c7d..91968564e47 100644 --- a/esp-hal-common/Cargo.toml +++ b/esp-hal-common/Cargo.toml @@ -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" , "procmacros/xtensa", "xtensa-lx-rt/esp32", "xtensa-lx/esp32", "critical-section/restore-state-u32", "lock_api"] diff --git a/esp-hal-common/src/mcpwm/timer.rs b/esp-hal-common/src/mcpwm/timer.rs index a15057721ad..adbc71c880c 100644 --- a/esp-hal-common/src/mcpwm/timer.rs +++ b/esp-hal-common/src/mcpwm/timer.rs @@ -67,22 +67,39 @@ impl Timer { // We only write to our TIMERx_SYNC register let block = unsafe { &*PWM::block() }; - // FIXME replace with safe API after https://github.com/esp-rs/esp-pacs/issues/57 match TIM { 0 => { let sw = block.timer0_sync.read().sw().bit_is_set(); - let bits = ((direction as u32) << 20) + ((phase as u32) << 4) + ((!sw as u32) << 1); - unsafe { block.timer0_sync.write(|w| w.bits(bits)) } + 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(); - let bits = ((direction as u32) << 20) + ((phase as u32) << 4) + ((!sw as u32) << 1); - unsafe { block.timer1_sync.write(|w| w.bits(bits)) } + 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(); - let bits = ((direction as u32) << 20) + ((phase as u32) << 4) + ((!sw as u32) << 1); - unsafe { block.timer2_sync.write(|w| w.bits(bits)) } + block.timer2_sync.write(|w| { + w.timer2_phase_direction() + .variant(direction as u8 != 0) + .timer2_phase() + .variant(phase) + .sw() + .variant(!sw) + }); } _ => { unreachable!()