diff --git a/examples/mcpwm-simple.rs b/examples/mcpwm-simple.rs new file mode 100644 index 00000000000..5921d82d59d --- /dev/null +++ b/examples/mcpwm-simple.rs @@ -0,0 +1,112 @@ +/// 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% +/// +/// . . +/// . . +/// .------. .------. +/// | | | | +/// pin4 | | | | +/// | | | | +/// ----- -------------------------- -------------------------- +/// . . +/// . . +/// .------------------------. .------------------------. +/// | | | | +/// pin5 | | | | +/// | | | | +/// ----- -------- -------- +/// . . +/// +/// +/// # duty = 50% +/// . . +/// . . +/// .---------------. .---------------. +/// | | | | +/// pin4 | | | | +/// | | | | +/// ----- ----------------- ----------------- +/// . . +/// . . +/// .---------------. .---------------. +/// | | | | +/// pin5 | | | | +/// | | | | +/// ----- ----------------- ----------------- +/// . . +/// +/// +/// # duty = 90% +/// . . +/// . . +/// .------------------------. .------------------------. +/// | | | | +/// pin4 | | | | +/// | | | | +/// ----- -------- -------- +/// . . +/// . . +/// .------. .------. +/// | | | | +/// pin5 | | | | +/// | | | | +/// ----- -------------------------- -------------------------- +/// . . + +#[cfg(all(any(esp32, esp32s3), esp_idf_version_major = "5"))] +fn main() -> anyhow::Result<()> { + use embedded_hal::delay::DelayUs; + + use esp_idf_hal::delay::FreeRtos; + use esp_idf_hal::mcpwm::{OperatorConfig, TimerConfig, TimerDriver}; + use esp_idf_hal::prelude::Peripherals; + + esp_idf_sys::link_patches(); + + println!("Configuring MCPWM"); + + let peripherals = Peripherals::take().unwrap(); + let timer_config = TimerConfig::default().period_ticks(8_000); // 10kHz + let operator_config = OperatorConfig::default(peripherals.pins.gpio4, peripherals.pins.gpio5); + let timer = + TimerDriver::new(peripherals.mcpwm0.timer0, timer_config).expect("Failed to set up timer"); + + let mut timer = timer + .into_connection() + .attach_operator0(peripherals.mcpwm0.operator0, operator_config) + .expect("Failed to set up operator and timer connection"); + + // Borrow references to the contained timer and operator + let (timer, operator, _, _) = timer.split(); + + println!("Starting duty-cycle loop"); + + let period_ticks = timer.get_period_peak(); + + // TODO: Will this work as expected in UP_DOWN counter mode? + for duty in (0..period_ticks).step_by(10).cycle() { + if duty % 100 == 0 { + println!( + "cmp: {}, duty {}%", + duty, + 100 * u32::from(duty) / u32::from(period_ticks) + ); + } + + operator.set_compare_value_x(duty)?; // In this configuration this controls the duty on pin4 + operator.set_compare_value_y(period_ticks - duty)?; // and this controls pin5 + FreeRtos.delay_ms(10)?; + } + + unreachable!() +} + +#[cfg(not(all(any(esp32, esp32s3), esp_idf_version_major = "5")))] +fn main() { + esp_idf_sys::link_patches(); + + println!("Sorry MCPWM is only supported on ESP32 and ESP32-S3"); +} diff --git a/src/lib.rs b/src/lib.rs index 3bf4c427026..cd74d91f7ec 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,6 +42,12 @@ pub mod ledc; not(feature = "riscv-ulp-hal") ))] pub mod mac; +#[cfg(all( + any(esp32, esp32s3), + esp_idf_version_major = "5", + not(feature = "riscv-ulp-hal") +))] +pub mod mcpwm; #[cfg(not(feature = "riscv-ulp-hal"))] pub mod modem; pub mod peripheral; diff --git a/src/mcpwm/comparator.rs b/src/mcpwm/comparator.rs new file mode 100644 index 00000000000..e70e22bd90c --- /dev/null +++ b/src/mcpwm/comparator.rs @@ -0,0 +1,80 @@ +use core::ptr; + +use esp_idf_sys::{ + esp, mcpwm_cmpr_handle_t, mcpwm_comparator_config_t, mcpwm_comparator_config_t__bindgen_ty_1, + mcpwm_gen_compare_event_action_t, mcpwm_gen_handle_t, mcpwm_gen_t, mcpwm_new_comparator, + mcpwm_oper_handle_t, mcpwm_timer_direction_t_MCPWM_TIMER_DIRECTION_DOWN, + mcpwm_timer_direction_t_MCPWM_TIMER_DIRECTION_UP, EspError, +}; + +use super::generator::CountingDirection; + +pub struct Comparator(pub(crate) mcpwm_cmpr_handle_t); + +impl Comparator { + pub(crate) unsafe fn configure( + &mut self, + gen: &mut mcpwm_gen_t, + cfg: CountingDirection, + ) -> Result<(), EspError> { + extern "C" { + fn mcpwm_generator_set_actions_on_compare_event( + gen: mcpwm_gen_handle_t, + ev_act0: mcpwm_gen_compare_event_action_t, + ev_act1: mcpwm_gen_compare_event_action_t, + ev_act_end: mcpwm_gen_compare_event_action_t, + ) -> esp_idf_sys::esp_err_t; + } + + esp!(mcpwm_generator_set_actions_on_compare_event( + gen, + mcpwm_gen_compare_event_action_t { + direction: mcpwm_timer_direction_t_MCPWM_TIMER_DIRECTION_UP, + comparator: self.0, + action: cfg.counting_up.into(), + }, + mcpwm_gen_compare_event_action_t { + direction: mcpwm_timer_direction_t_MCPWM_TIMER_DIRECTION_DOWN, + comparator: self.0, + action: cfg.counting_down.into(), + }, + mcpwm_gen_compare_event_action_t { + // <-- This marks the last argument in the variadic list + comparator: ptr::null_mut(), + ..Default::default() + } + )) + } +} + +#[derive(Debug, Clone, Copy)] +pub struct ComparatorConfig { + flags: mcpwm_comparator_config_t__bindgen_ty_1, +} + +impl ComparatorConfig { + pub(crate) unsafe fn init( + self, + operator_handle: mcpwm_oper_handle_t, + ) -> Result { + let cfg = mcpwm_comparator_config_t { flags: self.flags }; + + let mut cmp = ptr::null_mut(); + unsafe { + esp!(mcpwm_new_comparator(operator_handle, &cfg, &mut cmp))?; + } + + Ok(Comparator(cmp)) + } +} + +impl Default for ComparatorConfig { + fn default() -> Self { + let mut flags: mcpwm_comparator_config_t__bindgen_ty_1 = Default::default(); + // TODO: What should be set here? + flags.set_update_cmp_on_tep(0); + flags.set_update_cmp_on_tez(1); + flags.set_update_cmp_on_sync(0); + Self { flags } + } +} diff --git a/src/mcpwm/generator.rs b/src/mcpwm/generator.rs new file mode 100644 index 00000000000..824726da0d2 --- /dev/null +++ b/src/mcpwm/generator.rs @@ -0,0 +1,206 @@ +use core::{marker::PhantomData, ptr}; + +use esp_idf_sys::{ + esp, mcpwm_gen_handle_t, mcpwm_gen_timer_event_action_t, mcpwm_generator_action_t, + mcpwm_generator_action_t_MCPWM_GEN_ACTION_HIGH, mcpwm_generator_action_t_MCPWM_GEN_ACTION_KEEP, + mcpwm_generator_action_t_MCPWM_GEN_ACTION_LOW, + mcpwm_generator_action_t_MCPWM_GEN_ACTION_TOGGLE, mcpwm_generator_config_t, + mcpwm_generator_config_t__bindgen_ty_1, mcpwm_new_generator, mcpwm_oper_handle_t, + mcpwm_timer_direction_t_MCPWM_TIMER_DIRECTION_DOWN, + mcpwm_timer_direction_t_MCPWM_TIMER_DIRECTION_UP, mcpwm_timer_event_t_MCPWM_TIMER_EVENT_EMPTY, + mcpwm_timer_event_t_MCPWM_TIMER_EVENT_FULL, mcpwm_timer_event_t_MCPWM_TIMER_EVENT_INVALID, + EspError, +}; + +use crate::{gpio::OutputPin, peripheral::Peripheral}; + +use super::{comparator::Comparator, Group}; + +pub enum ComparatorChannel { + CmpX, + CmpY, +} + +pub struct Generator<'d, G> { + pub(crate) _handle: mcpwm_gen_handle_t, + _p: PhantomData<&'d mut ()>, + _group: PhantomData, +} + +pub struct GeneratorConfig<'d> { + pub(crate) flags: mcpwm_generator_config_t__bindgen_ty_1, + pub(crate) on_matches_cmp_x: CountingDirection, + pub(crate) on_matches_cmp_y: CountingDirection, + pub(crate) on_is_empty: CountingDirection, + pub(crate) on_is_full: CountingDirection, + pub(crate) pin: i32, + _p: PhantomData<&'d mut ()>, +} + +impl<'d> GeneratorConfig<'d> { + pub(crate) unsafe fn init( + self, + operator_handle: mcpwm_oper_handle_t, + cmp_x: &mut Comparator, + cmp_y: &mut Comparator, + ) -> Result, EspError> { + let cfg = mcpwm_generator_config_t { + gen_gpio_num: self.pin, + flags: self.flags, + }; + let mut gen = ptr::null_mut(); + + extern "C" { + fn mcpwm_generator_set_actions_on_timer_event( + gen: mcpwm_gen_handle_t, + ev_act0: mcpwm_gen_timer_event_action_t, + ev_act1: mcpwm_gen_timer_event_action_t, + ev_act2: mcpwm_gen_timer_event_action_t, + ev_act3: mcpwm_gen_timer_event_action_t, + + ev_act_end: mcpwm_gen_timer_event_action_t, + ) -> esp_idf_sys::esp_err_t; + } + + unsafe { + esp!(mcpwm_new_generator(operator_handle, &cfg, &mut gen))?; + + esp!(mcpwm_generator_set_actions_on_timer_event( + gen, + mcpwm_gen_timer_event_action_t { + direction: mcpwm_timer_direction_t_MCPWM_TIMER_DIRECTION_UP, + event: mcpwm_timer_event_t_MCPWM_TIMER_EVENT_EMPTY, + action: self.on_is_empty.counting_up.into(), + }, + mcpwm_gen_timer_event_action_t { + direction: mcpwm_timer_direction_t_MCPWM_TIMER_DIRECTION_DOWN, + event: mcpwm_timer_event_t_MCPWM_TIMER_EVENT_EMPTY, + action: self.on_is_empty.counting_down.into(), + }, + mcpwm_gen_timer_event_action_t { + direction: mcpwm_timer_direction_t_MCPWM_TIMER_DIRECTION_UP, + event: mcpwm_timer_event_t_MCPWM_TIMER_EVENT_FULL, + action: self.on_is_full.counting_up.into(), + }, + mcpwm_gen_timer_event_action_t { + direction: mcpwm_timer_direction_t_MCPWM_TIMER_DIRECTION_DOWN, + event: mcpwm_timer_event_t_MCPWM_TIMER_EVENT_FULL, + action: self.on_is_full.counting_down.into(), + }, + mcpwm_gen_timer_event_action_t { + // <-- This marks the last argument in the variadic list + event: mcpwm_timer_event_t_MCPWM_TIMER_EVENT_INVALID, + ..Default::default() + } + ))?; + + cmp_x.configure(&mut *gen, self.on_matches_cmp_x)?; + cmp_y.configure(&mut *gen, self.on_matches_cmp_y)?; + } + + // + Ok(Generator { + _handle: gen, + _p: PhantomData, + _group: PhantomData, + }) + } + + pub fn active_high( + pin: impl Peripheral

+ 'd, + cmp_channel: ComparatorChannel, + ) -> Self { + let mut result: Self = GeneratorConfig::empty(pin); + + result.on_is_empty.counting_up = GeneratorAction::SetHigh; + match cmp_channel { + ComparatorChannel::CmpX => { + result.on_matches_cmp_x.counting_up = GeneratorAction::SetLow + } + ComparatorChannel::CmpY => { + result.on_matches_cmp_y.counting_up = GeneratorAction::SetLow + } + } + result + } + + pub fn active_low( + pin: impl Peripheral

+ 'd, + cmp_channel: ComparatorChannel, + ) -> Self { + let mut result: Self = GeneratorConfig::empty(pin); + + result.on_is_empty.counting_up = GeneratorAction::SetLow; + match cmp_channel { + ComparatorChannel::CmpX => { + result.on_matches_cmp_x.counting_up = GeneratorAction::SetHigh + } + ComparatorChannel::CmpY => { + result.on_matches_cmp_y.counting_up = GeneratorAction::SetHigh + } + } + result + } +} + +// TODO: Do we have any use for this? +impl<'d> GeneratorConfig<'d> { + fn empty(pin: impl Peripheral

+ 'd) -> Self { + crate::into_ref!(pin); + + let mut flags: mcpwm_generator_config_t__bindgen_ty_1 = Default::default(); + flags.set_invert_pwm(0); + flags.set_io_loop_back(0); + + GeneratorConfig { + flags, + on_matches_cmp_x: CountingDirection::empty(), + on_matches_cmp_y: CountingDirection::empty(), + on_is_empty: CountingDirection { + counting_up: GeneratorAction::Nothing, + counting_down: GeneratorAction::Nothing, + }, + on_is_full: CountingDirection { + counting_up: GeneratorAction::Nothing, + counting_down: GeneratorAction::Nothing, + }, + pin: pin.pin(), + _p: PhantomData, + } + } +} + +// TODO: Come up with better name +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct CountingDirection { + pub(crate) counting_up: GeneratorAction, + pub(crate) counting_down: GeneratorAction, +} + +impl CountingDirection { + pub fn empty() -> Self { + CountingDirection { + counting_up: GeneratorAction::Nothing, + counting_down: GeneratorAction::Nothing, + } + } +} + +impl From for mcpwm_generator_action_t { + fn from(val: GeneratorAction) -> Self { + match val { + GeneratorAction::Nothing => mcpwm_generator_action_t_MCPWM_GEN_ACTION_KEEP, + GeneratorAction::SetLow => mcpwm_generator_action_t_MCPWM_GEN_ACTION_LOW, + GeneratorAction::SetHigh => mcpwm_generator_action_t_MCPWM_GEN_ACTION_HIGH, + GeneratorAction::Toggle => mcpwm_generator_action_t_MCPWM_GEN_ACTION_TOGGLE, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum GeneratorAction { + Nothing, + SetLow, + SetHigh, + Toggle, +} diff --git a/src/mcpwm/mod.rs b/src/mcpwm/mod.rs new file mode 100644 index 00000000000..ee44e0ff199 --- /dev/null +++ b/src/mcpwm/mod.rs @@ -0,0 +1,144 @@ +//! 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) +//! +//! ``` +//! -------------------------------------------------------------------------- +//! | MCPWM Group N | +//! | ------------------------- | +//! | | +//! | --------- ------------------------------------------------- | +//! | | Timer 0 |-------* | OPERATOR 0 | | +//! | --------- | | --------------- | | +//! | --------- | | ------------- | | +//! | | Timer 1 |----* | | *----------------------->| | | | +//! | --------- | | | | *------>| GENERATOR 0 |-|-|--> To Output pin +//! | --------- | *--|-|\ | | *---->| | | | +//! | | Timer 2 |-* | | | | \ *->Comparator 0>-* | ------------- | | +//! | --------- | *-----|-| |>-| | | | | +//! | | | | | | / *->Comparator 1>-|-* ------------- | | +//! | *--------|-|/ | *-|---->| | |-|--> To Output pin +//! | | | | | | *---->| GENERATOR 1 | | | +//! | | | | | *----------------------->| | | | +//! | | | | | ------------- | | +//! | | | | ------------------------------------------------- | +//! | | | | | +//! | | | | ------------------------------------------------- | +//! | | | *--| OPERATOR 1 | | +//! | | | | | --------------- |-|--> To Output pin +//! | | *-----| | | +//! | | | | | ... |-|--> To Output pin +//! | *--------| | | +//! | | | | ------------------------------------------------- | +//! | | | | | +//! | | | | ------------------------------------------------- | +//! | | | *--| OPERATOR 2 | | +//! | | | | | --------------- |-|--> To Output pin +//! | | *-----| | | +//! | | | | | ... |-|--> To Output pin +//! | *--------| | | +//! | ------------------------------------------------- | +//! | | +//! | | +//! ------------------------------------------------------------------------- +//! ``` +//! +//! # 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%. +//! ``` +//! let peripherals = Peripherals::take().unwrap(); +//! let timer_config = TimerConfig::default().frequency(25.kHz()); +//! let operator_config = OperatorConfig::default(); +//! let timer = Mcpwm::new(peripherals.mcpwm0.timer, timer_config)?; +//! +//! let timer = timer.into_connection() +//! .attatch_operator0( +//! peripherals.mcpwm0.operator0, +//! operator_config, +//! peripherals.pins.gpio4, +//! peripherals.pins.gpio5, +//! )?; +//! +//! let (timer, operator, _, _) = timer.split(); +//! +//! 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. + +mod comparator; +mod generator; +mod operator; +mod operator_config; +mod timer; +mod timer_connection; + +use core::ffi; + +pub use self::{ + generator::{GeneratorAction, GeneratorConfig}, + operator::{Operator, OPERATOR}, + operator_config::OperatorConfig, + timer::{TimerConfig, TimerDriver, TIMER}, + timer_connection::TimerConnection, +}; + +/// The Motor Control Pulse Width Modulator peripheral +pub struct MCPWM { + pub timer0: TIMER<0, G>, + pub timer1: TIMER<1, G>, + pub timer2: TIMER<2, G>, + + pub operator0: OPERATOR<0, G>, + pub operator1: OPERATOR<1, G>, + pub operator2: OPERATOR<2, G>, +} + +impl MCPWM { + /// # Safety + /// + /// It is safe to instantiate this exactly one time per `Group`. + pub unsafe fn new() -> Self { + Self { + timer0: TIMER::new(), + timer1: TIMER::new(), + timer2: TIMER::new(), + operator0: OPERATOR::new(), + operator1: OPERATOR::new(), + operator2: OPERATOR::new(), + } + } +} + +#[derive(Default)] +pub struct Group0; + +#[cfg(not(esp32c6))] +#[derive(Default)] +pub struct Group1; + +pub type Duty = u16; + +// Note this was called `Unit` in IDF < 5.0 +pub trait Group: Default { + const ID: ffi::c_int; +} + +impl Group for Group0 { + const ID: ffi::c_int = 0; +} + +#[cfg(not(esp32c6))] +impl Group for Group1 { + const ID: ffi::c_int = 1; +} diff --git a/src/mcpwm/operator.rs b/src/mcpwm/operator.rs new file mode 100644 index 00000000000..c834c1d5594 --- /dev/null +++ b/src/mcpwm/operator.rs @@ -0,0 +1,201 @@ +use esp_idf_sys::{ + esp, mcpwm_comparator_set_compare_value, mcpwm_oper_handle_t, mcpwm_operator_config_t, + mcpwm_operator_config_t__bindgen_ty_1, mcpwm_operator_connect_timer, mcpwm_timer_handle_t, + EspError, +}; + +use crate::mcpwm::Group; + +use super::{comparator::Comparator, generator::Generator, OperatorConfig}; + +use core::{marker::PhantomData, ptr}; + +pub struct OPERATOR { + _ptr: PhantomData<*const ()>, + _group: PhantomData, +} + +impl OPERATOR { + /// # Safety + /// + /// Care should be taken not to instnatiate this peripheralinstance, if it is already instantiated and used elsewhere + #[inline(always)] + pub unsafe fn new() -> Self { + Self { + _ptr: PhantomData, + _group: PhantomData, + } + } +} + +unsafe impl Send for OPERATOR {} + +impl crate::peripheral::sealed::Sealed for OPERATOR {} + +impl crate::peripheral::Peripheral for OPERATOR { + type P = Self; + + #[inline] + unsafe fn clone_unchecked(&mut self) -> Self::P { + Self { ..*self } + } +} + +// TODO: How do we want syncing to fit in to this? +// 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<'d, const N: u8, G: Group> { + _instance: OPERATOR, + _handle: mcpwm_oper_handle_t, + + comparator_x: Comparator, // SOC_MCPWM_COMPARATORS_PER_OPERATOR is 2 for ESP32 and ESP32-S3 + comparator_y: Comparator, + + _generator_a: Option>, // One generator per pin, with a maximum of two generators per Operator + _generator_b: Option>, + //deadtime: D +} + +pub(crate) unsafe fn new( + instance: OPERATOR, + timer_handle: mcpwm_timer_handle_t, + cfg: OperatorConfig, +) -> Result, EspError> +where + G: Group, +{ + let mut handle = ptr::null_mut(); + let mut flags: mcpwm_operator_config_t__bindgen_ty_1 = Default::default(); + + // TODO: What should these be set to? + flags.set_update_gen_action_on_tez(0); + flags.set_update_gen_action_on_tep(0); + flags.set_update_gen_action_on_sync(0); + + flags.set_update_dead_time_on_tez(0); + flags.set_update_dead_time_on_tep(0); + flags.set_update_dead_time_on_sync(0); + + let config = mcpwm_operator_config_t { + group_id: G::ID, + flags, + }; + + unsafe { + esp!(esp_idf_sys::mcpwm_new_operator(&config, &mut handle))?; + } + + let mut comparator_x = unsafe { cfg.comparator_x.init(handle)? }; + let mut comparator_y = unsafe { cfg.comparator_y.init(handle)? }; + + // Connect operator to timer + unsafe { + esp!(mcpwm_operator_connect_timer(handle, timer_handle))?; + } + + let generator_a = unsafe { + cfg.generator_a + .map(|g| g.init(handle, &mut comparator_x, &mut comparator_y)) + .transpose()? + }; + let generator_b = unsafe { + cfg.generator_b + .map(|g| g.init(handle, &mut comparator_x, &mut comparator_y)) + .transpose()? + }; + + Ok(Operator { + _instance: instance, + _handle: handle, + comparator_x, + comparator_y, + + _generator_a: generator_a, + _generator_b: generator_b, + }) +} + +impl<'d, const N: u8, G> Operator<'d, N, G> +where + G: Group, +{ + // TODO: Note that this is the comparator we are affecting, not the generator. Generator A may not necessarily have + // anything to do with comparator A. How do we best convay that? Should we call them Generator X/Y and Comparator A/B? + // + // Again should we always provide two comparators? That would make both set/get_duty_a/b always available... Should we + // instead let the user only provide only as few or many (max 2 as of ESP32/ESP32-S3) as they want. And depending on + // expose only the corresponding set/get_duty? + // + // Once again to clarify, set_duty affects the comparator. The generators(booth or none) may then choose to use that + // event, as well as timer events, to change the level of the pin. + // + /// Get compare value, often times same as the duty for output A. + /// + /// See `Self::set_compare_value_x` for more info + pub fn get_compare_value_x(&self) -> u16 { + todo!() + } + + /// Set compare value, often times same as the duty for output A. + /// + /// Depending on how the generators are configured this is, using the most common configuration, the duty of output A. + /// `value` is from the range 0 to timers peak value. However do note that if using a custom configuration this might + /// control something else like for example the phase. Look into Generator::TODO for more details + /// + /// TODO: what about CountMode::UpDown? + /// + /// NOTE: The compare value shouldn’t exceed timer’s count peak, otherwise, the compare event will never got triggered. + /// NOTE: This function is safe to from an ISR context + #[inline(always)] + pub fn set_compare_value_x(&mut self, value: u16) -> Result<(), EspError> { + unsafe { + esp!(mcpwm_comparator_set_compare_value( + self.comparator_x.0, + value.into() + )) + } + } +} + +impl<'d, const N: u8, G> Operator<'d, N, G> +where + G: Group, +{ + /// Get compare value, often times same as the duty for output B. + /// + /// See `Self::set_compare_value_x` for more info + pub fn get_compare_value_y(&self) -> u16 { + todo!() + } + + /// Set compare value, often times same as the duty for output B. + /// + /// Depending on how the generators are configured this is, using the most common configuration, the duty of output A. + /// `value` is from the range 0 to timers peak value. However do note that if using a custom configuration this might + /// control something else like for example the phase. Look into Generator::TODO for more details + /// + /// TODO: what about CountMode::UpDown? + /// + /// NOTE: The compare value shouldn’t exceed timer’s count peak, otherwise, the compare event will never got triggered. + /// NOTE: This function is safe to from an ISR context + #[inline(always)] + pub fn set_compare_value_y(&mut self, value: u16) -> Result<(), EspError> { + unsafe { + esp!(mcpwm_comparator_set_compare_value( + self.comparator_y.0, + value.into() + )) + } + } +} + +pub trait OptionalOperator {} +impl<'d, const N: u8, G> OptionalOperator for Operator<'d, N, G> where G: Group {} + +pub struct NoOperator; +impl OptionalOperator for NoOperator {} diff --git a/src/mcpwm/operator_config.rs b/src/mcpwm/operator_config.rs new file mode 100644 index 00000000000..60506108239 --- /dev/null +++ b/src/mcpwm/operator_config.rs @@ -0,0 +1,85 @@ +use esp_idf_sys::mcpwm_operator_config_t__bindgen_ty_1; + +use crate::gpio::OutputPin; + +use super::{ + comparator::ComparatorConfig, + generator::{ComparatorChannel, GeneratorConfig}, +}; + +#[derive(Default)] +pub struct OperatorConfig<'d> { + /// Configuration for Comparator X + pub(crate) comparator_x: ComparatorConfig, + + /// Configuration for Comparator Y + pub(crate) comparator_y: ComparatorConfig, + + /// Configuration for Generator A + pub(crate) generator_a: Option>, + + /// Configuration for Generator B + pub(crate) generator_b: Option>, +} + +impl<'d> OperatorConfig<'d> { + pub fn default(pin_a: PA, pin_b: PB) -> OperatorConfig<'d> { + OperatorConfig::empty() + .cmp_x(Default::default()) + .cmp_y(Default::default()) + .gen_a(GeneratorConfig::active_high(pin_a, ComparatorChannel::CmpX)) + .gen_b(GeneratorConfig::active_high(pin_b, ComparatorChannel::CmpY)) + } + + pub fn empty() -> Self { + let mut flags: mcpwm_operator_config_t__bindgen_ty_1 = Default::default(); + + // TODO: What value makes most sense as default here? + // TODO: When, how and who should set the flags? + // Should that be done automagically, manually or does some specific setting cover all cases? + // Flags for Operator + flags.set_update_gen_action_on_tez(1); + flags.set_update_gen_action_on_tep(1); + flags.set_update_gen_action_on_sync(1); + + flags.set_update_dead_time_on_tez(1); + flags.set_update_dead_time_on_tep(1); + flags.set_update_dead_time_on_sync(1); + + OperatorConfig { + comparator_x: Default::default(), // SOC_MCPWM_COMPARATORS_PER_OPERATOR is 2 for ESP32 and ESP32-S3 + comparator_y: Default::default(), + + generator_a: None, // One generator per pin, with a maximum of two generators per Operator + generator_b: None, + } + } +} + +impl<'d> OperatorConfig<'d> { + fn cmp_x(mut self, config: ComparatorConfig) -> Self { + self.comparator_x = config; + self + } + + fn cmp_y(mut self, config: ComparatorConfig) -> Self { + self.comparator_y = config; + self + } +} + +impl<'d> OperatorConfig<'d> { + #[allow(clippy::type_complexity)] + fn gen_a(mut self, config: GeneratorConfig<'d>) -> Self { + self.generator_a = Some(config); + self + } +} + +impl<'d> OperatorConfig<'d> { + #[allow(clippy::type_complexity)] + fn gen_b(mut self, config: GeneratorConfig<'d>) -> Self { + self.generator_b = Some(config); + self + } +} diff --git a/src/mcpwm/timer.rs b/src/mcpwm/timer.rs new file mode 100644 index 00000000000..52822ae55ae --- /dev/null +++ b/src/mcpwm/timer.rs @@ -0,0 +1,285 @@ +use core::convert::TryInto; +use core::marker::PhantomData; +use core::ptr; + +use esp_idf_sys::{ + esp, mcpwm_del_timer, mcpwm_new_timer, mcpwm_timer_config_t, + mcpwm_timer_config_t__bindgen_ty_1, mcpwm_timer_count_mode_t, + mcpwm_timer_count_mode_t_MCPWM_TIMER_COUNT_MODE_DOWN, + mcpwm_timer_count_mode_t_MCPWM_TIMER_COUNT_MODE_PAUSE, + mcpwm_timer_count_mode_t_MCPWM_TIMER_COUNT_MODE_UP, + mcpwm_timer_count_mode_t_MCPWM_TIMER_COUNT_MODE_UP_DOWN, mcpwm_timer_enable, + mcpwm_timer_handle_t, mcpwm_timer_start_stop, + mcpwm_timer_start_stop_cmd_t_MCPWM_TIMER_START_NO_STOP, + soc_periph_mcpwm_timer_clk_src_t_MCPWM_TIMER_CLK_SRC_DEFAULT, EspError, +}; + +use crate::mcpwm::Group; +use crate::prelude::{FromValueType, Hertz}; + +use super::operator::NoOperator; +use super::timer_connection::TimerConnection; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct TimerConfig { + resolution: Hertz, + period_ticks: u16, + count_mode: CountMode, + // TODO + // on_full: FF, + // on_empty: FE, + // on_stop: FS, +} + +impl Default for TimerConfig { + fn default() -> Self { + Self { + resolution: 80.MHz().into(), + period_ticks: 8_000, // 10kHz + count_mode: CountMode::Up, + } + } +} + +impl TimerConfig { + /*#[must_use] + pub fn resolution(mut self, resolution: impl Into) -> Self { + self.resolution = resolution.into(); + self + }*/ + + // TODO: make sure this description is accurate + /// Set number of ticks per period + /// + /// This is inversely proportional to the frequency of the signal + /// + /// You can calculate the frequency as + /// `frequency = resolution / period_ticks` + /// + /// For example a resolution of 80MHz and a period_ticks of 8_000: + /// `10kHz = 80MHz / 8_000` + /// + /// NOTE: This will be the same as `Self::get_period_peak` for all `CounterMode` except for + /// `CounterMode::UpDown` where the period will be twice as large as the peak value since + /// the timer will count from zero to peak and then down to zero again + #[must_use] + pub fn period_ticks(mut self, period_ticks: u16) -> Self { + self.period_ticks = period_ticks; + self + } + + #[must_use] + pub fn count_mode(mut self, counter_mode: CountMode) -> Self { + self.count_mode = counter_mode; + self + } +} + +pub struct TimerDriver { + _group: G, + handle: mcpwm_timer_handle_t, + _timer: TIMER, + /// Number of ticks within a period + /// + /// See `Self::get_period_ticks` for more info + period_ticks: u32, + + /// This is the maximum value that the comparator will see + /// + /// See `Self::get_period_peak` for more info + period_peak: u16, +} + +impl TimerDriver { + pub fn new(timer: TIMER, config: TimerConfig) -> Result { + let mut flags: mcpwm_timer_config_t__bindgen_ty_1 = Default::default(); + + // TODO: What should these be set to? + flags.set_update_period_on_empty(1); + flags.set_update_period_on_sync(0); + + let cfg = mcpwm_timer_config_t { + group_id: G::ID, + clk_src: soc_periph_mcpwm_timer_clk_src_t_MCPWM_TIMER_CLK_SRC_DEFAULT, + resolution_hz: config.resolution.0, + count_mode: config.count_mode.into(), + period_ticks: config.period_ticks.into(), + flags, + }; + let mut handle: mcpwm_timer_handle_t = ptr::null_mut(); + unsafe { + esp!(mcpwm_new_timer(&cfg, &mut handle))?; + } + // TODO: note that this has to be called before mcpwm_timer_enable + // mcpwm_timer_register_event_callbacks() + unsafe { + esp!(mcpwm_timer_enable(handle))?; + esp!(mcpwm_timer_start_stop( + handle, + mcpwm_timer_start_stop_cmd_t_MCPWM_TIMER_START_NO_STOP + ))?; + } + + let period_peak = if config.count_mode == CountMode::UpDown { + (cfg.period_ticks / 2).try_into().unwrap() + } else { + cfg.period_ticks.try_into().unwrap() + }; + + Ok(Self { + _group: G::default(), + handle, + _timer: timer, + period_ticks: cfg.period_ticks, + period_peak, + }) + } + + // TODO: make sure this description is accurate + /// Get number of ticks per period + /// + /// Use this when working with the frequency or the period + /// + /// NOTE: This will be the same as `Self::get_period_peak` for all `CounterMode` except for + /// `CounterMode::UpDown` where the period will be twice as large as the peak value since + /// the timer will count from zero to peak and then down to zero again + pub fn get_period_ticks(&self) -> u32 { + self.period_ticks + } + + // TODO: make sure this description is accurate + /// This is the maximum value that the comparator will see + /// + /// Use this working with the duty + /// + /// NOTE: This will not be the same as `Self::get_period_ticks` when using `CounterMode::UpDown` + /// See `Self::get_period_ticks` for more info + pub fn get_period_peak(&self) -> u16 { + self.period_peak + } + + pub fn timer(&self) -> mcpwm_timer_handle_t { + self.handle + } + + pub fn into_connection(self) -> TimerConnection { + TimerConnection::new(self) + } +} + +// TODO: Should this be done in TimerConnection instead to ensure everything is taken down +// in the correct order? +impl Drop for TimerDriver { + fn drop(&mut self) { + unsafe { + esp!(mcpwm_del_timer(self.handle)).unwrap(); + } + } +} + +/// Counter mode for operator's timer for generating PWM signal +// TODO: For UpDown, frequency is half of MCPWM frequency set +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum CountMode { + /// 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, + + /// Timer paused + Pause, +} + +impl From for mcpwm_timer_count_mode_t { + fn from(val: CountMode) -> Self { + match val { + //CounterMode::Frozen => mcpwm_counter_type_t_MCPWM_FREEZE_COUNTER, + CountMode::Up => mcpwm_timer_count_mode_t_MCPWM_TIMER_COUNT_MODE_UP, + CountMode::Down => mcpwm_timer_count_mode_t_MCPWM_TIMER_COUNT_MODE_DOWN, + CountMode::UpDown => mcpwm_timer_count_mode_t_MCPWM_TIMER_COUNT_MODE_UP_DOWN, + CountMode::Pause => mcpwm_timer_count_mode_t_MCPWM_TIMER_COUNT_MODE_PAUSE, + } + } +} + +pub struct TIMER { + _ptr: PhantomData<*const ()>, + _group: PhantomData, +} + +impl TIMER { + /// # Safety + /// + /// Care should be taken not to instnatiate this peripheralinstance, if it is already instantiated and used elsewhere + #[inline(always)] + pub unsafe fn new() -> Self { + Self { + _ptr: PhantomData, + _group: PhantomData, + } + } +} + +unsafe impl Send for TIMER {} + +impl crate::peripheral::sealed::Sealed for TIMER {} + +impl crate::peripheral::Peripheral for TIMER { + type P = Self; + + #[inline] + unsafe fn clone_unchecked(&mut self) -> Self::P { + Self { ..*self } + } +} diff --git a/src/mcpwm/timer_connection.rs b/src/mcpwm/timer_connection.rs new file mode 100644 index 00000000000..02562494a50 --- /dev/null +++ b/src/mcpwm/timer_connection.rs @@ -0,0 +1,138 @@ +use esp_idf_sys::EspError; + +use crate::mcpwm::Group; + +use super::{ + operator::{self, NoOperator, OptionalOperator, OPERATOR}, + timer::TimerDriver, + Operator, OperatorConfig, +}; + +// TODO: How do we want fault module to fit into this? +/// Created by `Timer::into_connection()` +pub struct TimerConnection +where + O0: OptionalOperator<0, G>, + O1: OptionalOperator<1, G>, + O2: OptionalOperator<2, G>, +{ + timer: TimerDriver, + operator0: O0, + operator1: O1, + operator2: O2, +} + +impl TimerConnection { + pub(crate) fn new(timer: TimerDriver) -> Self { + Self { + timer, + operator0: NoOperator, + operator1: NoOperator, + operator2: NoOperator, + } + } +} + +// Since there can only ever by one instance of every operator type (except NoOperator) +// we know that there can be no mem::swap or similar to cause any problems. +// +// Thus we know that after split is called nothing can be added/removed while still having access to +// the individual objects. We also garantuee that the operators wont live longer than the timer +impl< + const N: u8, + G: Group, + O0: OptionalOperator<0, G>, + O1: OptionalOperator<1, G>, + O2: OptionalOperator<2, G>, + > TimerConnection +{ + pub fn split(&mut self) -> (&mut TimerDriver, &mut O0, &mut O1, &mut O2) { + ( + &mut self.timer, + &mut self.operator0, + &mut self.operator1, + &mut self.operator2, + ) + } +} +// TODO: Do something more builder-pattern like for making the operator? +impl TimerConnection +where + G: Group, + O1: OptionalOperator<1, G>, + O2: OptionalOperator<2, G>, +{ + #[allow(clippy::type_complexity)] + pub fn attach_operator0( + self, + operator_handle: OPERATOR<0, G>, + operator_cfg: OperatorConfig<'_>, + ) -> Result, O1, O2>, EspError> { + let operator = unsafe { operator::new(operator_handle, self.timer.timer(), operator_cfg) }?; + Ok(TimerConnection { + timer: self.timer, + operator0: operator, + operator1: self.operator1, + operator2: self.operator2, + }) + } +} + +impl TimerConnection +where + G: Group, + O0: OptionalOperator<0, G>, + O2: OptionalOperator<2, G>, +{ + #[allow(clippy::type_complexity)] + pub fn attach_operator1( + self, + operator_handle: OPERATOR<1, G>, + operator_cfg: OperatorConfig<'_>, + ) -> Result, O2>, EspError> { + let operator = unsafe { operator::new(operator_handle, self.timer.timer(), operator_cfg) }?; + Ok(TimerConnection { + timer: self.timer, + operator0: self.operator0, + operator1: operator, + operator2: self.operator2, + }) + } +} + +impl TimerConnection +where + G: Group, + O0: OptionalOperator<0, G>, + O1: OptionalOperator<1, G>, +{ + #[allow(clippy::type_complexity)] + pub fn attach_operator2( + self, + operator_handle: OPERATOR<2, G>, + operator_cfg: OperatorConfig<'_>, + ) -> Result>, EspError> { + let operator = unsafe { operator::new(operator_handle, self.timer.timer(), operator_cfg) }?; + Ok(TimerConnection { + timer: self.timer, + operator0: self.operator0, + operator1: self.operator1, + operator2: operator, + }) + } +} + +/* +// TODO: Adding this prevents moving values out of the type as is done in attatch_operator() +// how do we make this work? +impl Drop for TimerConnection + where G: Group, + O0: OptionalOperator<0, G>, + O1: OptionalOperator<1, G>, + O2: OptionalOperator<2, G>, +{ + fn drop(&mut self) { + todo!() + } +} +*/ diff --git a/src/peripherals.rs b/src/peripherals.rs index d14dde82363..4c95293ce2f 100644 --- a/src/peripherals.rs +++ b/src/peripherals.rs @@ -11,6 +11,12 @@ use crate::ledc; not(feature = "riscv-ulp-hal") ))] use crate::mac; +#[cfg(all( + any(esp32, esp32s3), + esp_idf_version_major = "5", + not(feature = "riscv-ulp-hal") +))] +use crate::mcpwm; #[cfg(not(feature = "riscv-ulp-hal"))] use crate::modem; #[cfg(not(feature = "riscv-ulp-hal"))] @@ -54,6 +60,18 @@ pub struct Peripherals { pub can: can::CAN, #[cfg(not(feature = "riscv-ulp-hal"))] pub ledc: ledc::LEDC, + #[cfg(all( + any(esp32, esp32s3), + esp_idf_version_major = "5", + not(feature = "riscv-ulp-hal") + ))] + pub mcpwm0: mcpwm::MCPWM, + #[cfg(all( + any(esp32, esp32s3), + esp_idf_version_major = "5", + not(feature = "riscv-ulp-hal") + ))] + pub mcpwm1: mcpwm::MCPWM, #[cfg(not(feature = "riscv-ulp-hal"))] pub rmt: rmt::RMT, #[cfg(all( @@ -162,6 +180,18 @@ impl Peripherals { can: can::CAN::new(), #[cfg(not(feature = "riscv-ulp-hal"))] ledc: ledc::LEDC::new(), + #[cfg(all( + any(esp32, esp32s3), + not(feature = "riscv-ulp-hal"), + esp_idf_version_major = "5" + ))] + mcpwm0: mcpwm::MCPWM::::new(), + #[cfg(all( + any(esp32, esp32s3), + not(feature = "riscv-ulp-hal"), + esp_idf_version_major = "5" + ))] + mcpwm1: mcpwm::MCPWM::::new(), #[cfg(not(feature = "riscv-ulp-hal"))] rmt: rmt::RMT::new(), #[cfg(all(