From b42c6d54bc4f438d65bef9afe096dccd3e77bc50 Mon Sep 17 00:00:00 2001 From: Albin Hedman Date: Sun, 26 Jun 2022 23:02:05 +0200 Subject: [PATCH 01/23] The beginning of a first rough draft... --- examples/mcpwm-simple.rs | 36 +++ src/lib.rs | 2 + src/mcpwm.rs | 458 +++++++++++++++++++++++++++++++++++++++ src/peripherals.rs | 10 + 4 files changed, 506 insertions(+) create mode 100644 examples/mcpwm-simple.rs create mode 100644 src/mcpwm.rs diff --git a/examples/mcpwm-simple.rs b/examples/mcpwm-simple.rs new file mode 100644 index 00000000000..5633af0b1e3 --- /dev/null +++ b/examples/mcpwm-simple.rs @@ -0,0 +1,36 @@ +use embedded_hal::delay::blocking::DelayUs; + +use esp_idf_hal::delay::FreeRtos; +use esp_idf_hal::mcpwm::{Mcpwm, Operator, OperatorConfig}; +use esp_idf_hal::prelude::Peripherals; +use esp_idf_hal::units::FromValueType; + +fn main() -> anyhow::Result<()> { + esp_idf_sys::link_patches(); + + println!("Configuring MCPWM"); + + let peripherals = Peripherals::take().unwrap(); + let config = OperatorConfig::default().frequency(25.kHz().into()); + let mcpwm = Mcpwm::new(peripherals.mcpwm0.mcpwm)?; + let mut operator = Operator::new( + peripherals.mcpwm0.operator0, + &mcpwm, + &config, + peripherals.pins.gpio4, + peripherals.pins.gpio5, + )?; + + println!("Starting duty-cycle loop"); + + for &duty in [0.0, 20.0, 40.0, 60.0, 80.0, 100.0].iter().cycle() { + println!("Duty {}%", duty); + operator.set_duty_a(duty)?; + operator.set_duty_b(100.0 - duty)?; + FreeRtos.delay_ms(2000)?; + } + + loop { + FreeRtos.delay_ms(1000)?; + } +} diff --git a/src/lib.rs b/src/lib.rs index fc29f5b3f75..e612ee1bc9c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,6 +38,8 @@ pub mod i2c; #[cfg(not(feature = "riscv-ulp-hal"))] pub mod interrupt; pub mod ledc; +#[cfg(any(esp32, esp32s3))] +pub mod mcpwm; #[cfg(not(feature = "riscv-ulp-hal"))] pub mod mutex; pub mod peripherals; diff --git a/src/mcpwm.rs b/src/mcpwm.rs new file mode 100644 index 00000000000..ea400e31d77 --- /dev/null +++ b/src/mcpwm.rs @@ -0,0 +1,458 @@ +//! Motor Control Pulse Width Modulator peripheral +//! +//! Interface to the [Motor Control Pulse Width Modulator peripheral (MCPWM) +//! peripheral](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/mcpwm.html) +//! +//! This is an initial implementation supporting !::!:!:!::!:!:!:! +//! TODO: write stuff here +//! +//! TODO: COme up with nice example +//! # Examples +//! +//! Create a 25 kHz PWM signal with 75 % duty cycle on GPIO 1 +//! ``` +//! +//! TODO: write example not stolen from ledc +//! ``` +//! +//! See the `examples/` folder of this repository for more. + +use core::borrow::Borrow; + +use crate::gpio::OutputPin; +use crate::units::{FromValueType, Hertz}; +use esp_idf_sys::{esp, mcpwm_config_t, EspError}; +use esp_idf_sys::{ + mcpwm_counter_type_t, + mcpwm_counter_type_t_MCPWM_DOWN_COUNTER, + //mcpwm_counter_type_t_MCPWM_FREEZE_COUNTER, + mcpwm_counter_type_t_MCPWM_UP_COUNTER, + mcpwm_counter_type_t_MCPWM_UP_DOWN_COUNTER, +}; +use esp_idf_sys::{ + mcpwm_duty_type_t, mcpwm_duty_type_t_MCPWM_DUTY_MODE_0, mcpwm_duty_type_t_MCPWM_DUTY_MODE_1, +}; +use esp_idf_sys::{ + mcpwm_io_signals_t, mcpwm_io_signals_t_MCPWM0A, mcpwm_io_signals_t_MCPWM0B, + mcpwm_io_signals_t_MCPWM1A, mcpwm_io_signals_t_MCPWM1B, mcpwm_io_signals_t_MCPWM2A, + mcpwm_io_signals_t_MCPWM2B, +}; +use esp_idf_sys::{ + mcpwm_timer_t, mcpwm_timer_t_MCPWM_TIMER_0, mcpwm_timer_t_MCPWM_TIMER_1, + mcpwm_timer_t_MCPWM_TIMER_2, +}; +use esp_idf_sys::{mcpwm_unit_t, mcpwm_unit_t_MCPWM_UNIT_0, mcpwm_unit_t_MCPWM_UNIT_1}; + +/// The Motor Control Pulse Width Modulator peripheral +pub struct Peripheral { + pub mcpwm: MCPWM, // TODO: Is there a better way to name or structure this? + pub operator0: OPERATOR0, + pub operator1: OPERATOR1, + pub operator2: OPERATOR2, +} + +impl Peripheral { + pub unsafe fn new() -> Self { + Self { + mcpwm: MCPWM::new(), + operator0: OPERATOR0::new(), + operator1: OPERATOR1::new(), + operator2: OPERATOR2::new(), + } + } +} + +#[derive(Clone, Copy, Debug)] +pub enum DutyMode { + ActiveHigh, + ActiveLow, +} + +impl Into for DutyMode { + fn into(self) -> mcpwm_duty_type_t { + match self { + DutyMode::ActiveHigh => mcpwm_duty_type_t_MCPWM_DUTY_MODE_0, + DutyMode::ActiveLow => mcpwm_duty_type_t_MCPWM_DUTY_MODE_1, + } + } +} + +// TODO: For UpDown, frequency is half of MCPWM frequency set +#[derive(Clone, Copy, Debug)] +pub enum CounterMode { + //Frozen, + Up, + Down, + UpDown, +} + +impl Into for CounterMode { + fn into(self) -> mcpwm_counter_type_t { + match self { + // TODO: This seems to be new to IDF 4.4? + //CounterMode::Frozen => mcpwm_counter_type_t_MCPWM_FREEZE_COUNTER, + CounterMode::Up => mcpwm_counter_type_t_MCPWM_UP_COUNTER, + CounterMode::Down => mcpwm_counter_type_t_MCPWM_DOWN_COUNTER, + CounterMode::UpDown => mcpwm_counter_type_t_MCPWM_UP_DOWN_COUNTER, + } + } +} + +pub struct OperatorConfig { + frequency: Hertz, + duty_a: Duty, + duty_b: Duty, + + #[cfg(idf_newer_than_or_equal_v4_4_0)] + lowest_frequency: Hertz, + duty_mode: DutyMode, + counter_mode: CounterMode, + //deadtime: DeadtimeConfig, +} + +impl OperatorConfig { + #[must_use] + pub fn frequency(mut self, frequency: Hertz) -> Self { + self.frequency = frequency; + self + } + + #[cfg(idf_newer_than_or_equal_v4_4_0)] + #[must_use] + pub fn lowest_frequency(mut self, lowest_frequency: Hertz) -> Self { + self.lowest_frequency = lowest_frequency; + self + } + + #[must_use] + pub fn duty_mode(mut self, duty_mode: DutyMode) -> Self { + self.duty_mode = duty_mode; + self + } + + #[must_use] + pub fn counter_mode(mut self, counter_mode: CounterMode) -> Self { + self.counter_mode = counter_mode; + self + } + + /* + #[must_use] + pub fn deadtime_config(mut self, deadtime_config: DeadtimeConfig) -> Self { + todo!() + }*/ +} + +impl Default for OperatorConfig { + fn default() -> Self { + Self { + frequency: 1000.Hz(), + duty_a: 50.0, + duty_b: 50.0, + + #[cfg(idf_newer_than_or_equal_v4_4_0)] + lowest_frequency: 1.Hz(), + + duty_mode: DutyMode::ActiveHigh, + counter_mode: CounterMode::Up, + } + } +} + +#[derive(Default)] +pub struct UnitZero; + +#[derive(Default)] +pub struct UnitOne; + +pub type Duty = f32; +const MAX_DUTY: Duty = 100.0; + +pub struct MCPWM { + unit: U, +} + +impl MCPWM { + /// # Safety + /// + /// It is safe to instantiate this exactly one time per `Unit`. + unsafe fn new() -> Self { + Self { unit: U::default() } + } +} + +pub trait Unit: Default { + fn unit() -> mcpwm_unit_t; +} + +impl Unit for UnitZero { + fn unit() -> mcpwm_unit_t { + mcpwm_unit_t_MCPWM_UNIT_0 + } +} + +impl Unit for UnitOne { + fn unit() -> mcpwm_unit_t { + mcpwm_unit_t_MCPWM_UNIT_1 + } +} + +// TODO: How do we want fault module to fit into this? +// TODO: How do we want capture module to fit into this? + +pub struct Mcpwm { + instance: MCPWM, +} + +impl Mcpwm { + pub fn new(instance: MCPWM) -> Result { + todo!(); + Ok(Self { instance }) + } + + // TODO: I do not at all understand the motivation behind exposing the group prescaler as + // "resolution"? From what i can see, duty is exposed as a percentage so we are not talking + // about the pulse length in terms of cycles as is the case for ledc, right? What am I missing? + // Anyway, my reasoning: + // * High res => small prescaler => better maintan accuracy at high frequency, unable to reach lower freq + // * Lower res => larger prescaler => worse accuracy at high frequency(still as good at low frequency), + // able to reach lower freq + // Would it make more sense to expose it as "lowest reachable frequency" + // Also why are there no checks other than prescaler >= 1 in the idf? + // What happens if prescaler register value does not fit into the least significant 8 bits? + #[cfg(idf_newer_than_or_equal_v4_4_0)] + pub fn lowest_frequency(mut self, lowest_frequency: Hertz) -> Result { + // MCPWM clock source frequency for ESP32 and ESP32-s3 + const MCPWM_CLOCK_SOURCE_FREQUENCY: u32 = 160_000_000; + // Max PWM timer prescaler + const MAX_PWM_TIMER_PRESCALE: u32 = 0x1_00; + // Max PWM timer period + const MAX_PWM_TIMER_PERIOD: u32 = 0x1_00_00; + + // let lowest_frequency = MCPWM_CLOCK_SOURCE_FREQUENCY / group_prescaler_factor / MAX_PWM_TIMER_PRESCALE / MAX_PWM_TIMER_PERIOD; + // let MAX_PWM_TIMER_PRESCALE * MAX_PWM_TIMER_PERIOD * frequency = MCPWM_CLOCK_SOURCE_FREQUENCY / group_prescaler_factor; + // let group_prescaler_factor = MCPWM_CLOCK_SOURCE_FREQUENCY / (MAX_PWM_TIMER_PRESCALE * MAX_PWM_TIMER_PERIOD * frequency); + + // let resolution = MCPWM_CLOCK_SOURCE_FREQUENCY / group_prescaler; + + // let resolution = MCPWM_CLOCK_SOURCE_FREQUENCY / (MCPWM_CLOCK_SOURCE_FREQUENCY / (MAX_PWM_TIMER_PRESCALE * MAX_PWM_TIMER_PERIOD * frequency)); + // let resolution = (MAX_PWM_TIMER_PRESCALE * MAX_PWM_TIMER_PERIOD * frequency) * 160_000_000 / MCPWM_CLOCK_SOURCE_FREQUENCY; + let resolution = MAX_PWM_TIMER_PRESCALE * MAX_PWM_TIMER_PERIOD * lowest_reachable_frequency; + + mcpwm_group_set_resolution(); + todo!() + } +} + +// The hardware for ESP32 and ESP32-S3 can associate any operator(within the mcpwm module) with any +// timer(within the mcpwm module) for example allowing using the same timer for all three operators. +// However at least as of IDF v4.4 timer0 is hardcoded to operator0 and timer1 to operator1 and so on... +pub trait HwOperator { + fn timer() -> mcpwm_timer_t; + fn signal_a() -> mcpwm_io_signals_t; + fn signal_b() -> mcpwm_io_signals_t; + fn unit() -> mcpwm_unit_t { + U::unit() + } +} + +macro_rules! impl_operator_helper { + ($instance:ident: $timer:expr, $signal_a:expr, $signal_b:expr, $unit:ty) => { + impl HwOperator<$unit> for $instance<$unit> { + fn timer() -> mcpwm_timer_t { + $timer + } + + fn signal_a() -> mcpwm_io_signals_t { + $signal_a + } + + fn signal_b() -> mcpwm_io_signals_t { + $signal_b + } + } + }; +} + +macro_rules! impl_operator { + ($instance:ident: $timer:expr, $signal_a:expr, $signal_b:expr) => { + pub struct $instance { + unit: U, + } + + impl $instance { + /// # Safety + /// + /// It is safe to instantiate this operator exactly one time. + pub unsafe fn new() -> Self { + $instance { unit: U::default() } + } + } + + impl_operator_helper!($instance: $timer, $signal_a, $signal_b, UnitZero); + impl_operator_helper!($instance: $timer, $signal_a, $signal_b, UnitOne); + }; +} + +impl_operator!( + OPERATOR0: mcpwm_timer_t_MCPWM_TIMER_0, + mcpwm_io_signals_t_MCPWM0A, + mcpwm_io_signals_t_MCPWM0B +); +impl_operator!( + OPERATOR1: mcpwm_timer_t_MCPWM_TIMER_1, + mcpwm_io_signals_t_MCPWM1A, + mcpwm_io_signals_t_MCPWM1B +); +impl_operator!( + OPERATOR2: mcpwm_timer_t_MCPWM_TIMER_2, + mcpwm_io_signals_t_MCPWM2A, + mcpwm_io_signals_t_MCPWM2B +); + +// TODO: How do we want syncing to fit in to this? +// TODO: How do we want deadtime to fit into this? +// TODO: How do we want carrier to fit into this? + +pub struct Operator, M: Borrow>, PA: OutputPin, PB: OutputPin> { + instance: O, + unit: U, + mcpwm_module: M, + pin_a: Option, + pin_b: Option, +} + +impl, M: Borrow>, PA: OutputPin, PB: OutputPin> + Operator +{ + pub fn new>, B: Into>>( + operator: O, + mcpwm_module: M, + config: &OperatorConfig, + pin_a: A, + pin_b: B, + ) -> Result { + // TODO: Dont forget to make these cases work + #[cfg(idf_newer_than_or_equal_v4_4_0)] + { + if config.frequency < config.lowest_frequency { + return panic!("TODO: Invalid parameter, should this be checked in OperatorConfig or here or hope that the IDF? will handle the error checking"); + } + unsafe { + esp_idf_sys::mcpwm_timer_set_resolution(); + } + } + unsafe { + // TODO: Handle half pwm frequency when counter_mode = UpDown here? + + esp!(unsafe { + esp_idf_sys::mcpwm_init( + U::unit(), + O::timer(), + &mcpwm_config_t { + frequency: config.frequency.into(), + cmpr_a: config.duty_a, + cmpr_b: config.duty_b, + duty_mode: config.duty_mode.into(), + counter_mode: config.counter_mode.into(), + }, + ) + })?; + + todo!(); + } + + let pin_a: Option = pin_a.into(); + let pin_b: Option = pin_b.into(); + + if let Some(pin_a) = &pin_a { + let io_signal = O::signal_a(); + esp!(unsafe { esp_idf_sys::mcpwm_gpio_init(U::unit(), io_signal, pin_a.pin()) })?; + } + + if let Some(pin_b) = &pin_b { + let io_signal = O::signal_b(); + esp!(unsafe { esp_idf_sys::mcpwm_gpio_init(U::unit(), io_signal, pin_b.pin()) })?; + } + + Ok(Self { + instance: operator, + unit: U::default(), + mcpwm_module, + pin_a, + pin_b, + }) + } + + pub fn get_duty_a(&self) -> Result { + todo!() + } + + pub fn get_duty_b(&self) -> Result { + todo!() + } + + pub fn set_duty_a(&mut self, duty: Duty) -> Result<(), EspError> { + todo!() + } + + pub fn set_duty_b(&mut self, duty: Duty) -> Result<(), EspError> { + todo!() + } + + pub fn set_frequency() -> Result<(), EspError> { + todo!() + } + pub fn get_frequency() -> Result<(), EspError> { + todo!() + } +} + +pub enum Generator { + A, + B, +} + +// TODO: would we like to also implement PwmPin for something like Foo<&Operator, G: GeneratorAorB> +// this would allow things like: +// let operator = Operator::new(...); +// let gen_a = operator.generator_a_as_pwm_pin(); +// let gen_b = operator.generator_b_as_pwm_pin(); +// function_expecting_pwm_pin(&gen_a); +// gen_a.set_duty() +// gen_a.set_duty() +impl, M: Borrow>, PA: OutputPin, PB: OutputPin> + embedded_hal_0_2::Pwm for Operator +{ + type Channel = Generator; + type Duty = Duty; + type Time = (); + + fn disable(&mut self, channel: Self::Channel) { + todo!() + } + + fn enable(&mut self, channel: Self::Channel) { + todo!() + } + + fn get_period(&self) -> Self::Time { + todo!() + } + + fn get_duty(&self, channel: Self::Channel) -> Self::Duty { + self.get_duty(channel) + } + + fn get_max_duty(&self) -> Self::Duty { + MAX_DUTY + } + + fn set_duty(&mut self, channel: Self::Channel, duty: Self::Duty) { + self.set_duty(channel, duty) + } + + fn set_period

(&mut self, period: P) { + todo!() + } +} diff --git a/src/peripherals.rs b/src/peripherals.rs index 61773406124..9022c8af9d8 100644 --- a/src/peripherals.rs +++ b/src/peripherals.rs @@ -14,6 +14,8 @@ use crate::hall; use crate::i2c; #[cfg(not(feature = "riscv-ulp-hal"))] use crate::ledc; +#[cfg(any(esp32, esp32s3))] +use crate::mcpwm; #[cfg(not(feature = "riscv-ulp-hal"))] use crate::rmt; #[cfg(not(feature = "riscv-ulp-hal"))] @@ -53,6 +55,10 @@ pub struct Peripherals { pub can: can::CAN, #[cfg(not(feature = "riscv-ulp-hal"))] pub ledc: ledc::Peripheral, + #[cfg(any(esp32, esp32s3))] + pub mcpwm0: mcpwm::Peripheral, + #[cfg(any(esp32, esp32s3))] + pub mcpwm1: mcpwm::Peripheral, #[cfg(not(feature = "riscv-ulp-hal"))] pub rmt: rmt::Peripheral, #[cfg(all( @@ -107,6 +113,10 @@ impl Peripherals { can: can::CAN::new(), #[cfg(not(feature = "riscv-ulp-hal"))] ledc: ledc::Peripheral::new(), + #[cfg(any(esp32, esp32s3))] + mcpwm0: mcpwm::Peripheral::new(), + #[cfg(any(esp32, esp32s3))] + mcpwm1: mcpwm::Peripheral::new(), #[cfg(not(feature = "riscv-ulp-hal"))] rmt: rmt::Peripheral::new(), #[cfg(all( From 507dd8bfce75931958ae6a5248a905e41f33028f Mon Sep 17 00:00:00 2001 From: Albin Hedman Date: Thu, 30 Jun 2022 22:16:14 +0200 Subject: [PATCH 02/23] Fix some of the TODOs, disable example for esp variants without mcpwm --- examples/mcpwm-simple.rs | 2 + src/mcpwm.rs | 176 ++++++++++++++++++++++++++------------- 2 files changed, 122 insertions(+), 56 deletions(-) diff --git a/examples/mcpwm-simple.rs b/examples/mcpwm-simple.rs index 5633af0b1e3..e877f593335 100644 --- a/examples/mcpwm-simple.rs +++ b/examples/mcpwm-simple.rs @@ -1,3 +1,5 @@ +#![cfg(any(esp32, esp32s3))] + use embedded_hal::delay::blocking::DelayUs; use esp_idf_hal::delay::FreeRtos; diff --git a/src/mcpwm.rs b/src/mcpwm.rs index ea400e31d77..4d28e33e6cd 100644 --- a/src/mcpwm.rs +++ b/src/mcpwm.rs @@ -6,13 +6,31 @@ //! This is an initial implementation supporting !::!:!:!::!:!:!:! //! TODO: write stuff here //! -//! TODO: COme up with nice example +//! TODO: Come up with nice example //! # Examples //! -//! Create a 25 kHz PWM signal with 75 % duty cycle on GPIO 1 +//! Create a pair of PWM signals on pin 4 and 5. The duty on pin 4 will ramp from 0% to 100% +//! while pin 5 will ramp from 100% down to 0%. //! ``` +//! let peripherals = Peripherals::take().unwrap(); +//! let config = OperatorConfig::default().frequency(25.kHz().into()); +//! let mcpwm = Mcpwm::new(peripherals.mcpwm0.mcpwm)?; +//! let mut operator = Operator::new( +//! peripherals.mcpwm0.operator0, +//! &mcpwm, +//! &config, +//! peripherals.pins.gpio4, +//! peripherals.pins.gpio5, +//! )?; //! -//! TODO: write example not stolen from ledc +//! println!("Starting duty-cycle loop"); +//! +//! for &duty in [0.0, 20.0, 40.0, 60.0, 80.0, 100.0].iter() { +//! println!("Duty {}%", duty); +//! operator.set_duty_a(duty)?; +//! operator.set_duty_b(100.0 - duty)?; +//! FreeRtos.delay_ms(2000)?; +//! } //! ``` //! //! See the `examples/` folder of this repository for more. @@ -169,7 +187,7 @@ pub type Duty = f32; const MAX_DUTY: Duty = 100.0; pub struct MCPWM { - unit: U, + _unit: U, } impl MCPWM { @@ -177,7 +195,9 @@ impl MCPWM { /// /// It is safe to instantiate this exactly one time per `Unit`. unsafe fn new() -> Self { - Self { unit: U::default() } + Self { + _unit: U::default(), + } } } @@ -201,13 +221,14 @@ impl Unit for UnitOne { // TODO: How do we want capture module to fit into this? pub struct Mcpwm { - instance: MCPWM, + _instance: MCPWM, } impl Mcpwm { pub fn new(instance: MCPWM) -> Result { - todo!(); - Ok(Self { instance }) + Ok(Self { + _instance: instance, + }) } // TODO: I do not at all understand the motivation behind exposing the group prescaler as @@ -277,7 +298,7 @@ macro_rules! impl_operator_helper { macro_rules! impl_operator { ($instance:ident: $timer:expr, $signal_a:expr, $signal_b:expr) => { pub struct $instance { - unit: U, + _unit: U, } impl $instance { @@ -285,7 +306,9 @@ macro_rules! impl_operator { /// /// It is safe to instantiate this operator exactly one time. pub unsafe fn new() -> Self { - $instance { unit: U::default() } + $instance { + _unit: U::default(), + } } } @@ -315,11 +338,12 @@ impl_operator!( // TODO: How do we want carrier to fit into this? pub struct Operator, M: Borrow>, PA: OutputPin, PB: OutputPin> { - instance: O, - unit: U, - mcpwm_module: M, - pin_a: Option, - pin_b: Option, + _instance: O, + _unit: U, + _mcpwm_module: M, + + _pin_a: Option, + _pin_b: Option, } impl, M: Borrow>, PA: OutputPin, PB: OutputPin> @@ -336,31 +360,28 @@ impl, M: Borrow>, PA: OutputPin, PB: OutputPi #[cfg(idf_newer_than_or_equal_v4_4_0)] { if config.frequency < config.lowest_frequency { - return panic!("TODO: Invalid parameter, should this be checked in OperatorConfig or here or hope that the IDF? will handle the error checking"); + return Err(panic!("TODO: Invalid parameter, should this be checked in OperatorConfig or here or hope that the IDF? will handle the error checking")); } unsafe { esp_idf_sys::mcpwm_timer_set_resolution(); } } - unsafe { - // TODO: Handle half pwm frequency when counter_mode = UpDown here? - - esp!(unsafe { - esp_idf_sys::mcpwm_init( - U::unit(), - O::timer(), - &mcpwm_config_t { - frequency: config.frequency.into(), - cmpr_a: config.duty_a, - cmpr_b: config.duty_b, - duty_mode: config.duty_mode.into(), - counter_mode: config.counter_mode.into(), - }, - ) - })?; - - todo!(); - } + + // TODO: Handle half pwm frequency when counter_mode = UpDown here? + + esp!(unsafe { + esp_idf_sys::mcpwm_init( + U::unit(), + O::timer(), + &mcpwm_config_t { + frequency: config.frequency.into(), + cmpr_a: config.duty_a, + cmpr_b: config.duty_b, + duty_mode: config.duty_mode.into(), + counter_mode: config.counter_mode.into(), + }, + ) + })?; let pin_a: Option = pin_a.into(); let pin_b: Option = pin_b.into(); @@ -376,35 +397,68 @@ impl, M: Borrow>, PA: OutputPin, PB: OutputPi } Ok(Self { - instance: operator, - unit: U::default(), - mcpwm_module, - pin_a, - pin_b, + _instance: operator, + _unit: U::default(), + _mcpwm_module: mcpwm_module, + _pin_a: pin_a, + _pin_b: pin_b, }) } - pub fn get_duty_a(&self) -> Result { - todo!() + pub fn get_duty_a(&self) -> Duty { + unsafe { + esp_idf_sys::mcpwm_get_duty( + U::unit(), + O::timer(), + esp_idf_sys::mcpwm_generator_t_MCPWM_GEN_A, + ) + } } - pub fn get_duty_b(&self) -> Result { - todo!() + pub fn get_duty_b(&self) -> Duty { + unsafe { + esp_idf_sys::mcpwm_get_duty( + U::unit(), + O::timer(), + esp_idf_sys::mcpwm_generator_t_MCPWM_GEN_B, + ) + } } + /// Set duty as percentage between 0.0 and 100.0 pub fn set_duty_a(&mut self, duty: Duty) -> Result<(), EspError> { - todo!() + unsafe { + esp!(esp_idf_sys::mcpwm_set_duty( + U::unit(), + O::timer(), + esp_idf_sys::mcpwm_generator_t_MCPWM_GEN_A, + duty + )) + } } pub fn set_duty_b(&mut self, duty: Duty) -> Result<(), EspError> { - todo!() + unsafe { + esp!(esp_idf_sys::mcpwm_set_duty( + U::unit(), + O::timer(), + esp_idf_sys::mcpwm_generator_t_MCPWM_GEN_B, + duty + )) + } } - pub fn set_frequency() -> Result<(), EspError> { - todo!() + pub fn set_frequency(frequency: u32) -> Result<(), EspError> { + unsafe { + esp!(esp_idf_sys::mcpwm_set_frequency( + U::unit(), + O::timer(), + frequency + )) + } } - pub fn get_frequency() -> Result<(), EspError> { - todo!() + pub fn get_frequency() -> u32 { + unsafe { esp_idf_sys::mcpwm_get_frequency(U::unit(), O::timer()) } } } @@ -413,7 +467,7 @@ pub enum Generator { B, } -// TODO: would we like to also implement PwmPin for something like Foo<&Operator, G: GeneratorAorB> +// TODO: would we like to also implement PwmPin for something like Foo<&Operator, G: Generator> // this would allow things like: // let operator = Operator::new(...); // let gen_a = operator.generator_a_as_pwm_pin(); @@ -428,11 +482,11 @@ impl, M: Borrow>, PA: OutputPin, PB: OutputPi type Duty = Duty; type Time = (); - fn disable(&mut self, channel: Self::Channel) { + fn disable(&mut self, _channel: Self::Channel) { todo!() } - fn enable(&mut self, channel: Self::Channel) { + fn enable(&mut self, _channel: Self::Channel) { todo!() } @@ -441,7 +495,10 @@ impl, M: Borrow>, PA: OutputPin, PB: OutputPi } fn get_duty(&self, channel: Self::Channel) -> Self::Duty { - self.get_duty(channel) + match channel { + Generator::A => self.get_duty_a(), + Generator::B => self.get_duty_b(), + } } fn get_max_duty(&self) -> Self::Duty { @@ -449,10 +506,17 @@ impl, M: Borrow>, PA: OutputPin, PB: OutputPi } fn set_duty(&mut self, channel: Self::Channel, duty: Self::Duty) { - self.set_duty(channel, duty) + match channel { + Generator::A => self + .set_duty_a(duty) + .expect("Failed to set duty for generator A"), + Generator::B => self + .set_duty_b(duty) + .expect("Failed to set duty for generator B"), + } } - fn set_period

(&mut self, period: P) { + fn set_period

(&mut self, _period: P) { todo!() } } From e4ed558d6e91c2348168ec505c5b3aa69ef53dc4 Mon Sep 17 00:00:00 2001 From: Albin Hedman Date: Fri, 1 Jul 2022 17:45:36 +0200 Subject: [PATCH 03/23] Fix clippy lints --- src/mcpwm.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/mcpwm.rs b/src/mcpwm.rs index 4d28e33e6cd..e33cd3f60b3 100644 --- a/src/mcpwm.rs +++ b/src/mcpwm.rs @@ -70,6 +70,9 @@ pub struct Peripheral { } impl Peripheral { + /// # Safety + /// + /// It is safe to instantiate this exactly one time per `Unit`. pub unsafe fn new() -> Self { Self { mcpwm: MCPWM::new(), @@ -86,9 +89,9 @@ pub enum DutyMode { ActiveLow, } -impl Into for DutyMode { - fn into(self) -> mcpwm_duty_type_t { - match self { +impl From for mcpwm_duty_type_t { + fn from(val: DutyMode) -> Self { + match val { DutyMode::ActiveHigh => mcpwm_duty_type_t_MCPWM_DUTY_MODE_0, DutyMode::ActiveLow => mcpwm_duty_type_t_MCPWM_DUTY_MODE_1, } @@ -104,9 +107,9 @@ pub enum CounterMode { UpDown, } -impl Into for CounterMode { - fn into(self) -> mcpwm_counter_type_t { - match self { +impl From for mcpwm_counter_type_t { + fn from(val: CounterMode) -> Self { + match val { // TODO: This seems to be new to IDF 4.4? //CounterMode::Frozen => mcpwm_counter_type_t_MCPWM_FREEZE_COUNTER, CounterMode::Up => mcpwm_counter_type_t_MCPWM_UP_COUNTER, @@ -304,7 +307,7 @@ macro_rules! impl_operator { impl $instance { /// # Safety /// - /// It is safe to instantiate this operator exactly one time. + /// It is safe to instantiate this operator exactly one time per Unit. pub unsafe fn new() -> Self { $instance { _unit: U::default(), From 1e2a947831e8222ec58d8aaf82d77a889cc5eaba Mon Sep 17 00:00:00 2001 From: Albin Hedman Date: Fri, 1 Jul 2022 21:47:44 +0200 Subject: [PATCH 04/23] Update cfg's --- src/lib.rs | 2 +- src/mcpwm.rs | 2 ++ src/peripherals.rs | 6 +++--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e612ee1bc9c..a88b00e9e59 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,7 +38,7 @@ pub mod i2c; #[cfg(not(feature = "riscv-ulp-hal"))] pub mod interrupt; pub mod ledc; -#[cfg(any(esp32, esp32s3))] +#[cfg(all(any(esp32, esp32s3), not(feature = "riscv-ulp-hal")))] pub mod mcpwm; #[cfg(not(feature = "riscv-ulp-hal"))] pub mod mutex; diff --git a/src/mcpwm.rs b/src/mcpwm.rs index e33cd3f60b3..e4b9258fed6 100644 --- a/src/mcpwm.rs +++ b/src/mcpwm.rs @@ -1,3 +1,5 @@ +#![cfg(all(any(esp32, esp32s3), not(feature = "riscv-ulp-hal")))] + //! Motor Control Pulse Width Modulator peripheral //! //! Interface to the [Motor Control Pulse Width Modulator peripheral (MCPWM) diff --git a/src/peripherals.rs b/src/peripherals.rs index 9022c8af9d8..a7a6dc9bc02 100644 --- a/src/peripherals.rs +++ b/src/peripherals.rs @@ -14,7 +14,7 @@ use crate::hall; use crate::i2c; #[cfg(not(feature = "riscv-ulp-hal"))] use crate::ledc; -#[cfg(any(esp32, esp32s3))] +#[cfg(all(any(esp32, esp32s3), not(feature = "riscv-ulp-hal")))] use crate::mcpwm; #[cfg(not(feature = "riscv-ulp-hal"))] use crate::rmt; @@ -55,9 +55,9 @@ pub struct Peripherals { pub can: can::CAN, #[cfg(not(feature = "riscv-ulp-hal"))] pub ledc: ledc::Peripheral, - #[cfg(any(esp32, esp32s3))] + #[cfg(all(any(esp32, esp32s3), not(feature = "riscv-ulp-hal")))] pub mcpwm0: mcpwm::Peripheral, - #[cfg(any(esp32, esp32s3))] + #[cfg(all(any(esp32, esp32s3), not(feature = "riscv-ulp-hal")))] pub mcpwm1: mcpwm::Peripheral, #[cfg(not(feature = "riscv-ulp-hal"))] pub rmt: rmt::Peripheral, From 6260781e06646607fb48f90413049158f2a59f9f Mon Sep 17 00:00:00 2001 From: Albin Hedman Date: Fri, 15 Jul 2022 21:24:56 +0200 Subject: [PATCH 05/23] use esp_idf_sys::* --- src/mcpwm.rs | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/src/mcpwm.rs b/src/mcpwm.rs index e4b9258fed6..2fa5b0661a1 100644 --- a/src/mcpwm.rs +++ b/src/mcpwm.rs @@ -41,27 +41,7 @@ use core::borrow::Borrow; use crate::gpio::OutputPin; use crate::units::{FromValueType, Hertz}; -use esp_idf_sys::{esp, mcpwm_config_t, EspError}; -use esp_idf_sys::{ - mcpwm_counter_type_t, - mcpwm_counter_type_t_MCPWM_DOWN_COUNTER, - //mcpwm_counter_type_t_MCPWM_FREEZE_COUNTER, - mcpwm_counter_type_t_MCPWM_UP_COUNTER, - mcpwm_counter_type_t_MCPWM_UP_DOWN_COUNTER, -}; -use esp_idf_sys::{ - mcpwm_duty_type_t, mcpwm_duty_type_t_MCPWM_DUTY_MODE_0, mcpwm_duty_type_t_MCPWM_DUTY_MODE_1, -}; -use esp_idf_sys::{ - mcpwm_io_signals_t, mcpwm_io_signals_t_MCPWM0A, mcpwm_io_signals_t_MCPWM0B, - mcpwm_io_signals_t_MCPWM1A, mcpwm_io_signals_t_MCPWM1B, mcpwm_io_signals_t_MCPWM2A, - mcpwm_io_signals_t_MCPWM2B, -}; -use esp_idf_sys::{ - mcpwm_timer_t, mcpwm_timer_t_MCPWM_TIMER_0, mcpwm_timer_t_MCPWM_TIMER_1, - mcpwm_timer_t_MCPWM_TIMER_2, -}; -use esp_idf_sys::{mcpwm_unit_t, mcpwm_unit_t_MCPWM_UNIT_0, mcpwm_unit_t_MCPWM_UNIT_1}; +use esp_idf_sys::*; /// The Motor Control Pulse Width Modulator peripheral pub struct Peripheral { From f2d6c0ced35c5dff01ed3a6a462ac7b435e8767e Mon Sep 17 00:00:00 2001 From: Albin Hedman Date: Tue, 19 Jul 2022 22:44:44 +0200 Subject: [PATCH 06/23] Address some of the comments and add deadtime support --- examples/mcpwm-simple.rs | 6 +- src/mcpwm.rs | 328 +++++++++++++++++++++++++-------------- 2 files changed, 213 insertions(+), 121 deletions(-) diff --git a/examples/mcpwm-simple.rs b/examples/mcpwm-simple.rs index e877f593335..5876511ce28 100644 --- a/examples/mcpwm-simple.rs +++ b/examples/mcpwm-simple.rs @@ -1,5 +1,3 @@ -#![cfg(any(esp32, esp32s3))] - use embedded_hal::delay::blocking::DelayUs; use esp_idf_hal::delay::FreeRtos; @@ -7,6 +5,7 @@ use esp_idf_hal::mcpwm::{Mcpwm, Operator, OperatorConfig}; use esp_idf_hal::prelude::Peripherals; use esp_idf_hal::units::FromValueType; +#[cfg(any(esp32, esp32s3))] fn main() -> anyhow::Result<()> { esp_idf_sys::link_patches(); @@ -36,3 +35,6 @@ fn main() -> anyhow::Result<()> { FreeRtos.delay_ms(1000)?; } } + +#[cfg(not(any(esp32, esp32s3)))] +fn main() {} diff --git a/src/mcpwm.rs b/src/mcpwm.rs index 2fa5b0661a1..3825eeca5fb 100644 --- a/src/mcpwm.rs +++ b/src/mcpwm.rs @@ -1,15 +1,23 @@ -#![cfg(all(any(esp32, esp32s3), not(feature = "riscv-ulp-hal")))] - //! Motor Control Pulse Width Modulator peripheral //! //! Interface to the [Motor Control Pulse Width Modulator peripheral (MCPWM) //! peripheral](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/mcpwm.html) //! -//! This is an initial implementation supporting !::!:!:!::!:!:!:! -//! TODO: write stuff here -//! -//! TODO: Come up with nice example -//! # Examples +//! ``` +//! --------------- --------------- +//! | MCPWM Unit 0 | | MCPWM Unit 1 | +//! | ------------ | | ------------ | +//! | | | | +//! | OPERATOR 0 |--> A | OPERATOR 0 |--> A +//! | |--> B | |--> B +//! | | | | +//! | OPERATOR 1 |--> A | OPERATOR 1 |--> A +//! | |--> B | |--> B +//! | | | | +//! | OPERATOR 2 |--> A | OPERATOR 2 |--> A +//! | |--> B | |--> B +//! --------------- --------------- +//! ``` //! //! Create a pair of PWM signals on pin 4 and 5. The duty on pin 4 will ramp from 0% to 100% //! while pin 5 will ramp from 100% down to 0%. @@ -43,9 +51,18 @@ use crate::gpio::OutputPin; use crate::units::{FromValueType, Hertz}; use esp_idf_sys::*; +// MCPWM clock source frequency for ESP32 and ESP32-s3 +const MCPWM_CLOCK_SOURCE_FREQUENCY: u32 = 160_000_000; + +// Max PWM timer prescaler +const MAX_PWM_TIMER_PRESCALE: u32 = 0x1_00; + +// Max PWM timer period +const MAX_PWM_TIMER_PERIOD: u32 = 0x1_00_00; + /// The Motor Control Pulse Width Modulator peripheral pub struct Peripheral { - pub mcpwm: MCPWM, // TODO: Is there a better way to name or structure this? + pub mcpwm: MCPWM, pub operator0: OPERATOR0, pub operator1: OPERATOR1, pub operator2: OPERATOR2, @@ -83,7 +100,8 @@ impl From for mcpwm_duty_type_t { // TODO: For UpDown, frequency is half of MCPWM frequency set #[derive(Clone, Copy, Debug)] pub enum CounterMode { - //Frozen, + #[cfg(not(esp_idf_version = "4.3"))] + Frozen, Up, Down, UpDown, @@ -92,8 +110,8 @@ pub enum CounterMode { impl From for mcpwm_counter_type_t { fn from(val: CounterMode) -> Self { match val { - // TODO: This seems to be new to IDF 4.4? - //CounterMode::Frozen => mcpwm_counter_type_t_MCPWM_FREEZE_COUNTER, + #[cfg(not(esp_idf_version = "4.3"))] + CounterMode::Frozen => mcpwm_counter_type_t_MCPWM_FREEZE_COUNTER, CounterMode::Up => mcpwm_counter_type_t_MCPWM_UP_COUNTER, CounterMode::Down => mcpwm_counter_type_t_MCPWM_DOWN_COUNTER, CounterMode::UpDown => mcpwm_counter_type_t_MCPWM_UP_DOWN_COUNTER, @@ -101,16 +119,108 @@ impl From for mcpwm_counter_type_t { } } +// TODO: Note that `red` and `fed` from the IDF's perspecitve is time as in number of clock cycles after the +// MCPWM modules group prescaler. How do we want to expose this? Do we expose it as just that, a cycle count? +// Or do we expose it as a time which we then calculate the cycle count from? +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum DeadtimeConfig { + // TODO: Figure out what all of those options do and give them nice descriptions + /// MCPWM_BYPASS_RED + BypassRisingEdge { fed: u16 }, + + /// MCPWM_BYPASS_FED + BypassFallingEdge { red: u16 }, + + /// MCPWM_ACTIVE_HIGH_MODE + ActiveHigh { red: u16, fed: u16 }, + + /// MCPWM_ACTIVE_LOW_MODE + ActiveLow { red: u16, fed: u16 }, + + /// MCPWM_ACTIVE_HIGH_COMPLIMENT_MODE + ActiveHighComplement { red: u16, fed: u16 }, + + /// MCPWM_ACTIVE_LOW_COMPLIMENT_MODE + ActiveLowComplement { red: u16, fed: u16 }, + + /// MCPWM_ACTIVE_RED_FED_FROM_PWMXA + ActiveRedFedFromPwmxa { red: u16, fed: u16 }, + + /// MCPWM_ACTIVE_RED_FED_FROM_PWMXB + ActiveRedFedFromPwmxb { red: u16, fed: u16 }, +} + +struct DeadtimeArgs { + red: u16, + fed: u16, + mode: mcpwm_deadtime_type_t, +} + +impl DeadtimeConfig { + fn as_args(&self) -> DeadtimeArgs { + match *self { + DeadtimeConfig::BypassRisingEdge { fed } => DeadtimeArgs { + red: 0, + fed, + mode: mcpwm_deadtime_type_t_MCPWM_BYPASS_RED, + }, + + DeadtimeConfig::BypassFallingEdge { red } => DeadtimeArgs { + red, + fed: 0, + mode: mcpwm_deadtime_type_t_MCPWM_BYPASS_FED, + }, + + DeadtimeConfig::ActiveHigh { red, fed } => DeadtimeArgs { + red, + fed, + mode: mcpwm_deadtime_type_t_MCPWM_ACTIVE_HIGH_MODE, + }, + + DeadtimeConfig::ActiveLow { red, fed } => DeadtimeArgs { + red, + fed, + mode: mcpwm_deadtime_type_t_MCPWM_ACTIVE_LOW_MODE, + }, + + DeadtimeConfig::ActiveHighComplement { red, fed } => DeadtimeArgs { + red, + fed, + mode: mcpwm_deadtime_type_t_MCPWM_ACTIVE_HIGH_COMPLIMENT_MODE, + }, + + DeadtimeConfig::ActiveLowComplement { red, fed } => DeadtimeArgs { + red, + fed, + mode: mcpwm_deadtime_type_t_MCPWM_ACTIVE_LOW_COMPLIMENT_MODE, + }, + + DeadtimeConfig::ActiveRedFedFromPwmxa { red, fed } => DeadtimeArgs { + red, + fed, + mode: mcpwm_deadtime_type_t_MCPWM_ACTIVE_RED_FED_FROM_PWMXA, + }, + + DeadtimeConfig::ActiveRedFedFromPwmxb { red, fed } => DeadtimeArgs { + red, + fed, + mode: mcpwm_deadtime_type_t_MCPWM_ACTIVE_RED_FED_FROM_PWMXB, + }, + } + } +} + pub struct OperatorConfig { frequency: Hertz, duty_a: Duty, duty_b: Duty, - #[cfg(idf_newer_than_or_equal_v4_4_0)] + #[cfg(not(esp_idf_version = "4.3"))] lowest_frequency: Hertz, duty_mode: DutyMode, counter_mode: CounterMode, - //deadtime: DeadtimeConfig, + + deadtime: Option, } impl OperatorConfig { @@ -120,7 +230,7 @@ impl OperatorConfig { self } - #[cfg(idf_newer_than_or_equal_v4_4_0)] + #[cfg(not(esp_idf_version = "4.3"))] #[must_use] pub fn lowest_frequency(mut self, lowest_frequency: Hertz) -> Self { self.lowest_frequency = lowest_frequency; @@ -139,11 +249,11 @@ impl OperatorConfig { self } - /* #[must_use] - pub fn deadtime_config(mut self, deadtime_config: DeadtimeConfig) -> Self { - todo!() - }*/ + pub fn deadtime(mut self, deadtime: impl Into>) -> Self { + self.deadtime = deadtime.into(); + self + } } impl Default for OperatorConfig { @@ -153,11 +263,13 @@ impl Default for OperatorConfig { duty_a: 50.0, duty_b: 50.0, - #[cfg(idf_newer_than_or_equal_v4_4_0)] - lowest_frequency: 1.Hz(), + #[cfg(not(esp_idf_version = "4.3"))] + lowest_frequency: 16.Hz(), duty_mode: DutyMode::ActiveHigh, counter_mode: CounterMode::Up, + + deadtime: None, } } } @@ -169,7 +281,6 @@ pub struct UnitZero; pub struct UnitOne; pub type Duty = f32; -const MAX_DUTY: Duty = 100.0; pub struct MCPWM { _unit: U, @@ -203,50 +314,58 @@ impl Unit for UnitOne { } // TODO: How do we want fault module to fit into this? -// TODO: How do we want capture module to fit into this? pub struct Mcpwm { + #[cfg(not(esp_idf_version = "4.3"))] + /// This is the frequency of the clock signal passed on as clock source for the operators timers + /// Those timers in turn have their own prescalers to scale this down even further + /// + /// NOTE: This is only to be set by calling Self::lowest_frequency + operator_input_frequency: u32, _instance: MCPWM, } impl Mcpwm { pub fn new(instance: MCPWM) -> Result { - Ok(Self { + let res = Self { + #[cfg(not(esp_idf_version = "4.3"))] + operator_input_frequency: 0, _instance: instance, - }) - } + }; - // TODO: I do not at all understand the motivation behind exposing the group prescaler as - // "resolution"? From what i can see, duty is exposed as a percentage so we are not talking - // about the pulse length in terms of cycles as is the case for ledc, right? What am I missing? - // Anyway, my reasoning: - // * High res => small prescaler => better maintan accuracy at high frequency, unable to reach lower freq - // * Lower res => larger prescaler => worse accuracy at high frequency(still as good at low frequency), - // able to reach lower freq - // Would it make more sense to expose it as "lowest reachable frequency" - // Also why are there no checks other than prescaler >= 1 in the idf? - // What happens if prescaler register value does not fit into the least significant 8 bits? - #[cfg(idf_newer_than_or_equal_v4_4_0)] - pub fn lowest_frequency(mut self, lowest_frequency: Hertz) -> Result { - // MCPWM clock source frequency for ESP32 and ESP32-s3 - const MCPWM_CLOCK_SOURCE_FREQUENCY: u32 = 160_000_000; - // Max PWM timer prescaler - const MAX_PWM_TIMER_PRESCALE: u32 = 0x1_00; - // Max PWM timer period - const MAX_PWM_TIMER_PERIOD: u32 = 0x1_00_00; + #[cfg(not(esp_idf_version = "4.3"))] + { + res.lowest_frequency(15.Hz()) + } - // let lowest_frequency = MCPWM_CLOCK_SOURCE_FREQUENCY / group_prescaler_factor / MAX_PWM_TIMER_PRESCALE / MAX_PWM_TIMER_PERIOD; - // let MAX_PWM_TIMER_PRESCALE * MAX_PWM_TIMER_PERIOD * frequency = MCPWM_CLOCK_SOURCE_FREQUENCY / group_prescaler_factor; - // let group_prescaler_factor = MCPWM_CLOCK_SOURCE_FREQUENCY / (MAX_PWM_TIMER_PRESCALE * MAX_PWM_TIMER_PERIOD * frequency); + #[cfg(esp_idf_version = "4.3")] + { + Ok(res) + } + } - // let resolution = MCPWM_CLOCK_SOURCE_FREQUENCY / group_prescaler; + /// Specify lowest reachable frequency + /// + /// The lower this is set, the lower frequencies will be reachable. However, this is at the cost of worse + /// resolution at higher frequencies. + /// + /// Same thing goes for the other way. The higher value set here, the more resolution and so on. + #[cfg(not(esp_idf_version = "4.3"))] + pub fn lowest_frequency(mut self, lowest_frequency: Hertz) -> Result { + // TODO: Do we care about frequency < 1Hz? + let operator_input_frequency = + MAX_PWM_TIMER_PRESCALE * MAX_PWM_TIMER_PERIOD * u32::from(lowest_frequency); + let group_pre_scale = MCPWM_CLOCK_SOURCE_FREQUENCY / operator_input_frequency; + if group_pre_scale > 256 || group_pre_scale < 1 { + return Err(EspError::from(ESP_ERR_INVALID_ARG).unwrap()); + } - // let resolution = MCPWM_CLOCK_SOURCE_FREQUENCY / (MCPWM_CLOCK_SOURCE_FREQUENCY / (MAX_PWM_TIMER_PRESCALE * MAX_PWM_TIMER_PERIOD * frequency)); - // let resolution = (MAX_PWM_TIMER_PRESCALE * MAX_PWM_TIMER_PERIOD * frequency) * 160_000_000 / MCPWM_CLOCK_SOURCE_FREQUENCY; - let resolution = MAX_PWM_TIMER_PRESCALE * MAX_PWM_TIMER_PERIOD * lowest_reachable_frequency; + let resolution = + MAX_PWM_TIMER_PRESCALE * MAX_PWM_TIMER_PERIOD * u32::from(lowest_frequency); + esp!(unsafe { mcpwm_group_set_resolution(U::unit(), resolution) })?; + self.operator_input_frequency = operator_input_frequency; - mcpwm_group_set_resolution(); - todo!() + Ok(self) } } @@ -321,6 +440,7 @@ impl_operator!( // TODO: How do we want syncing to fit in to this? // TODO: How do we want deadtime to fit into this? // TODO: How do we want carrier to fit into this? +// TODO: How do we want capture to fit into this? pub struct Operator, M: Borrow>, PA: OutputPin, PB: OutputPin> { _instance: O, @@ -341,18 +461,28 @@ impl, M: Borrow>, PA: OutputPin, PB: OutputPi pin_a: A, pin_b: B, ) -> Result { - // TODO: Dont forget to make these cases work - #[cfg(idf_newer_than_or_equal_v4_4_0)] + #[cfg(not(esp_idf_version = "4.3"))] { if config.frequency < config.lowest_frequency { - return Err(panic!("TODO: Invalid parameter, should this be checked in OperatorConfig or here or hope that the IDF? will handle the error checking")); + // Can not specify a clock frequency lower then what has + // been configured as the lowest clock frequency + // Use `OperatorConfig::lowest_frequency` to enable lower frequencies + return Err(EspError::from(ESP_ERR_INVALID_ARG).unwrap()); } + + if config.lowest_frequency > mcpwm_module.borrow().operator_input_frequency.Hz() { + // Can not specify a lowest_frequency larger than the corresponding value for + // the parent MCPWM module. Use `Mcpwm::lowest_frequency` to enable higher frequencies + return Err(EspError::from(ESP_ERR_INVALID_ARG).unwrap()); + } + + let resolution = u32::from(config.lowest_frequency) * MAX_PWM_TIMER_PERIOD; unsafe { - esp_idf_sys::mcpwm_timer_set_resolution(); + esp_idf_sys::mcpwm_timer_set_resolution(U::unit(), O::timer(), resolution); } } - // TODO: Handle half pwm frequency when counter_mode = UpDown here? + // TODO: Handle/document half pwm frequency when counter_mode = UpDown? esp!(unsafe { esp_idf_sys::mcpwm_init( @@ -368,6 +498,20 @@ impl, M: Borrow>, PA: OutputPin, PB: OutputPi ) })?; + match config.deadtime { + None => unsafe { + // Only way this can faild is if an invalid timer or unit is specified + // which we know can not happen. So we don't have to check for errors + mcpwm_deadtime_disable(U::unit(), O::timer()); + }, + Some(config) => { + let DeadtimeArgs { red, fed, mode } = config.as_args(); + unsafe { + mcpwm_deadtime_enable(U::unit(), O::timer(), mode, red.into(), fed.into()); + } + } + } + let pin_a: Option = pin_a.into(); let pin_b: Option = pin_b.into(); @@ -390,6 +534,7 @@ impl, M: Borrow>, PA: OutputPin, PB: OutputPi }) } + /// Get duty as percentage between 0.0 and 100.0 pub fn get_duty_a(&self) -> Duty { unsafe { esp_idf_sys::mcpwm_get_duty( @@ -400,6 +545,7 @@ impl, M: Borrow>, PA: OutputPin, PB: OutputPi } } + /// Get duty as percentage between 0.0 and 100.0 pub fn get_duty_b(&self) -> Duty { unsafe { esp_idf_sys::mcpwm_get_duty( @@ -422,6 +568,7 @@ impl, M: Borrow>, PA: OutputPin, PB: OutputPi } } + /// Set duty as percentage between 0.0 and 100.0 pub fn set_duty_b(&mut self, duty: Duty) -> Result<(), EspError> { unsafe { esp!(esp_idf_sys::mcpwm_set_duty( @@ -433,75 +580,18 @@ impl, M: Borrow>, PA: OutputPin, PB: OutputPi } } - pub fn set_frequency(frequency: u32) -> Result<(), EspError> { + /// Set PWM frequency + pub fn set_frequency(frequency: Hertz) -> Result<(), EspError> { unsafe { esp!(esp_idf_sys::mcpwm_set_frequency( U::unit(), O::timer(), - frequency + frequency.into() )) } } - pub fn get_frequency() -> u32 { - unsafe { esp_idf_sys::mcpwm_get_frequency(U::unit(), O::timer()) } - } -} - -pub enum Generator { - A, - B, -} - -// TODO: would we like to also implement PwmPin for something like Foo<&Operator, G: Generator> -// this would allow things like: -// let operator = Operator::new(...); -// let gen_a = operator.generator_a_as_pwm_pin(); -// let gen_b = operator.generator_b_as_pwm_pin(); -// function_expecting_pwm_pin(&gen_a); -// gen_a.set_duty() -// gen_a.set_duty() -impl, M: Borrow>, PA: OutputPin, PB: OutputPin> - embedded_hal_0_2::Pwm for Operator -{ - type Channel = Generator; - type Duty = Duty; - type Time = (); - - fn disable(&mut self, _channel: Self::Channel) { - todo!() - } - - fn enable(&mut self, _channel: Self::Channel) { - todo!() - } - - fn get_period(&self) -> Self::Time { - todo!() - } - - fn get_duty(&self, channel: Self::Channel) -> Self::Duty { - match channel { - Generator::A => self.get_duty_a(), - Generator::B => self.get_duty_b(), - } - } - - fn get_max_duty(&self) -> Self::Duty { - MAX_DUTY - } - - fn set_duty(&mut self, channel: Self::Channel, duty: Self::Duty) { - match channel { - Generator::A => self - .set_duty_a(duty) - .expect("Failed to set duty for generator A"), - Generator::B => self - .set_duty_b(duty) - .expect("Failed to set duty for generator B"), - } - } - fn set_period

(&mut self, _period: P) { - todo!() + pub fn get_frequency() -> Hertz { + Hertz::from(unsafe { esp_idf_sys::mcpwm_get_frequency(U::unit(), O::timer()) }) } } From 7a76c5cd32b37af2367f1a5285a7a0bc92b5b09a Mon Sep 17 00:00:00 2001 From: Albin Hedman Date: Tue, 19 Jul 2022 23:52:23 +0200 Subject: [PATCH 07/23] Clippy --- src/mcpwm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mcpwm.rs b/src/mcpwm.rs index 3825eeca5fb..b8f883430aa 100644 --- a/src/mcpwm.rs +++ b/src/mcpwm.rs @@ -356,7 +356,7 @@ impl Mcpwm { let operator_input_frequency = MAX_PWM_TIMER_PRESCALE * MAX_PWM_TIMER_PERIOD * u32::from(lowest_frequency); let group_pre_scale = MCPWM_CLOCK_SOURCE_FREQUENCY / operator_input_frequency; - if group_pre_scale > 256 || group_pre_scale < 1 { + if !(1..=256).contains(&group_pre_scale) { return Err(EspError::from(ESP_ERR_INVALID_ARG).unwrap()); } From 5ef5013d0d318e8f4b13fd97e55e0b46b28a9dd0 Mon Sep 17 00:00:00 2001 From: Albin Hedman Date: Wed, 20 Jul 2022 00:15:21 +0200 Subject: [PATCH 08/23] Fix warnings in example --- examples/mcpwm-simple.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/mcpwm-simple.rs b/examples/mcpwm-simple.rs index 5876511ce28..259aed7451e 100644 --- a/examples/mcpwm-simple.rs +++ b/examples/mcpwm-simple.rs @@ -1,12 +1,12 @@ -use embedded_hal::delay::blocking::DelayUs; - -use esp_idf_hal::delay::FreeRtos; -use esp_idf_hal::mcpwm::{Mcpwm, Operator, OperatorConfig}; -use esp_idf_hal::prelude::Peripherals; -use esp_idf_hal::units::FromValueType; - #[cfg(any(esp32, esp32s3))] fn main() -> anyhow::Result<()> { + use embedded_hal::delay::blocking::DelayUs; + + use esp_idf_hal::delay::FreeRtos; + use esp_idf_hal::mcpwm::{Mcpwm, Operator, OperatorConfig}; + use esp_idf_hal::prelude::Peripherals; + use esp_idf_hal::units::FromValueType; + esp_idf_sys::link_patches(); println!("Configuring MCPWM"); From eaf4ae7db449fee81f205f88d70eeecfa4dc0f5c Mon Sep 17 00:00:00 2001 From: Albin Hedman Date: Wed, 20 Jul 2022 12:25:04 +0200 Subject: [PATCH 09/23] Fix example --- examples/mcpwm-simple.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/mcpwm-simple.rs b/examples/mcpwm-simple.rs index 259aed7451e..4683362804c 100644 --- a/examples/mcpwm-simple.rs +++ b/examples/mcpwm-simple.rs @@ -37,4 +37,6 @@ fn main() -> anyhow::Result<()> { } #[cfg(not(any(esp32, esp32s3)))] -fn main() {} +fn main() { + esp_idf_sys::link_patches(); +} From 16df098b8bf6e602e4a080cf05fcdc3170809869 Mon Sep 17 00:00:00 2001 From: Albin Hedman Date: Sun, 24 Jul 2022 13:18:26 +0200 Subject: [PATCH 10/23] Add some docs for deadtime modes --- src/mcpwm.rs | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 147 insertions(+), 1 deletion(-) diff --git a/src/mcpwm.rs b/src/mcpwm.rs index b8f883430aa..f2c6c6de925 100644 --- a/src/mcpwm.rs +++ b/src/mcpwm.rs @@ -122,25 +122,171 @@ impl From for mcpwm_counter_type_t { // TODO: Note that `red` and `fed` from the IDF's perspecitve is time as in number of clock cycles after the // MCPWM modules group prescaler. How do we want to expose this? Do we expose it as just that, a cycle count? // Or do we expose it as a time which we then calculate the cycle count from? +/// Deadtime config for MCPWM operator +/// Note that the dead times are calculated from MCPWMXA's flanks unless explicitly stated otherwise #[derive(Copy, Clone, PartialEq, Debug)] pub enum DeadtimeConfig { // TODO: Figure out what all of those options do and give them nice descriptions /// MCPWM_BYPASS_RED + /// + /// . . . + /// . . . + /// .--------------------. . + /// | | . + /// MCPWMXA in | | . + /// | | . + /// --------------- --------------------- + /// . . . + /// . . . + /// .--------------------. . + /// | | . + /// MCPWMXA out | | . + /// | | . + /// --------------- --------------------- + /// . . . + /// . . . + /// .------------------------. + /// | >. |< fed + /// MCPWMXB out | . | + /// | . | + /// --------------. . ----------------- + /// . . . BypassRisingEdge { fed: u16 }, /// MCPWM_BYPASS_FED + /// + /// . . . + /// . . . + /// .--------------------. + /// | . | + /// MCPWMXA in | . | + /// | . | + /// --------------- . --------------------- + /// . . . + /// . . . + /// . .----------------. + /// red >. |< | + /// MCPWMXA out . | | + /// . | | + /// ------------------- --------------------- + /// . . . + /// . . . + /// .--------------------. + /// | . | + /// MCPWMXB out | . | + /// | . | + /// --------------- . --------------------- + /// . . . BypassFallingEdge { red: u16 }, /// MCPWM_ACTIVE_HIGH_MODE + /// + /// . . . . + /// . . . . + /// .--------------------. . + /// | . | . + /// MCPWMXA in | . | . + /// | . | . + /// --------------- . --------------------- + /// . . . . + /// . . . . + /// . .----------------. . + /// red >. |< | . + /// MCPWMXA out . | | . + /// . | | . + /// ------------------- --------------------- + /// . . . . + /// . . . . + /// .------------------------. + /// | . >. |< fed + /// MCPWMXB out | . . | + /// | . . | + /// --------------. . . ----------------- + /// . . . . ActiveHigh { red: u16, fed: u16 }, /// MCPWM_ACTIVE_LOW_MODE + /// + /// . . . . + /// . . . . + /// .--------------------. . + /// | . | . + /// MCPWMXA in | . | . + /// | . | . + /// --------------- . --------------------- + /// . . . . + /// . . . . + /// ------------------. .-------------------- + /// red >. |< | . + /// MCPWMXA out . | | . + /// . | | . + /// . ------------------ + /// . . . . + /// . . . . + /// --------------. . . .---------------- + /// | . >. |< fed + /// MCPWMXB out | . . | + /// | . . | + /// -------------------------- + /// . . . . + /// . . . . ActiveLow { red: u16, fed: u16 }, - /// MCPWM_ACTIVE_HIGH_COMPLIMENT_MODE + // TODO: Is this actually true? -------- + // | + // v + /// MCPWM_ACTIVE_HIGH_COMPLIMENT_MODE - The most common deadtime mode + /// + /// . . . . + /// . . . . + /// .--------------------. . + /// | . | . + /// MCPWMXA in | . | . + /// | . | . + /// --------------- . --------------------- + /// . . . . + /// . . . . + /// . .----------------. . + /// red >. |< | . + /// MCPWMXA out . | | . + /// . | | . + /// ------------------- --------------------- + /// . . . . + /// . . . . + /// --------------. . . .---------------- + /// | . >. |< fed + /// MCPWMXB out | . . | + /// | . . | + /// -------------------------- + /// . . . . + /// . . . . ActiveHighComplement { red: u16, fed: u16 }, /// MCPWM_ACTIVE_LOW_COMPLIMENT_MODE + /// + /// . . . . + /// . . . . + /// .--------------------. . + /// | . | . + /// MCPWMXA in | . | . + /// | . | . + /// --------------- . --------------------- + /// . . . . + /// . . . . + /// ------------------. .-------------------- + /// red >. |< | . + /// MCPWMXA out . | | . + /// . | | . + /// . ------------------ + /// . . . . + /// . . . . + /// .------------------------. + /// | . >. |< fed + /// MCPWMXB out | . . | + /// | . . | + /// --------------- . . ----------------- + /// . . . . + /// . . . . ActiveLowComplement { red: u16, fed: u16 }, /// MCPWM_ACTIVE_RED_FED_FROM_PWMXA From d989ba2cfb68e2eb0d449cc0c47145bd344bdf08 Mon Sep 17 00:00:00 2001 From: Albin Hedman Date: Mon, 25 Jul 2022 13:49:07 +0200 Subject: [PATCH 11/23] Added Mcpwm::operator_source_frequency --- src/mcpwm.rs | 100 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 78 insertions(+), 22 deletions(-) diff --git a/src/mcpwm.rs b/src/mcpwm.rs index f2c6c6de925..c9705722576 100644 --- a/src/mcpwm.rs +++ b/src/mcpwm.rs @@ -119,10 +119,10 @@ impl From for mcpwm_counter_type_t { } } -// TODO: Note that `red` and `fed` from the IDF's perspecitve is time as in number of clock cycles after the -// MCPWM modules group prescaler. How do we want to expose this? Do we expose it as just that, a cycle count? -// Or do we expose it as a time which we then calculate the cycle count from? -/// Deadtime config for MCPWM operator +/// Dead time config for MCPWM operator +/// +/// `red` and `fed` is time as in number of clock cycles after the MCPWM modules group prescaler. +/// /// Note that the dead times are calculated from MCPWMXA's flanks unless explicitly stated otherwise #[derive(Copy, Clone, PartialEq, Debug)] pub enum DeadtimeConfig { @@ -232,10 +232,7 @@ pub enum DeadtimeConfig { /// . . . . ActiveLow { red: u16, fed: u16 }, - // TODO: Is this actually true? -------- - // | - // v - /// MCPWM_ACTIVE_HIGH_COMPLIMENT_MODE - The most common deadtime mode + /// MCPWM_ACTIVE_HIGH_COMPLIMENT_MODE /// /// . . . . /// . . . . @@ -467,7 +464,7 @@ pub struct Mcpwm { /// Those timers in turn have their own prescalers to scale this down even further /// /// NOTE: This is only to be set by calling Self::lowest_frequency - operator_input_frequency: u32, + operator_source_frequency: u32, _instance: MCPWM, } @@ -475,13 +472,15 @@ impl Mcpwm { pub fn new(instance: MCPWM) -> Result { let res = Self { #[cfg(not(esp_idf_version = "4.3"))] - operator_input_frequency: 0, + operator_source_frequency: 0, _instance: instance, }; #[cfg(not(esp_idf_version = "4.3"))] { - res.lowest_frequency(15.Hz()) + // TODO: Do we want to make this into something more builder-pattern like to + // avoid this potentially redundant function call? + res.operator_source_frequency(10.MHz()) } #[cfg(esp_idf_version = "4.3")] @@ -490,26 +489,53 @@ impl Mcpwm { } } + pub fn release(self) -> MCPWM { + // TODO: Do we need to reset any state here such as group_prescaler? + self._instance + } + /// Specify lowest reachable frequency /// /// The lower this is set, the lower frequencies will be reachable. However, this is at the cost of worse /// resolution at higher frequencies. - /// + /// /// Same thing goes for the other way. The higher value set here, the more resolution and so on. #[cfg(not(esp_idf_version = "4.3"))] pub fn lowest_frequency(mut self, lowest_frequency: Hertz) -> Result { // TODO: Do we care about frequency < 1Hz? - let operator_input_frequency = + let operator_source_frequency = MAX_PWM_TIMER_PRESCALE * MAX_PWM_TIMER_PERIOD * u32::from(lowest_frequency); - let group_pre_scale = MCPWM_CLOCK_SOURCE_FREQUENCY / operator_input_frequency; + let group_pre_scale = MCPWM_CLOCK_SOURCE_FREQUENCY / operator_source_frequency; if !(1..=256).contains(&group_pre_scale) { return Err(EspError::from(ESP_ERR_INVALID_ARG).unwrap()); } - let resolution = - MAX_PWM_TIMER_PRESCALE * MAX_PWM_TIMER_PERIOD * u32::from(lowest_frequency); - esp!(unsafe { mcpwm_group_set_resolution(U::unit(), resolution) })?; - self.operator_input_frequency = operator_input_frequency; + esp!(unsafe { mcpwm_group_set_resolution(U::unit(), operator_source_frequency) })?; + self.operator_source_frequency = operator_source_frequency; + + Ok(self) + } + + /// Specify frequency passed to operators timers as clock source + /// + /// The timers of the operators can then in turn scale this frequency down further. + /// + /// The lower this is set, the lower frequencies will be reachable. However, this is + /// at the cost of worse resolution at higher frequencies. Same thing goes for the + /// other way. The higher value set here, the more resolution and so on. + #[cfg(not(esp_idf_version = "4.3"))] + pub fn operator_source_frequency(mut self, frequency: impl Into) -> Result { + let frequency: Hertz = frequency.into(); + let frequency: u32 = frequency.into(); + + // TODO: Do we care about frequency < 1Hz? + let group_pre_scale = MCPWM_CLOCK_SOURCE_FREQUENCY / frequency; + if !(1..=256).contains(&group_pre_scale) { + return Err(EspError::from(ESP_ERR_INVALID_ARG).unwrap()); + } + + esp!(unsafe { mcpwm_group_set_resolution(U::unit(), frequency) })?; + self.operator_source_frequency = frequency; Ok(self) } @@ -584,7 +610,7 @@ impl_operator!( ); // TODO: How do we want syncing to fit in to this? -// TODO: How do we want deadtime to fit into this? +// TODO: How do we want dead time to fit into this? // TODO: How do we want carrier to fit into this? // TODO: How do we want capture to fit into this? @@ -597,8 +623,13 @@ pub struct Operator, M: Borrow>, PA: OutputPi _pin_b: Option, } -impl, M: Borrow>, PA: OutputPin, PB: OutputPin> - Operator +impl Operator +where + U: Unit, + O: HwOperator, + M: Borrow>, + PA: OutputPin, + PB: OutputPin, { pub fn new>, B: Into>>( operator: O, @@ -616,7 +647,7 @@ impl, M: Borrow>, PA: OutputPin, PB: OutputPi return Err(EspError::from(ESP_ERR_INVALID_ARG).unwrap()); } - if config.lowest_frequency > mcpwm_module.borrow().operator_input_frequency.Hz() { + if config.lowest_frequency > mcpwm_module.borrow().operator_source_frequency.Hz() { // Can not specify a lowest_frequency larger than the corresponding value for // the parent MCPWM module. Use `Mcpwm::lowest_frequency` to enable higher frequencies return Err(EspError::from(ESP_ERR_INVALID_ARG).unwrap()); @@ -680,6 +711,31 @@ impl, M: Borrow>, PA: OutputPin, PB: OutputPi }) } + pub fn release(self) -> (O, Option, Option) { + // mcpwm_stop will only fail when invalid args are given + esp!(unsafe { mcpwm_stop(U::unit(), O::timer()) }).unwrap(); + + // TODO: Test and verify if this is the right way + if self._pin_a.is_some() { + // TODO: How to unset pin? + // let io_signal = O::signal_a(); + // mcpwm_gpio_init(U::unit(), io_signal, -1) + // does not seem to be it... + todo!(); + } + + if self._pin_b.is_some() { + // TODO: How to unset pin? + // let io_signal = O::signal_b(); + // mcpwm_gpio_init(U::unit(), io_signal, -1) + // does not seem to be it... + + todo!(); + } + // TODO: Do we need to reset any more state here such as dead time config? + (self._instance, self._pin_a, self._pin_b) + } + /// Get duty as percentage between 0.0 and 100.0 pub fn get_duty_a(&self) -> Duty { unsafe { From 16657d9884a6d3a721dcb0e8558be0dc0af6ab85 Mon Sep 17 00:00:00 2001 From: Albin Hedman Date: Mon, 25 Jul 2022 14:15:21 +0200 Subject: [PATCH 12/23] cargo fmt --- src/mcpwm.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/mcpwm.rs b/src/mcpwm.rs index c9705722576..d0e719937e3 100644 --- a/src/mcpwm.rs +++ b/src/mcpwm.rs @@ -520,11 +520,14 @@ impl Mcpwm { /// /// The timers of the operators can then in turn scale this frequency down further. /// - /// The lower this is set, the lower frequencies will be reachable. However, this is + /// The lower this is set, the lower frequencies will be reachable. However, this is /// at the cost of worse resolution at higher frequencies. Same thing goes for the /// other way. The higher value set here, the more resolution and so on. #[cfg(not(esp_idf_version = "4.3"))] - pub fn operator_source_frequency(mut self, frequency: impl Into) -> Result { + pub fn operator_source_frequency( + mut self, + frequency: impl Into, + ) -> Result { let frequency: Hertz = frequency.into(); let frequency: u32 = frequency.into(); From 0d490fdb8902475391030dcb4af1ff48b5f8ba40 Mon Sep 17 00:00:00 2001 From: Albin Hedman Date: Wed, 27 Jul 2022 13:26:48 +0200 Subject: [PATCH 13/23] Add more documentation --- examples/mcpwm-simple.rs | 64 ++++++++++- src/mcpwm.rs | 242 +++++++++++++++++++++++++++++++-------- 2 files changed, 251 insertions(+), 55 deletions(-) diff --git a/examples/mcpwm-simple.rs b/examples/mcpwm-simple.rs index 4683362804c..bcc3d849e1d 100644 --- a/examples/mcpwm-simple.rs +++ b/examples/mcpwm-simple.rs @@ -1,3 +1,57 @@ +/// # x = 10 +/// +/// . . +/// . . +/// .------. .------. +/// | | | | +/// pin4 | | | | +/// | | | | +/// ----- -------------------------- -------------------------- +/// . . +/// . . +/// .------------------------. .------------------------. +/// | | | | +/// pin5 | | | | +/// | | | | +/// ----- -------- -------- +/// . . +/// +/// +/// # x = 50 +/// . . +/// . . +/// .---------------. .---------------. +/// | | | | +/// pin4 | | | | +/// | | | | +/// ----- ----------------- ----------------- +/// . . +/// . . +/// .---------------. .---------------. +/// | | | | +/// pin5 | | | | +/// | | | | +/// ----- ----------------- ----------------- +/// . . +/// +/// +/// # x = 90 +/// . . +/// . . +/// .------------------------. .------------------------. +/// | | | | +/// pin4 | | | | +/// | | | | +/// ----- -------- -------- +/// . . +/// . . +/// .------. .------. +/// | | | | +/// pin5 | | | | +/// | | | | +/// ----- -------------------------- -------------------------- +/// . . + #[cfg(any(esp32, esp32s3))] fn main() -> anyhow::Result<()> { use embedded_hal::delay::blocking::DelayUs; @@ -12,7 +66,7 @@ fn main() -> anyhow::Result<()> { println!("Configuring MCPWM"); let peripherals = Peripherals::take().unwrap(); - let config = OperatorConfig::default().frequency(25.kHz().into()); + let config = OperatorConfig::default().frequency(25.kHz()); let mcpwm = Mcpwm::new(peripherals.mcpwm0.mcpwm)?; let mut operator = Operator::new( peripherals.mcpwm0.operator0, @@ -24,10 +78,10 @@ fn main() -> anyhow::Result<()> { println!("Starting duty-cycle loop"); - for &duty in [0.0, 20.0, 40.0, 60.0, 80.0, 100.0].iter().cycle() { - println!("Duty {}%", duty); - operator.set_duty_a(duty)?; - operator.set_duty_b(100.0 - duty)?; + for &x in [0.0, 20.0, 40.0, 60.0, 80.0, 100.0].iter().cycle() { + println!("Duty {}%", x); + operator.set_duty_a(x)?; + operator.set_duty_b(100.0 - x)?; FreeRtos.delay_ms(2000)?; } diff --git a/src/mcpwm.rs b/src/mcpwm.rs index d0e719937e3..4ac16d9c512 100644 --- a/src/mcpwm.rs +++ b/src/mcpwm.rs @@ -19,6 +19,8 @@ //! --------------- --------------- //! ``` //! +//! # Example +//! //! Create a pair of PWM signals on pin 4 and 5. The duty on pin 4 will ramp from 0% to 100% //! while pin 5 will ramp from 100% down to 0%. //! ``` @@ -82,9 +84,19 @@ impl Peripheral { } } +/// Duty modes for operator #[derive(Clone, Copy, Debug)] pub enum DutyMode { + /// Active high + /// + /// Setting duty = 100% will result in a constant high output + /// Setting duty = 0% will result in a constant low output ActiveHigh, + + /// Active low + /// + /// Setting duty = 100% will result in a constant low output + /// Setting duty = 0% will result in a constant high output ActiveLow, } @@ -97,13 +109,64 @@ impl From for mcpwm_duty_type_t { } } +/// Counter mode for operator's timer for generating PWM signal // TODO: For UpDown, frequency is half of MCPWM frequency set #[derive(Clone, Copy, Debug)] pub enum CounterMode { + /// Timer is frozen or paused #[cfg(not(esp_idf_version = "4.3"))] Frozen, + /// Edge aligned. The counter will start from its lowest value and increment every clock cycle until the period is reached. + /// + /// The wave form will end up looking something like the following: + /// ``` + /// start, counter = 0 reset, counter = period + /// | | + /// | |*--- start, counter = 0 + /// v <---- duty ----> . v| + /// . . .v + /// .--------------------. ..---- + /// | Active | .| + /// | | .| + /// | | Not active .| + /// - --------------------- + /// ``` Up, + + /// Edge aligned. The counter will start from its highest value, period and decrement every clock cycle until the zero is reached + /// + /// The wave form will end up looking something like the following: + /// ``` + /// start, counter = period reset, counter = 0 + /// | | + /// | |*--- start, counter = period + /// v . v| + /// . . <---- duty ----> .v + /// . .--------------------.. + /// . Active | |. + /// . | |. + /// . Not active | Active |. + /// ---------------------- ---- + /// ``` Down, + + /// Symmetric mode. The counter will start from its lowest value and increment every clock cycle until the period is reached + /// + /// The wave form will end up looking something like the following: + /// ``` + /// change count dir to decrement, counter = period + /// start, counter = 0, incrementing | change count dir to increment, counter = 0 + /// | | | + /// | |*--- counter = period |*----- start, counter = 0, incrementing + /// v <---- duty ----> . v| . <---- duty ----> || + /// . . .v . vv + /// ---------------------. .. .-------------------------------------------. .. .-- + /// Active | .. | Active Active | .. | + /// | .. | | .. | + /// | Not active .. Not active | | Not active .. Not active | + /// ---------------------------------------- ---------------------------------------- + /// ``` + /// NOTE: That in this mode, the frequency will be half of that specified UpDown, } @@ -121,7 +184,7 @@ impl From for mcpwm_counter_type_t { /// Dead time config for MCPWM operator /// -/// `red` and `fed` is time as in number of clock cycles after the MCPWM modules group prescaler. +/// `rising_edge_delay` and `falling_edge_delay` is time as in number of clock cycles after the MCPWM modules group prescaler. /// /// Note that the dead times are calculated from MCPWMXA's flanks unless explicitly stated otherwise #[derive(Copy, Clone, PartialEq, Debug)] @@ -129,6 +192,7 @@ pub enum DeadtimeConfig { // TODO: Figure out what all of those options do and give them nice descriptions /// MCPWM_BYPASS_RED /// + /// ``` /// . . . /// . . . /// .--------------------. . @@ -151,10 +215,13 @@ pub enum DeadtimeConfig { /// | . | /// --------------. . ----------------- /// . . . - BypassRisingEdge { fed: u16 }, + /// . . . . + /// ``` + BypassRisingEdge { falling_edge_delay: u16 }, /// MCPWM_BYPASS_FED /// + /// ``` /// . . . /// . . . /// .--------------------. @@ -177,10 +244,13 @@ pub enum DeadtimeConfig { /// | . | /// --------------- . --------------------- /// . . . - BypassFallingEdge { red: u16 }, + /// . . . . + /// ``` + BypassFallingEdge { rising_edge_delay: u16 }, /// MCPWM_ACTIVE_HIGH_MODE /// + /// ``` /// . . . . /// . . . . /// .--------------------. . @@ -203,10 +273,16 @@ pub enum DeadtimeConfig { /// | . . | /// --------------. . . ----------------- /// . . . . - ActiveHigh { red: u16, fed: u16 }, + /// . . . . + /// ``` + ActiveHigh { + rising_edge_delay: u16, + falling_edge_delay: u16, + }, /// MCPWM_ACTIVE_LOW_MODE /// + /// ``` /// . . . . /// . . . . /// .--------------------. . @@ -230,10 +306,15 @@ pub enum DeadtimeConfig { /// -------------------------- /// . . . . /// . . . . - ActiveLow { red: u16, fed: u16 }, + /// ``` + ActiveLow { + rising_edge_delay: u16, + falling_edge_delay: u16, + }, /// MCPWM_ACTIVE_HIGH_COMPLIMENT_MODE /// + /// ``` /// . . . . /// . . . . /// .--------------------. . @@ -257,10 +338,15 @@ pub enum DeadtimeConfig { /// -------------------------- /// . . . . /// . . . . - ActiveHighComplement { red: u16, fed: u16 }, + /// ``` + ActiveHighComplement { + rising_edge_delay: u16, + falling_edge_delay: u16, + }, /// MCPWM_ACTIVE_LOW_COMPLIMENT_MODE /// + /// ``` /// . . . . /// . . . . /// .--------------------. . @@ -284,75 +370,104 @@ pub enum DeadtimeConfig { /// --------------- . . ----------------- /// . . . . /// . . . . - ActiveLowComplement { red: u16, fed: u16 }, + /// ``` + ActiveLowComplement { + rising_edge_delay: u16, + falling_edge_delay: u16, + }, /// MCPWM_ACTIVE_RED_FED_FROM_PWMXA - ActiveRedFedFromPwmxa { red: u16, fed: u16 }, + ActiveRedFedFromPwmxa { + rising_edge_delay: u16, + falling_edge_delay: u16, + }, /// MCPWM_ACTIVE_RED_FED_FROM_PWMXB - ActiveRedFedFromPwmxb { red: u16, fed: u16 }, -} - -struct DeadtimeArgs { - red: u16, - fed: u16, - mode: mcpwm_deadtime_type_t, + ActiveRedFedFromPwmxb { + rising_edge_delay: u16, + falling_edge_delay: u16, + }, } impl DeadtimeConfig { fn as_args(&self) -> DeadtimeArgs { match *self { - DeadtimeConfig::BypassRisingEdge { fed } => DeadtimeArgs { - red: 0, - fed, + DeadtimeConfig::BypassRisingEdge { falling_edge_delay } => DeadtimeArgs { + rising_edge_delay: 0, + falling_edge_delay, mode: mcpwm_deadtime_type_t_MCPWM_BYPASS_RED, }, - DeadtimeConfig::BypassFallingEdge { red } => DeadtimeArgs { - red, - fed: 0, + DeadtimeConfig::BypassFallingEdge { rising_edge_delay } => DeadtimeArgs { + rising_edge_delay, + falling_edge_delay: 0, mode: mcpwm_deadtime_type_t_MCPWM_BYPASS_FED, }, - DeadtimeConfig::ActiveHigh { red, fed } => DeadtimeArgs { - red, - fed, + DeadtimeConfig::ActiveHigh { + rising_edge_delay, + falling_edge_delay, + } => DeadtimeArgs { + rising_edge_delay, + falling_edge_delay, mode: mcpwm_deadtime_type_t_MCPWM_ACTIVE_HIGH_MODE, }, - DeadtimeConfig::ActiveLow { red, fed } => DeadtimeArgs { - red, - fed, + DeadtimeConfig::ActiveLow { + rising_edge_delay, + falling_edge_delay, + } => DeadtimeArgs { + rising_edge_delay, + falling_edge_delay, mode: mcpwm_deadtime_type_t_MCPWM_ACTIVE_LOW_MODE, }, - DeadtimeConfig::ActiveHighComplement { red, fed } => DeadtimeArgs { - red, - fed, + DeadtimeConfig::ActiveHighComplement { + rising_edge_delay, + falling_edge_delay, + } => DeadtimeArgs { + rising_edge_delay, + falling_edge_delay, mode: mcpwm_deadtime_type_t_MCPWM_ACTIVE_HIGH_COMPLIMENT_MODE, }, - DeadtimeConfig::ActiveLowComplement { red, fed } => DeadtimeArgs { - red, - fed, + DeadtimeConfig::ActiveLowComplement { + rising_edge_delay, + falling_edge_delay, + } => DeadtimeArgs { + rising_edge_delay, + falling_edge_delay, mode: mcpwm_deadtime_type_t_MCPWM_ACTIVE_LOW_COMPLIMENT_MODE, }, - DeadtimeConfig::ActiveRedFedFromPwmxa { red, fed } => DeadtimeArgs { - red, - fed, + DeadtimeConfig::ActiveRedFedFromPwmxa { + rising_edge_delay, + falling_edge_delay, + } => DeadtimeArgs { + rising_edge_delay, + falling_edge_delay, mode: mcpwm_deadtime_type_t_MCPWM_ACTIVE_RED_FED_FROM_PWMXA, }, - DeadtimeConfig::ActiveRedFedFromPwmxb { red, fed } => DeadtimeArgs { - red, - fed, + DeadtimeConfig::ActiveRedFedFromPwmxb { + rising_edge_delay, + falling_edge_delay, + } => DeadtimeArgs { + rising_edge_delay, + falling_edge_delay, mode: mcpwm_deadtime_type_t_MCPWM_ACTIVE_RED_FED_FROM_PWMXB, }, } } } +struct DeadtimeArgs { + rising_edge_delay: u16, + falling_edge_delay: u16, + mode: mcpwm_deadtime_type_t, +} + +/// Operator configuration pub struct OperatorConfig { frequency: Hertz, duty_a: Duty, @@ -367,19 +482,32 @@ pub struct OperatorConfig { } impl OperatorConfig { + /// Frequency which the operator will run at, can also be changed live later #[must_use] - pub fn frequency(mut self, frequency: Hertz) -> Self { - self.frequency = frequency; + pub fn frequency(mut self, frequency: impl Into) -> Self { + self.frequency = frequency.into(); self } + /// Lowest frequency which the operator needs to be able to reach + /// + /// This setting will limit what frequency range the operator can reach. + /// Setting a low value here will lead to worse resolution at higher + /// frequencies. A high value on the other hand will prevent any frequencies + /// lower than that. + /// + /// Thus, for maximum resolution set lowest_frequency to the lowest expected + /// frequency which will be used. + /// + /// NOTE: This value can currently not be changed live #[cfg(not(esp_idf_version = "4.3"))] #[must_use] - pub fn lowest_frequency(mut self, lowest_frequency: Hertz) -> Self { - self.lowest_frequency = lowest_frequency; + pub fn lowest_frequency(mut self, lowest_frequency: impl Into) -> Self { + self.lowest_frequency = lowest_frequency.into(); self } + /// Specify what duty mode to use for the operator. #[must_use] pub fn duty_mode(mut self, duty_mode: DutyMode) -> Self { self.duty_mode = duty_mode; @@ -457,7 +585,7 @@ impl Unit for UnitOne { } // TODO: How do we want fault module to fit into this? - +/// Motor Control PWM module abstraction pub struct Mcpwm { #[cfg(not(esp_idf_version = "4.3"))] /// This is the frequency of the clock signal passed on as clock source for the operators timers @@ -617,6 +745,10 @@ impl_operator!( // TODO: How do we want carrier to fit into this? // TODO: How do we want capture to fit into this? +/// Motor Control operator abstraction +/// +/// Every Motor Control module has three operators. Every operator can generate two output signals called A and B. +/// A and B share the same timer and thus frequency and phase but can have induvidual duty set. pub struct Operator, M: Borrow>, PA: OutputPin, PB: OutputPin> { _instance: O, _unit: U, @@ -685,9 +817,19 @@ where mcpwm_deadtime_disable(U::unit(), O::timer()); }, Some(config) => { - let DeadtimeArgs { red, fed, mode } = config.as_args(); + let DeadtimeArgs { + rising_edge_delay, + falling_edge_delay, + mode, + } = config.as_args(); unsafe { - mcpwm_deadtime_enable(U::unit(), O::timer(), mode, red.into(), fed.into()); + mcpwm_deadtime_enable( + U::unit(), + O::timer(), + mode, + rising_edge_delay.into(), + falling_edge_delay.into(), + ); } } } @@ -739,7 +881,7 @@ where (self._instance, self._pin_a, self._pin_b) } - /// Get duty as percentage between 0.0 and 100.0 + /// Get duty as percentage between 0.0 and 100.0 for output A pub fn get_duty_a(&self) -> Duty { unsafe { esp_idf_sys::mcpwm_get_duty( @@ -750,7 +892,7 @@ where } } - /// Get duty as percentage between 0.0 and 100.0 + /// Get duty as percentage between 0.0 and 100.0 for output B pub fn get_duty_b(&self) -> Duty { unsafe { esp_idf_sys::mcpwm_get_duty( @@ -761,7 +903,7 @@ where } } - /// Set duty as percentage between 0.0 and 100.0 + /// Set duty as percentage between 0.0 and 100.0 for output A pub fn set_duty_a(&mut self, duty: Duty) -> Result<(), EspError> { unsafe { esp!(esp_idf_sys::mcpwm_set_duty( @@ -773,7 +915,7 @@ where } } - /// Set duty as percentage between 0.0 and 100.0 + /// Set duty as percentage between 0.0 and 100.0 for output B pub fn set_duty_b(&mut self, duty: Duty) -> Result<(), EspError> { unsafe { esp!(esp_idf_sys::mcpwm_set_duty( From b17a9ffd4cf96345faa3f91661e4f9086bbd3638 Mon Sep 17 00:00:00 2001 From: Albin Hedman Date: Fri, 5 Aug 2022 18:08:46 +0200 Subject: [PATCH 14/23] Update example(crashing) --- examples/mcpwm-simple.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/examples/mcpwm-simple.rs b/examples/mcpwm-simple.rs index bcc3d849e1d..f91fd6c33a6 100644 --- a/examples/mcpwm-simple.rs +++ b/examples/mcpwm-simple.rs @@ -78,16 +78,19 @@ fn main() -> anyhow::Result<()> { println!("Starting duty-cycle loop"); - for &x in [0.0, 20.0, 40.0, 60.0, 80.0, 100.0].iter().cycle() { - println!("Duty {}%", x); - operator.set_duty_a(x)?; - operator.set_duty_b(100.0 - x)?; - FreeRtos.delay_ms(2000)?; - } + for x in (0..10000u16).cycle() { + let duty = f32::from(x) * 0.01; + + if x % 100 == 0 { + println!("Duty {}%", duty); + } - loop { - FreeRtos.delay_ms(1000)?; + operator.set_duty_a(duty)?; + operator.set_duty_b(100.0 - duty)?; + FreeRtos.delay_ms(10)?; } + + unreachable!() } #[cfg(not(any(esp32, esp32s3)))] From 0af99da291da3d96ae0a72a3191c0c36c95fdd14 Mon Sep 17 00:00:00 2001 From: Albin Hedman Date: Mon, 8 Aug 2022 19:11:00 +0200 Subject: [PATCH 15/23] Work around float printing issue --- examples/mcpwm-simple.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/mcpwm-simple.rs b/examples/mcpwm-simple.rs index f91fd6c33a6..c99d3cac605 100644 --- a/examples/mcpwm-simple.rs +++ b/examples/mcpwm-simple.rs @@ -82,7 +82,7 @@ fn main() -> anyhow::Result<()> { let duty = f32::from(x) * 0.01; if x % 100 == 0 { - println!("Duty {}%", duty); + println!("Duty {}%", x / 100); } operator.set_duty_a(duty)?; From b2ae6e772e02cdf2a8d43b51b0cb2ca5fef69d66 Mon Sep 17 00:00:00 2001 From: Albin Hedman Date: Mon, 8 Aug 2022 19:20:50 +0200 Subject: [PATCH 16/23] Clarify deadtime --- src/mcpwm.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/mcpwm.rs b/src/mcpwm.rs index 4ac16d9c512..38e65110d29 100644 --- a/src/mcpwm.rs +++ b/src/mcpwm.rs @@ -192,6 +192,9 @@ pub enum DeadtimeConfig { // TODO: Figure out what all of those options do and give them nice descriptions /// MCPWM_BYPASS_RED /// + /// Note that `MCPWMXB in` will be completely ignored. This means `Operator::set_duty_b` will + /// have no effect with this dead time mode + /// /// ``` /// . . . /// . . . @@ -221,6 +224,9 @@ pub enum DeadtimeConfig { /// MCPWM_BYPASS_FED /// + /// Note that `MCPWMXB in` will be completely ignored. This means `Operator::set_duty_b` will + /// have no effect with this dead time mode + /// /// ``` /// . . . /// . . . @@ -250,6 +256,9 @@ pub enum DeadtimeConfig { /// MCPWM_ACTIVE_HIGH_MODE /// + /// Note that `MCPWMXB in` will be completely ignored. This means `Operator::set_duty_b` will + /// have no effect with this dead time mode + /// /// ``` /// . . . . /// . . . . @@ -282,6 +291,9 @@ pub enum DeadtimeConfig { /// MCPWM_ACTIVE_LOW_MODE /// + /// Note that `MCPWMXB in` will be completely ignored. This means `Operator::set_duty_b` will + /// have no effect with this dead time mode + /// /// ``` /// . . . . /// . . . . @@ -314,6 +326,9 @@ pub enum DeadtimeConfig { /// MCPWM_ACTIVE_HIGH_COMPLIMENT_MODE /// + /// Note that `MCPWMXB in` will be completely ignored. This means `Operator::set_duty_b` will + /// have no effect with this dead time mode + /// /// ``` /// . . . . /// . . . . @@ -346,6 +361,9 @@ pub enum DeadtimeConfig { /// MCPWM_ACTIVE_LOW_COMPLIMENT_MODE /// + /// Note that `MCPWMXB in` will be completely ignored. This means `Operator::set_duty_b` will + /// have no effect with this dead time mode + /// /// ``` /// . . . . /// . . . . @@ -377,12 +395,18 @@ pub enum DeadtimeConfig { }, /// MCPWM_ACTIVE_RED_FED_FROM_PWMXA + /// + /// Note that `MCPWMXB in` will be completely ignored. This means `Operator::set_duty_b` will + /// have no effect with this dead time mode ActiveRedFedFromPwmxa { rising_edge_delay: u16, falling_edge_delay: u16, }, /// MCPWM_ACTIVE_RED_FED_FROM_PWMXB + /// + /// Note that `MCPWMXA in` will be completely ignored. This means `Operator::set_duty_a` will + /// have no effect with this dead time mode ActiveRedFedFromPwmxb { rising_edge_delay: u16, falling_edge_delay: u16, From 4820189b4833e65eda98c503a89390f624ee2161 Mon Sep 17 00:00:00 2001 From: Albin Hedman Date: Mon, 8 Aug 2022 19:23:17 +0200 Subject: [PATCH 17/23] Some cleanup --- src/mcpwm.rs | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/src/mcpwm.rs b/src/mcpwm.rs index 38e65110d29..27fa4715bda 100644 --- a/src/mcpwm.rs +++ b/src/mcpwm.rs @@ -659,7 +659,7 @@ impl Mcpwm { MAX_PWM_TIMER_PRESCALE * MAX_PWM_TIMER_PERIOD * u32::from(lowest_frequency); let group_pre_scale = MCPWM_CLOCK_SOURCE_FREQUENCY / operator_source_frequency; if !(1..=256).contains(&group_pre_scale) { - return Err(EspError::from(ESP_ERR_INVALID_ARG).unwrap()); + esp!(ESP_ERR_INVALID_ARG)?; } esp!(unsafe { mcpwm_group_set_resolution(U::unit(), operator_source_frequency) })?; @@ -686,7 +686,7 @@ impl Mcpwm { // TODO: Do we care about frequency < 1Hz? let group_pre_scale = MCPWM_CLOCK_SOURCE_FREQUENCY / frequency; if !(1..=256).contains(&group_pre_scale) { - return Err(EspError::from(ESP_ERR_INVALID_ARG).unwrap()); + esp!(ESP_ERR_INVALID_ARG)?; } esp!(unsafe { mcpwm_group_set_resolution(U::unit(), frequency) })?; @@ -803,13 +803,13 @@ where // Can not specify a clock frequency lower then what has // been configured as the lowest clock frequency // Use `OperatorConfig::lowest_frequency` to enable lower frequencies - return Err(EspError::from(ESP_ERR_INVALID_ARG).unwrap()); + esp!(ESP_ERR_INVALID_ARG)?; } if config.lowest_frequency > mcpwm_module.borrow().operator_source_frequency.Hz() { // Can not specify a lowest_frequency larger than the corresponding value for // the parent MCPWM module. Use `Mcpwm::lowest_frequency` to enable higher frequencies - return Err(EspError::from(ESP_ERR_INVALID_ARG).unwrap()); + esp!(ESP_ERR_INVALID_ARG)?; } let resolution = u32::from(config.lowest_frequency) * MAX_PWM_TIMER_PERIOD; @@ -884,23 +884,18 @@ where // mcpwm_stop will only fail when invalid args are given esp!(unsafe { mcpwm_stop(U::unit(), O::timer()) }).unwrap(); - // TODO: Test and verify if this is the right way - if self._pin_a.is_some() { - // TODO: How to unset pin? - // let io_signal = O::signal_a(); - // mcpwm_gpio_init(U::unit(), io_signal, -1) - // does not seem to be it... - todo!(); + // Detatch pins from MCPWM operator + if let Some(_pin) = &self._pin_a { + // TODO + //pin.reset(); } - if self._pin_b.is_some() { - // TODO: How to unset pin? - // let io_signal = O::signal_b(); - // mcpwm_gpio_init(U::unit(), io_signal, -1) - // does not seem to be it... - - todo!(); + // Detatch pins from MCPWM operator + if let Some(_pin) = &self._pin_b { + // TODO + //pin.reset(); } + // TODO: Do we need to reset any more state here such as dead time config? (self._instance, self._pin_a, self._pin_b) } From a12baad9260c86cbe95878a144bd7d66bdbe20fd Mon Sep 17 00:00:00 2001 From: Albin Hedman Date: Mon, 8 Aug 2022 19:24:03 +0200 Subject: [PATCH 18/23] Fix some methods missing their self --- src/mcpwm.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mcpwm.rs b/src/mcpwm.rs index 27fa4715bda..dcc0cd3216b 100644 --- a/src/mcpwm.rs +++ b/src/mcpwm.rs @@ -947,7 +947,7 @@ where } /// Set PWM frequency - pub fn set_frequency(frequency: Hertz) -> Result<(), EspError> { + pub fn set_frequency(&mut self, frequency: Hertz) -> Result<(), EspError> { unsafe { esp!(esp_idf_sys::mcpwm_set_frequency( U::unit(), @@ -957,7 +957,7 @@ where } } - pub fn get_frequency() -> Hertz { + pub fn get_frequency(&self) -> Hertz { Hertz::from(unsafe { esp_idf_sys::mcpwm_get_frequency(U::unit(), O::timer()) }) } } From 07691e71d3f7ca96bb3f21a6660160a4b9185fdd Mon Sep 17 00:00:00 2001 From: Albin Hedman Date: Fri, 12 Aug 2022 17:16:39 +0200 Subject: [PATCH 19/23] Final(?) adjustments to doc --- src/mcpwm.rs | 63 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 57 insertions(+), 6 deletions(-) diff --git a/src/mcpwm.rs b/src/mcpwm.rs index dcc0cd3216b..3d22952aefc 100644 --- a/src/mcpwm.rs +++ b/src/mcpwm.rs @@ -218,7 +218,7 @@ pub enum DeadtimeConfig { /// | . | /// --------------. . ----------------- /// . . . - /// . . . . + /// . . . /// ``` BypassRisingEdge { falling_edge_delay: u16 }, @@ -250,7 +250,7 @@ pub enum DeadtimeConfig { /// | . | /// --------------- . --------------------- /// . . . - /// . . . . + /// . . . /// ``` BypassFallingEdge { rising_edge_delay: u16 }, @@ -378,7 +378,7 @@ pub enum DeadtimeConfig { /// red >. |< | . /// MCPWMXA out . | | . /// . | | . - /// . ------------------ + /// . ------------------ . /// . . . . /// . . . . /// .------------------------. @@ -398,6 +398,32 @@ pub enum DeadtimeConfig { /// /// Note that `MCPWMXB in` will be completely ignored. This means `Operator::set_duty_b` will /// have no effect with this dead time mode + /// + /// ``` + /// . . . . + /// . . . . + /// .--------------------. . + /// | . | . + /// MCPWMXA in | . | . + /// | . | . + /// --------------- . --------------------- + /// . . . . + /// . . . . + /// . .--------------------. + /// red >. |< . | + /// MCPWMXA out . | . | + /// . | . | + /// ------------------- . --------------------- + /// . . . . + /// . . . . + /// . .--------------------. + /// red >. |< . | + /// MCPWMXB out . | . | + /// . | . | + /// ------------------- . --------------------- + /// . . . . + /// . . . . + /// ``` ActiveRedFedFromPwmxa { rising_edge_delay: u16, falling_edge_delay: u16, @@ -407,6 +433,31 @@ pub enum DeadtimeConfig { /// /// Note that `MCPWMXA in` will be completely ignored. This means `Operator::set_duty_a` will /// have no effect with this dead time mode + /// ``` + /// . . . . + /// . . . . + /// .--------------------. . + /// | . | . + /// MCPWMXB in | . | . + /// | . | . + /// --------------- . --------------------- + /// . . . . + /// . . . . + /// . .--------------------. + /// red >. |< . | + /// MCPWMXA out . | . | + /// . | . | + /// ------------------- . --------------------- + /// . . . . + /// . . . . + /// . .--------------------. + /// red >. |< . | + /// MCPWMXB out . | . | + /// . | . | + /// ------------------- . --------------------- + /// . . . . + /// . . . . + /// ``` ActiveRedFedFromPwmxb { rising_edge_delay: u16, falling_edge_delay: u16, @@ -765,7 +816,6 @@ impl_operator!( ); // TODO: How do we want syncing to fit in to this? -// TODO: How do we want dead time to fit into this? // TODO: How do we want carrier to fit into this? // TODO: How do we want capture to fit into this? @@ -880,7 +930,7 @@ where }) } - pub fn release(self) -> (O, Option, Option) { + /*pub fn release(self) -> (O, Option, Option) { // mcpwm_stop will only fail when invalid args are given esp!(unsafe { mcpwm_stop(U::unit(), O::timer()) }).unwrap(); @@ -898,7 +948,7 @@ where // TODO: Do we need to reset any more state here such as dead time config? (self._instance, self._pin_a, self._pin_b) - } + }*/ /// Get duty as percentage between 0.0 and 100.0 for output A pub fn get_duty_a(&self) -> Duty { @@ -957,6 +1007,7 @@ where } } + /// Get PWM frequency pub fn get_frequency(&self) -> Hertz { Hertz::from(unsafe { esp_idf_sys::mcpwm_get_frequency(U::unit(), O::timer()) }) } From 295f0bcd3faa328843f26b1444933d8b68130c3a Mon Sep 17 00:00:00 2001 From: Albin Hedman Date: Fri, 12 Aug 2022 17:23:45 +0200 Subject: [PATCH 20/23] Explain example mcpwm-simple --- examples/mcpwm-simple.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/examples/mcpwm-simple.rs b/examples/mcpwm-simple.rs index c99d3cac605..07f99d73676 100644 --- a/examples/mcpwm-simple.rs +++ b/examples/mcpwm-simple.rs @@ -1,4 +1,8 @@ -/// # x = 10 +/// Simple example showing how MCPWM may be used to generate two synchronized pwm signals with varying duty. +/// The duty on the pin4 will increase from 0% all the way up to 100% and repeat. At the same time the duty +/// on pin 5 will go from 100% down to 0% +/// +/// # duty = 10 /// /// . . /// . . @@ -17,7 +21,7 @@ /// . . /// /// -/// # x = 50 +/// # duty = 50 /// . . /// . . /// .---------------. .---------------. @@ -35,7 +39,7 @@ /// . . /// /// -/// # x = 90 +/// # duty = 90 /// . . /// . . /// .------------------------. .------------------------. From 018056a33d630532c00bdb71b7320980b9961f53 Mon Sep 17 00:00:00 2001 From: Albin Hedman Date: Wed, 31 Aug 2022 14:02:51 +0200 Subject: [PATCH 21/23] Allow setting initial duty in OperatorConfig --- src/mcpwm.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/mcpwm.rs b/src/mcpwm.rs index 3d22952aefc..3b037a3e13b 100644 --- a/src/mcpwm.rs +++ b/src/mcpwm.rs @@ -564,6 +564,20 @@ impl OperatorConfig { self } + /// Set initual duty cycle for output A as percentage + #[must_use] + pub fn duty_a(mut self, duty: Duty) -> Self { + self.duty_a = duty; + self + } + + /// Set initual duty cycle for output B as percentage + #[must_use] + pub fn duty_b(mut self, duty: Duty) -> Self { + self.duty_b = duty; + self + } + /// Lowest frequency which the operator needs to be able to reach /// /// This setting will limit what frequency range the operator can reach. From 9b9d9f6706786db9aa2e9354d8e222811c82e0b8 Mon Sep 17 00:00:00 2001 From: Albin Hedman Date: Wed, 31 Aug 2022 14:05:28 +0200 Subject: [PATCH 22/23] Clamp timer resolution to not be greater than timer group resolution --- src/mcpwm.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mcpwm.rs b/src/mcpwm.rs index 3b037a3e13b..051df1868ef 100644 --- a/src/mcpwm.rs +++ b/src/mcpwm.rs @@ -870,13 +870,15 @@ where esp!(ESP_ERR_INVALID_ARG)?; } - if config.lowest_frequency > mcpwm_module.borrow().operator_source_frequency.Hz() { + let operator_source_frequency = mcpwm_module.borrow().operator_source_frequency; + if config.lowest_frequency > operator_source_frequency.Hz() { // Can not specify a lowest_frequency larger than the corresponding value for // the parent MCPWM module. Use `Mcpwm::lowest_frequency` to enable higher frequencies esp!(ESP_ERR_INVALID_ARG)?; } let resolution = u32::from(config.lowest_frequency) * MAX_PWM_TIMER_PERIOD; + let resolution = resolution.min(operator_source_frequency); unsafe { esp_idf_sys::mcpwm_timer_set_resolution(U::unit(), O::timer(), resolution); } From 52e45423983fc1edfd881d12894a6118ad164316 Mon Sep 17 00:00:00 2001 From: Albin Hedman Date: Wed, 31 Aug 2022 14:06:42 +0200 Subject: [PATCH 23/23] Expose Mcpwm's unit and Operator's timer --- src/mcpwm.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/mcpwm.rs b/src/mcpwm.rs index 051df1868ef..480f6f5e3eb 100644 --- a/src/mcpwm.rs +++ b/src/mcpwm.rs @@ -187,7 +187,7 @@ impl From for mcpwm_counter_type_t { /// `rising_edge_delay` and `falling_edge_delay` is time as in number of clock cycles after the MCPWM modules group prescaler. /// /// Note that the dead times are calculated from MCPWMXA's flanks unless explicitly stated otherwise -#[derive(Copy, Clone, PartialEq, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum DeadtimeConfig { // TODO: Figure out what all of those options do and give them nice descriptions /// MCPWM_BYPASS_RED @@ -759,6 +759,10 @@ impl Mcpwm { Ok(self) } + + pub fn unit(&self) -> mcpwm_unit_t { + U::unit() + } } // The hardware for ESP32 and ESP32-S3 can associate any operator(within the mcpwm module) with any @@ -1027,4 +1031,8 @@ where pub fn get_frequency(&self) -> Hertz { Hertz::from(unsafe { esp_idf_sys::mcpwm_get_frequency(U::unit(), O::timer()) }) } + + pub fn timer(&self) -> mcpwm_timer_t { + O::timer() + } }