diff --git a/esp-hal/Cargo.toml b/esp-hal/Cargo.toml index 1eed6879771..a57003e2b20 100644 --- a/esp-hal/Cargo.toml +++ b/esp-hal/Cargo.toml @@ -108,17 +108,18 @@ ufmt-write = { version = "0.1", optional = true } # IMPORTANT: # Each supported device MUST have its PAC included below along with a # corresponding feature. -esp32 = { version = "0.40", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "8d0a072" } -esp32c2 = { version = "0.29", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "8d0a072" } -esp32c3 = { version = "0.32", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "8d0a072" } -esp32c5 = { version = "0.2", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "8d0a072" } -esp32c6 = { version = "0.23", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "8d0a072" } -esp32c61 = { version = "0.3", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "8d0a072" } -esp32h2 = { version = "0.19", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "8d0a072" } -esp32s2 = { version = "0.31", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "8d0a072" } -esp32s3 = { version = "0.35", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "8d0a072" } -esp32s31 = { version = "0.1", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "8d0a072" } -esp32p4 = { version = "0.2", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "8d0a072" } +# Remove once e78b9efbb166ee2197a5b67144044b137c429e55 is pulled into esp-pacs main +esp32 = { version = "0.40", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "e78b9efbb166ee2197a5b67144044b137c429e55" } +esp32c2 = { version = "0.29", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "e78b9efbb166ee2197a5b67144044b137c429e55" } +esp32c3 = { version = "0.32", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "e78b9efbb166ee2197a5b67144044b137c429e55" } +esp32c5 = { version = "0.2", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "e78b9efbb166ee2197a5b67144044b137c429e55" } +esp32c6 = { version = "0.23", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "e78b9efbb166ee2197a5b67144044b137c429e55" } +esp32c61 = { version = "0.3", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "e78b9efbb166ee2197a5b67144044b137c429e55" } +esp32h2 = { version = "0.19", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "e78b9efbb166ee2197a5b67144044b137c429e55" } +esp32s2 = { version = "0.31", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "e78b9efbb166ee2197a5b67144044b137c429e55" } +esp32s3 = { version = "0.35", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "e78b9efbb166ee2197a5b67144044b137c429e55" } +esp32s31 = { version = "0.1", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "e78b9efbb166ee2197a5b67144044b137c429e55" } +esp32p4 = { version = "0.2", features = ["critical-section"], optional = true, git = "https://github.com/esp-rs/esp-pacs", rev = "e78b9efbb166ee2197a5b67144044b137c429e55" } [target.'cfg(target_arch = "riscv32")'.dependencies] riscv = { version = "0.16.1" } diff --git a/esp-hal/src/mcpwm/capture.rs b/esp-hal/src/mcpwm/capture.rs new file mode 100644 index 00000000000..d8c67b383a2 --- /dev/null +++ b/esp-hal/src/mcpwm/capture.rs @@ -0,0 +1,351 @@ +#![cfg_attr(docsrs, procmacros::doc_replace( + "mcpwm_freq" => { + cfg(not(esp32h2)) => "40", + cfg(esp32h2) => "32" + } +))] +//! # MCPWM Capture Module +//! +//! ## Overview +//! The `Capture` is responsible for managing the recording of the +//! capture timer during specific software or hardware triggered +//! 'capture' events. +//! +//! ## Configuration +//! This module provides the flexibility of configuring any GPIO pin as an input +//! for capturing the rising and/or falling edge of a signal. This module allows +//! for the ability to trigger software captures to record the current timer's value. +//! +//! ## Example +//! +//! This example shows configuring MCPWM for receiving +//! rising and falling edges from a GPIO pin. +//! +//! ```rust, no_run +//! # {before_snippet} +//! # use critical_section::Mutex; +//! # use esp_hal::mcpwm::{AnyMcPwm, McPwm, PeripheralClockConfig}; +//! # use esp_hal::mcpwm::capture::{CaptureChannel, CaptureChannelConfig, CaptureMode, CaptureTimerConfig}; +//! # use esp_hal::time::Rate; +//! # use core::cell::RefCell; +//! # +//! # let pin = peripherals.GPIO0; +//! +//! static CAP0: Mutex>>> = +//! Mutex::new(RefCell::new(None)); +//! +//! // initialize peripheral +//! let clock_cfg = PeripheralClockConfig::with_frequency(Rate::from_mhz(__mcpwm_freq__))?; +//! let mut mcpwm = McPwm::new(AnyMcPwm::from(peripherals.MCPWM0), clock_cfg); +//! +//! // initialize capture timer +//! let cap_timer_cfg = CaptureTimerConfig::default(); +//! mcpwm.capture_timer.apply_config(cap_timer_cfg); +//! mcpwm.capture_timer.start(); +//! +//! // create capture channel with a `pin` and rising edge capture mode +//! let mut capture = mcpwm.capture0.with_signal_input(pin); +//! capture.apply_config(CaptureChannelConfig::default()); +//! capture.set_enable(true); +//! capture.listen(CaptureMode::RisingEdge); +//! +//! critical_section::with(|cs| CAP0.borrow_ref_mut(cs).replace(capture)); +//! +//! #[esp_hal::handler] +//! fn interrupt_handler() { +//! critical_section::with(|cs| { +//! let mut capture = CAP0.borrow_ref_mut(cs); +//! if let Some(capture) = capture.as_mut() { +//! if capture.is_interrupt_set() { +//! let event = capture.events(); +//! let time = event.time(); +//! let edge = event.edge(); +//! // do something with edge and time +//! capture.clear_interrupt(); +//! } +//! } +//! }); +//! } +//! # Ok(()) +//! # } +//! ``` + +use core::marker::PhantomData; + +use enumset::EnumSet; + +use super::{Event, PeripheralGuard}; +pub use crate::pac::mcpwm0::{ + cap_ch_cfg::CAP0_MODE as CaptureMode, + cap_status::CAP0_EDGE as CaptureEdge, +}; +use crate::{ + gpio::interconnect::PeripheralInput, + mcpwm::{Info, PwmClockGuard, sync::SyncKind}, + pac, +}; + +/// Configuration for capture timer +#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CaptureTimerConfig { + sync_phase: u32, +} + +impl CaptureTimerConfig { + /// Sets the sync phase for the capture timer + pub fn with_sync_phase(self, sync_phase: u32) -> Self { + Self { sync_phase } + } +} +/// The MCPWM Capture Timer +/// +/// ## Overview +/// This timer is used for all [`CaptureChannel`]'s for a given MCPWM instance. +/// * This timer can be configured with a sync source with [`CaptureTimerConfig`]. +/// +/// ### Sync Events +/// When this timer receives a sync event the counter of the timer is reset +/// to the phase value set in [`CaptureTimerConfig`]. +/// +/// **Note:** This timer always counts up. +pub struct CaptureTimer<'d> { + mcpwm_info: &'static Info, + _phantom: PhantomData<&'d ()>, + _guard: PeripheralGuard, + _pwm_clock_guard: PwmClockGuard, +} + +impl<'d> CaptureTimer<'d> { + pub(super) fn new(guard: PeripheralGuard, mcpwm_info: &'static Info) -> Self { + Self { + mcpwm_info, + _phantom: PhantomData, + _guard: guard, + _pwm_clock_guard: PwmClockGuard::new(mcpwm_info), + } + } + + /// Start the capture timer + pub fn start(&mut self) { + self.cfg().modify(|_r, w| w.cap_timer_en().bit(true)); + } + + /// Pauses the capture timer + pub fn pause(&mut self) { + self.cfg().modify(|_r, w| w.cap_timer_en().bit(false)); + } + + /// Stops the timer and resets the timers counter to 0 + /// **Warning**: This sets the timers sync phase to 0 + pub fn reset(&mut self) { + self.cfg().modify(|_r, w| w.cap_timer_en().bit(false)); + self.phase().write(|w| unsafe { w.cap_phase().bits(0) }); + self.trigger_sync(); + } + + /// Configure the capture timer with the provided config + pub fn apply_config(&mut self, config: CaptureTimerConfig) { + self.phase() + .write(|w| unsafe { w.cap_phase().bits(config.sync_phase) }); + } + + /// Triggers a software sync event on the capture timer. + /// Refer to how sync events are handled in the [`CaptureTimer`] documentation. + pub fn trigger_sync(&mut self) { + self.cfg().modify(|_r, w| w.cap_sync_sw().set_bit()); + } + + /// Sets the capture timers sync source. Refer to how sync events are + /// handled in the [`CaptureTimer`] documentation. + pub fn set_sync_in(&mut self, sync_sel: SyncKind) { + // SAFETY: Only CAP_TIMER_CFG accessed; unique per PWM instance + self.cfg().modify(|_r, w| { + w.cap_synci_en().bit(sync_sel != SyncKind::None); + unsafe { w.cap_synci_sel().bits(sync_sel as u8) } + }); + } + + fn cfg(&mut self) -> &'d crate::Reg { + let info = self.mcpwm_info; + info.regs().cap_timer_cfg() + } + + fn phase(&mut self) -> &'d crate::Reg { + let info = self.mcpwm_info; + info.regs().cap_timer_phase() + } +} + +/// Represents the capture event +/// Contains the capture time and the captured edge +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CaptureEvent { + time: u32, + edge: CaptureEdge, +} + +impl CaptureEvent { + /// Gets the captured time + pub const fn time(&self) -> u32 { + self.time + } + + /// Gets the captured edge + pub const fn edge(&self) -> CaptureEdge { + self.edge + } +} + +/// Configuration for capture channel +#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +// Hash cannot be implemented for CaptureMode +pub struct CaptureChannelConfig { + invert: bool, + prescaler: u8, +} + +impl CaptureChannelConfig { + /// Sets the invert value for the config + pub fn with_invert(self, invert: bool) -> Self { + Self { invert, ..self } + } + + /// Sets the prescaler for the config + pub fn with_prescaler(self, prescaler: u8) -> Self { + Self { prescaler, ..self } + } +} + +/// A MCPWM Capture Channel +/// +/// The MCPWM Capture Channel has the following functions: +/// * Enable/Disable capturing on this channel +/// * Setting the capture input GPIO pin +/// * Whether to trigger capture events on rising and/or falling edges +/// * Read the last capture edge, and the last capture time +pub struct CaptureChannel<'d> { + mcpwm_info: &'static Info, + number: u8, + _phantom: PhantomData<&'d ()>, + _guard: PeripheralGuard, + _pwm_clock_guard: PwmClockGuard, +} + +impl<'d> CaptureChannel<'d> { + pub(super) fn new( + guard: PeripheralGuard, + mcpwm_info: &'static Info, + number: u8, + config: CaptureChannelConfig, + ) -> Self { + let mut channel = Self { + mcpwm_info, + number, + _phantom: PhantomData, + _guard: guard, + _pwm_clock_guard: PwmClockGuard::new(mcpwm_info), + }; + channel.configure(config); + channel + } + + pub(super) fn configure(&mut self, config: CaptureChannelConfig) { + self.cfg().modify(|_, w| { + w.in_invert().variant(config.invert); + unsafe { w.prescale().bits(config.prescaler) } + }); + } + + /// Assign the input signal for the capture + pub fn with_signal_input<'a>(self, input: impl PeripheralInput<'a>) -> Self { + let info = self.mcpwm_info; + let input_signal = info.capture_input_signal(self.number); + + if input_signal as usize <= property!("gpio.input_signal_max") { + let source = input.into(); + source.set_input_enable(true); + input_signal.connect_to(&source); + } else { + warn!("Signal {:?} out of range", input_signal); + } + + self + } + + /// Enable or disables this capture channel + pub fn set_enable(&mut self, enable: bool) { + self.cfg().modify(|_, w| w.en().variant(enable)); + } + + /// Set the config + pub fn apply_config(&mut self, config: CaptureChannelConfig) { + self.configure(config); + } + + /// Triggers a software capture on the current capture channel + pub fn trigger_capture(&mut self) { + self.cfg().modify(|_, w| w.sw().set_bit()); + } + + /// Sets the capture channel to listen to captures on specific edge events + /// This channel can listen to Falling, and/or Rising edges on any of the GPIO pins. + /// Using [`CaptureMode::None`] is the same as calling [`CaptureChannel::unlisten`] and will + /// stop listening to any events on this channel. + #[instability::unstable] + pub fn listen(&mut self, capture_mode: CaptureMode) { + self.cfg().modify(|_, w| w.mode().variant(capture_mode)); + + let info = self.mcpwm_info; + info.enable_listen( + self.number, + EnumSet::only(Event::Capture), + capture_mode != CaptureMode::None, + ); + } + + /// Stops listening to events on this channel + #[instability::unstable] + pub fn unlisten(&mut self) { + self.listen(CaptureMode::None); + } + + /// If the interrupt was set for this channel + #[instability::unstable] + pub fn is_interrupt_set(&self) -> bool { + let info = self.mcpwm_info; + info.interrupt_set(self.number, Event::Capture) + } + + /// Clear the interrupt + #[instability::unstable] + pub fn clear_interrupt(&self) { + let info = self.mcpwm_info; + info.clear_interrupt(self.number, Event::Capture); + } + + /// Gets the last captured event + #[instability::unstable] + pub fn events(&self) -> CaptureEvent { + let info = self.mcpwm_info; + let time = info + .regs() + .cap_ch(self.number as usize) + .read() + .value() + .bits(); + let edge = info + .regs() + .cap_status() + .read() + .cap_edge(self.number) + .variant(); + CaptureEvent { time, edge } + } + + fn cfg(&mut self) -> &'d crate::Reg { + self.mcpwm_info.regs().cap_ch_cfg(self.number as usize) + } +} diff --git a/esp-hal/src/mcpwm/mod.rs b/esp-hal/src/mcpwm/mod.rs index 15c992ee372..9508e756c9a 100644 --- a/esp-hal/src/mcpwm/mod.rs +++ b/esp-hal/src/mcpwm/mod.rs @@ -8,7 +8,7 @@ cfg(esp32s3) => "CRYPTO_PWM_CLK (160 MHz)", cfg(esp32c6) => "PLL_F160M (160 MHz)", cfg(esp32h2) => "PLL_F96M_CLK (96 MHz)", - } + }, ))] //! # Motor Control Pulse Width Modulator (MCPWM) //! @@ -25,13 +25,18 @@ //! - Generate Space Vector PWM (SVPWM) signals for Field Oriented Control (FOC) //! //! ## Configuration -//! //! * PWM Timers 0, 1 and 2 //! * Every PWM timer has a dedicated 8-bit clock prescaler. //! * The 16-bit counter in the PWM timer can work in count-up mode, count-down mode or //! count-up-down mode. //! * A hardware sync or software sync can trigger a reload on the PWM timer with a phase -//! register (Not yet implemented) +//! register. +//! * Timers run until a preconfigured [`timer::StopCondition`], this enum also specifies +//! running continuously. +//! * Timers can generate [`timer::TimerEvent`] interrupts based on hardware events. +//! * Each timer can produce hardware sync events based on [`timer::SyncOutSelect`]. +//! * Timers can be configured to receive hardware sync events from other timers, or external +//! GPIO pins. //! * PWM Operators 0, 1 and 2 //! * Every PWM operator has two PWM outputs: PWMxA and PWMxB. They can work independently, in //! symmetric and asymmetric configuration. @@ -39,14 +44,36 @@ //! * Configurable dead-time on rising and falling edges; each set up independently. (Not yet //! implemented) //! * All events can trigger CPU interrupts. (Not yet implemented) -//! * Modulating of PWM output by high-frequency carrier signals, useful when gate drivers are -//! insulated with a transformer. (Not yet implemented) //! * Period, time stamps and important control registers have shadow registers with flexible //! updating methods. +//! * Capture Channels 0, 1 and 2 +//! * Every capture channel has one signal input. With an optional invert filter +//! * Each capture module can be configured to detect rising and (or), falling edges on an +//! external signal. +//! * Capture channels can produce hardware capture events based on [`capture::CaptureEvent`]. +//! * To record the current value of the capture counter. A software trigger can be used to +//! trigger a capture event. Note: The edge that is captured is UNSPECIFIED for software +//! captures. +//! * Each capture channel can be configured with a 8-bit pre-scaler. Which only triggers +//! capture events every Nth edge captured. ( Useful for high frequencies ) +//! * Capture Timer: +//! * Capture timer can be configured with a sync in source. +//! * A hardware sync or software sync can trigger a reload of the capture timer's counter with +//! the set value called sync phase. +#![cfg_attr( + mcpwm_capture_clk_from_group, + doc = " * Capture timer's clock source is the same as the PWM timers clock source" +)] +#![cfg_attr( + not(mcpwm_capture_clk_from_group), + doc = " * Capture timer has its own independent clock source from the MCPWM peripheral." +)] //! * Fault Detection Module (Not yet implemented) -//! * Capture Module (Not yet implemented) -//! -//! Clock source is __clock_src__ by default. +#![cfg_attr( + not(mcpwm_capture_clk_from_group), + doc = "\nCapture clock source is `ADB-CLK (80 MHz)` by default.\n" +)] +//! Clock source is `__clock_src__` by default. //! //! ## Examples //! @@ -58,12 +85,12 @@ //! //! ```rust, no_run //! # {before_snippet} -//! # use esp_hal::mcpwm::{operator::{DeadTimeCfg, PWMStream, PwmPinConfig}, timer::PwmWorkingMode, McPwm, PeripheralClockConfig}; +//! # use esp_hal::mcpwm::{operator::{DeadTimeCfg, PWMStream, PwmPinConfig}, timer::PwmWorkingMode, McPwm, AnyMcPwm, PeripheralClockConfig}; //! # let pin = peripherals.GPIO0; //! //! // initialize peripheral //! let clock_cfg = PeripheralClockConfig::with_frequency(Rate::from_mhz(__mcpwm_freq__))?; -//! let mut mcpwm = McPwm::new(peripherals.MCPWM0, clock_cfg); +//! let mut mcpwm = McPwm::new(AnyMcPwm::from(peripherals.MCPWM0), clock_cfg); //! //! // connect operator0 to timer0 //! mcpwm.operator0.set_timer(&mcpwm.timer0); @@ -77,18 +104,28 @@ //! // of 20 kHz //! let timer_clock_cfg = clock_cfg //! .timer_clock_with_frequency(99, PwmWorkingMode::Increase, -//! Rate::from_khz(20))?; mcpwm.timer0.start(timer_clock_cfg); +//! Rate::from_khz(20))?; +//! mcpwm.timer0.apply_config(timer_clock_cfg)?; +//! mcpwm.timer0.start(); //! //! // pin will be high 50% of the time //! pwm_pin.set_timestamp(50); //! # {after_snippet} //! ``` -use operator::Operator; -use timer::Timer; +use enumset::{EnumSet, EnumSetType}; +#[cfg(soc_has_mcpwm0)] +use crate::mcpwm::{ + capture::{CaptureChannel, CaptureTimer}, + operator::Operator, + sync::SyncLine, + timer::Timer, +}; use crate::{ - gpio::OutputSignal, + gpio::{InputSignal, OutputSignal}, + interrupt::{self, InterruptHandler}, + mcpwm::capture::CaptureChannelConfig, pac, private::DropGuard, soc::clocks::{self, ClockTree}, @@ -96,77 +133,113 @@ use crate::{ time::Rate, }; +/// MCPWM capture channels +pub mod capture; /// MCPWM operators pub mod operator; +/// Sync +pub mod sync; /// MCPWM timers pub mod timer; -type RegisterBlock = pac::mcpwm0::RegisterBlock; - -#[allow(dead_code)] // Field is seemingly unused but we rely on its Drop impl -struct PwmClockGuard(DropGuard<(), fn(())>); - -impl PwmClockGuard { - fn instance() -> clocks::McpwmInstance { - match PWM::peripheral() { - Peripheral::Mcpwm0 => clocks::McpwmInstance::Mcpwm0, - #[cfg(soc_has_mcpwm1)] - Peripheral::Mcpwm1 => clocks::McpwmInstance::Mcpwm1, - _ => unreachable!(), - } +crate::any_peripheral! { + /// Any MCPWM peripheral. + pub peripheral AnyMcPwm<'d> { + #[cfg(soc_has_mcpwm0)] + Mcpwm0(crate::peripherals::MCPWM0<'d>), + #[cfg(soc_has_mcpwm1)] + Mcpwm1(crate::peripherals::MCPWM1<'d>), } +} - pub fn new() -> Self { - ClockTree::with(move |clocks| Self::instance::().request_function_clock(clocks)); - - Self(DropGuard::new((), |_| { - ClockTree::with(move |clocks| Self::instance::().release_function_clock(clocks)); - })) +impl Instance for AnyMcPwm<'_> { + fn info(&self) -> &'static Info { + any::delegate!(self, mcpwm => { mcpwm.info() }) } } /// The MCPWM peripheral #[non_exhaustive] -pub struct McPwm<'d, PWM> { - _inner: PWM, +pub struct McPwm<'d> { + mcpwm: AnyMcPwm<'d>, + /// Timer0 - pub timer0: Timer<0, PWM>, + pub timer0: Timer<'d>, /// Timer1 - pub timer1: Timer<1, PWM>, + pub timer1: Timer<'d>, /// Timer2 - pub timer2: Timer<2, PWM>, + pub timer2: Timer<'d>, + /// Capture Timer + pub capture_timer: CaptureTimer<'d>, + /// Operator0 - pub operator0: Operator<'d, 0, PWM>, + pub operator0: Operator<'d>, /// Operator1 - pub operator1: Operator<'d, 1, PWM>, + pub operator1: Operator<'d>, /// Operator2 - pub operator2: Operator<'d, 2, PWM>, + pub operator2: Operator<'d>, + + /// Capture0 + pub capture0: CaptureChannel<'d>, + /// Capture1 + pub capture1: CaptureChannel<'d>, + /// Capture2 + pub capture2: CaptureChannel<'d>, + + /// Sync line 0 + pub sync0: SyncLine, + /// Sync line 1 + pub sync1: SyncLine, + /// Sync line 2 + pub sync2: SyncLine, } -impl<'d, PWM: PwmPeripheral + 'd> McPwm<'d, PWM> { - /// `pwm_clk = clocks.crypto_pwm_clock / (prescaler + 1)` - pub fn new(peripheral: PWM, peripheral_clock: PeripheralClockConfig) -> Self { - let guard = PeripheralGuard::new(PWM::peripheral()); - - let register_block = unsafe { &*PWM::block() }; +impl<'d> McPwm<'d> { + /// Create a new instance generics + pub fn new(mcpwm: AnyMcPwm<'d>, peripheral_clock: PeripheralClockConfig) -> Self { + let info = mcpwm.info(); + let guard = PeripheralGuard::new(info.peripheral()); - // set prescaler - register_block + // set prescaler for timer (0-2) + info.regs() .clk_cfg() .write(|w| unsafe { w.clk_prescale().bits(peripheral_clock.prescaler) }); // enable clock - register_block.clk().write(|w| w.en().set_bit()); + info.regs().clk().write(|w| w.en().set_bit()); + let info = mcpwm.info(); Self { - _inner: peripheral, - timer0: Timer::new(guard.clone()), - timer1: Timer::new(guard.clone()), - timer2: Timer::new(guard.clone()), - operator0: Operator::new(guard.clone()), - operator1: Operator::new(guard.clone()), - operator2: Operator::new(guard), + mcpwm, + timer0: Timer::new(guard.clone(), info, 0, &peripheral_clock), + timer1: Timer::new(guard.clone(), info, 1, &peripheral_clock), + timer2: Timer::new(guard.clone(), info, 2, &peripheral_clock), + operator0: Operator::new(guard.clone(), 0, info), + operator1: Operator::new(guard.clone(), 1, info), + operator2: Operator::new(guard.clone(), 2, info), + capture_timer: CaptureTimer::new(guard.clone(), info), + capture0: CaptureChannel::new(guard.clone(), info, 0, CaptureChannelConfig::default()), + capture1: CaptureChannel::new(guard.clone(), info, 1, CaptureChannelConfig::default()), + capture2: CaptureChannel::new(guard, info, 2, CaptureChannelConfig::default()), + sync0: SyncLine::new(0, info), + sync1: SyncLine::new(1, info), + sync2: SyncLine::new(2, info), + } + } + + /// Set the interrupt handler for the MCPWM peripheral. + /// + /// Note that this will replace any previously registered interrupt + /// handlers. + #[instability::unstable] + pub fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + let info = self.mcpwm.info(); + let interrupt = info.interrupt(); + + for core in crate::system::Cpu::other() { + crate::interrupt::disable(core, interrupt); } + interrupt::bind_handler(interrupt, handler); } } @@ -272,66 +345,265 @@ impl PeripheralClockConfig { ) -> Result { timer::TimerClockConfig::with_frequency(self, period, mode, target_freq) } + + /// Get a timer clock configuration with the default values. + /// + /// ### Note: + /// - Prescaler defaults to the minimum value of `0`. + /// - Period defaults to the maximum value of `u16::MAX`. + /// - PWM working mode defaults to `PwmWorkingMode::Increase`. + /// + /// The frequency is calculated with the formula described in + /// [`PeripheralClockConfig::timer_clock_with_prescaler`] with the default prescaler value. + pub fn timer_clock_default(&self) -> timer::TimerClockConfig { + timer::TimerClockConfig::default(self) + } } /// Target frequency could not be set. /// Check how the frequency is calculated in the corresponding method docs. -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct FrequencyError; -/// A MCPWM peripheral -pub trait PwmPeripheral: crate::private::Sealed { - /// Get a pointer to the peripheral RegisterBlock - fn block() -> *const RegisterBlock; - /// Get operator GPIO mux output signal - fn output_signal() -> OutputSignal; - /// Peripheral - fn peripheral() -> Peripheral; +impl core::fmt::Display for FrequencyError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "Target frequency could not be set") + } } -#[cfg(soc_has_mcpwm0)] -impl PwmPeripheral for crate::peripherals::MCPWM0<'_> { - fn block() -> *const RegisterBlock { - Self::regs() +impl core::error::Error for FrequencyError {} + +type RegisterBlock = pac::mcpwm0::RegisterBlock; + +/// Peripheral info for an MCPWM instance. +#[doc(hidden)] +#[derive(Debug)] +#[non_exhaustive] +#[allow(private_interfaces, reason = "Unstable details")] +pub struct Info { + /// Register block + register_block: *const RegisterBlock, + /// System peripheral marker. + _peripheral: crate::system::Peripheral, + /// Interrupt marker + _interrupt: crate::peripherals::Interrupt, + /// Sync inputs + sync_input: [InputSignal; 3], + /// Capture inputs + capture_input: [InputSignal; 3], + /// Operator A outputs + operator_a_output: [OutputSignal; 3], + /// Operator B outputs + operator_b_output: [OutputSignal; 3], +} + +/// Dispatches event to register bit getter for interrupt status checks +macro_rules! dispatch_event_bit { + ($int_register:expr, $event:expr, $unit:expr) => { + match $event { + Event::TimerStop => $int_register.timer_stop($unit).bit(), + Event::TimerEqualZero => $int_register.timer_tez($unit).bit(), + Event::TimerEqualPeriod => $int_register.timer_tep($unit).bit(), + Event::Capture => $int_register.cap($unit).bit(), + Event::CompareA => $int_register.cmpr_tea($unit).bit(), + Event::CompareB => $int_register.cmpr_teb($unit).bit(), + Event::Fault => $int_register.fault($unit).bit(), + Event::FaultClear => $int_register.fault_clr($unit).bit(), + Event::FaultCycleByCycle => $int_register.tz_cbc($unit).bit(), + Event::FaultOneShotMode => $int_register.tz_ost($unit).bit(), + } + }; +} + +/// Dispatches event to register bit setter for interrupt control +macro_rules! dispatch_event_write { + ($int_register:expr, $event:expr, $unit:expr, $value:expr) => { + match $event { + Event::TimerStop => $int_register.timer_stop($unit).bit($value), + Event::TimerEqualZero => $int_register.timer_tez($unit).bit($value), + Event::TimerEqualPeriod => $int_register.timer_tep($unit).bit($value), + Event::Capture => $int_register.cap($unit).bit($value), + Event::CompareA => $int_register.cmpr_tea($unit).bit($value), + Event::CompareB => $int_register.cmpr_teb($unit).bit($value), + Event::Fault => $int_register.fault($unit).bit($value), + Event::FaultClear => $int_register.fault_clr($unit).bit($value), + Event::FaultCycleByCycle => $int_register.tz_cbc($unit).bit($value), + Event::FaultOneShotMode => $int_register.tz_ost($unit).bit($value), + } + }; +} + +/// Event types for MCPWM +#[derive(Debug, EnumSetType)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[doc(hidden)] +pub enum Event { + /// Event for when a timer stops + TimerStop, + /// Event for when a timer equals zero + TimerEqualZero, + /// Event for when a timer equals period + TimerEqualPeriod, + /// Event for a fault + Fault, + /// Event for a fault clear + FaultClear, + /// Event for a compare A + CompareA, + /// Event for a compare B + CompareB, + /// Event for a fault cycle by cycle + FaultCycleByCycle, + /// Event for a fault one shot mode + FaultOneShotMode, + /// Event for a capture + Capture, +} + +/// A peripheral singleton compatible with the MCPWM driver. +pub trait Instance: crate::private::Sealed + any::Degrade { + /// Returns the peripheral data describing this instance. + #[doc(hidden)] + fn info(&self) -> &'static Info; +} + +impl Info { + /// Returns the register block for this PWM instance. + pub fn regs(&self) -> &RegisterBlock { + unsafe { &*self.register_block } } - fn output_signal() -> OutputSignal { - match (OP, IS_A) { - (0, true) => OutputSignal::PWM0_0A, - (1, true) => OutputSignal::PWM0_1A, - (2, true) => OutputSignal::PWM0_2A, - (0, false) => OutputSignal::PWM0_0B, - (1, false) => OutputSignal::PWM0_1B, - (2, false) => OutputSignal::PWM0_2B, - _ => unreachable!(), + /// Returns the peripheral + pub fn peripheral(&self) -> crate::system::Peripheral { + self._peripheral + } + + /// Returns the interrupt marker + pub fn interrupt(&self) -> crate::peripherals::Interrupt { + self._interrupt + } + + /// Returns the output signal for operators + pub fn operator_output_signal(&self, operator: u8, is_a: bool) -> OutputSignal { + match is_a { + true => self.operator_a_output[operator as usize], + false => self.operator_b_output[operator as usize], } } - fn peripheral() -> Peripheral { - Peripheral::Mcpwm0 + /// Returns the sync input signal + pub fn sync_input_signal(&self, sync: u8) -> InputSignal { + self.sync_input[sync as usize] + } + + /// Returns the capture input signal + pub fn capture_input_signal(&self, chan: u8) -> InputSignal { + self.capture_input[chan as usize] + } + + /// Return if the interrupt for an event is set + pub fn interrupt_set(&self, unit: u8, event: Event) -> bool { + let regs = self.regs(); + let int_st = regs.int_st().read(); + dispatch_event_bit!(int_st, event, unit) + } + + /// Clear the interrupt for an event on a specific UNIT # + pub fn clear_interrupt(&self, unit: u8, event: Event) { + let regs = self.regs(); + regs.int_clr().write(|w| { + dispatch_event_write!(w, event, unit, true); + w + }); + } + + /// Enables listening for an event on a specific UNIT # + pub fn enable_listen(&self, unit: u8, events: EnumSet, value: bool) { + let regs = self.regs(); + regs.int_ena().modify(|_, w| { + for event in events { + dispatch_event_write!(w, event, unit, value); + } + w + }); } } -#[cfg(soc_has_mcpwm1)] -impl PwmPeripheral for crate::peripherals::MCPWM1<'_> { - fn block() -> *const RegisterBlock { - Self::regs() +impl PartialEq for Info { + fn eq(&self, other: &Self) -> bool { + core::ptr::eq(self.register_block, other.register_block) } +} + +unsafe impl Sync for Info {} + +// Create an `Instance` impl for each MCPWM peripheral +for_each_mcpwm!( + ($id:literal, $inst:ident, $sys:ident) => { + paste::paste! { + impl Instance for crate::peripherals::$inst<'_> { + /// Returns peripheral data for MCPWM $id + fn info(&self) -> &'static Info { + static INFO: Info = Info { + register_block: crate::peripherals::$inst::ptr(), + _peripheral: crate::system::Peripheral::$sys, + _interrupt: crate::peripherals::Interrupt::$inst, + sync_input: [ + InputSignal::[], + InputSignal::[], + InputSignal::[], + ], + capture_input: [ + InputSignal::[], + InputSignal::[], + InputSignal::[], + ], + operator_a_output: [ + OutputSignal::[], + OutputSignal::[], + OutputSignal::[], + ], + operator_b_output: [ + OutputSignal::[], + OutputSignal::[], + OutputSignal::[], + ], + }; + + &INFO + } + } + } + }; +); + +#[allow(dead_code)] // Field is seemingly unused but we rely on its Drop impl +struct PwmClockGuard(DropGuard); - fn output_signal() -> OutputSignal { - match (OP, IS_A) { - (0, true) => OutputSignal::PWM1_0A, - (1, true) => OutputSignal::PWM1_1A, - (2, true) => OutputSignal::PWM1_2A, - (0, false) => OutputSignal::PWM1_0B, - (1, false) => OutputSignal::PWM1_1B, - (2, false) => OutputSignal::PWM1_2B, +impl PwmClockGuard { + fn instance(mcpwm_info: &'static Info) -> clocks::McpwmInstance { + match mcpwm_info.peripheral() { + Peripheral::Mcpwm0 => clocks::McpwmInstance::Mcpwm0, + #[cfg(soc_has_mcpwm1)] + Peripheral::Mcpwm1 => clocks::McpwmInstance::Mcpwm1, _ => unreachable!(), } } - fn peripheral() -> Peripheral { - Peripheral::Mcpwm1 + pub fn new(mcpwm_info: &'static Info) -> Self { + ClockTree::with(move |clocks| Self::instance(mcpwm_info).request_function_clock(clocks)); + + Self(DropGuard::new(Self::instance(mcpwm_info), move |mcpwm| { + ClockTree::with(move |clocks| mcpwm.release_function_clock(clocks)); + })) + } +} + +impl<'d> crate::private::Sealed for McPwm<'d> {} +#[instability::unstable] +impl<'d> crate::interrupt::InterruptConfigurable for McPwm<'d> { + fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + self.set_interrupt_handler(handler); } } diff --git a/esp-hal/src/mcpwm/operator.rs b/esp-hal/src/mcpwm/operator.rs index 8d035a44baa..9ead7dfedb9 100644 --- a/esp-hal/src/mcpwm/operator.rs +++ b/esp-hal/src/mcpwm/operator.rs @@ -7,14 +7,14 @@ //! ## Configuration //! This module provides flexibility in configuring the PWM outputs. Its //! implementation allows for motor control and other applications that demand -//! accurate pulse timing and sophisticated modulation techniques. +//! accurate pulse timing and modulation techniques. use core::marker::PhantomData; use super::PeripheralGuard; use crate::{ gpio::interconnect::{OutputSignal, PeripheralOutput}, - mcpwm::{PwmClockGuard, PwmPeripheral, timer::Timer}, + mcpwm::{Info, PwmClockGuard, timer::Timer}, pac, }; @@ -30,7 +30,8 @@ pub enum PWMStream { /// Configuration for MCPWM Operator DeadTime /// It's recommended to reference the technical manual for configuration -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct DeadTimeCfg { cfg_reg: u32, } @@ -167,24 +168,23 @@ impl DeadTimeCfg { /// implemented) /// * Superimposes a carrier on the PWM signal, if configured to do so. (Not yet implemented) /// * Handles response under fault conditions. (Not yet implemented) -pub struct Operator<'d, const OP: u8, PWM> { - phantom: PhantomData<&'d PWM>, +pub struct Operator<'d> { + mcpwm_info: &'static Info, + number: u8, + _phantom: PhantomData<&'d ()>, _guard: PeripheralGuard, _pwm_clock_guard: PwmClockGuard, } -impl<'d, const OP: u8, PWM: PwmPeripheral> Operator<'d, OP, PWM> { - pub(super) fn new(guard: PeripheralGuard) -> Self { - // Side note: - // It would have been nice to deselect any timer reference on peripheral - // initialization. - // However experimentation (ESP32-S3) showed that writing `3` to timersel - // will not disable the timer reference but instead act as though `2` was - // written. - Operator { - phantom: PhantomData, +impl<'d> Operator<'d> { + pub(super) fn new(guard: PeripheralGuard, number: u8, mcpwm_info: &'static Info) -> Self { + // NOTE: Writing 3 to timersel does not disable timer reference (hardware limitation) + Self { + mcpwm_info, + number, + _phantom: PhantomData, _guard: guard, - _pwm_clock_guard: PwmClockGuard::new::(), + _pwm_clock_guard: PwmClockGuard::new(mcpwm_info), } } @@ -192,19 +192,12 @@ impl<'d, const OP: u8, PWM: PwmPeripheral> Operator<'d, OP, PWM> { /// /// ### Note: /// By default TIMER0 is used - pub fn set_timer(&mut self, timer: &Timer) { - let _ = timer; + pub fn set_timer(&mut self, timer: &Timer<'d>) { // SAFETY: // We only write to our OPERATORx_TIMERSEL register - let block = unsafe { &*PWM::block() }; - block.operator_timersel().modify(|_, w| match OP { - 0 => unsafe { w.operator0_timersel().bits(TIM) }, - 1 => unsafe { w.operator1_timersel().bits(TIM) }, - 2 => unsafe { w.operator2_timersel().bits(TIM) }, - _ => { - unreachable!() - } - }); + let timer_select = unsafe { self.timesel() }; + timer_select + .modify(|_, w| unsafe { w.operator_timersel(self.number).bits(timer.number()) }); } /// Use the A output with the given pin and configuration @@ -212,8 +205,8 @@ impl<'d, const OP: u8, PWM: PwmPeripheral> Operator<'d, OP, PWM> { self, pin: impl PeripheralOutput<'d>, config: PwmPinConfig, - ) -> PwmPin<'d, PWM, OP, true> { - PwmPin::new(pin, config) + ) -> PwmPin<'d, true> { + PwmPin::new(pin, self.mcpwm_info, self.number, config) } /// Use the B output with the given pin and configuration @@ -221,8 +214,8 @@ impl<'d, const OP: u8, PWM: PwmPeripheral> Operator<'d, OP, PWM> { self, pin: impl PeripheralOutput<'d>, config: PwmPinConfig, - ) -> PwmPin<'d, PWM, OP, false> { - PwmPin::new(pin, config) + ) -> PwmPin<'d, false> { + PwmPin::new(pin, self.mcpwm_info, self.number, config) } /// Use both the A and the B output with the given pins and configurations @@ -232,8 +225,11 @@ impl<'d, const OP: u8, PWM: PwmPeripheral> Operator<'d, OP, PWM> { config_a: PwmPinConfig, pin_b: impl PeripheralOutput<'d>, config_b: PwmPinConfig, - ) -> (PwmPin<'d, PWM, OP, true>, PwmPin<'d, PWM, OP, false>) { - (PwmPin::new(pin_a, config_a), PwmPin::new(pin_b, config_b)) + ) -> (PwmPin<'d, true>, PwmPin<'d, false>) { + ( + PwmPin::new(pin_a, self.mcpwm_info, self.number, config_a), + PwmPin::new(pin_b, self.mcpwm_info, self.number, config_b), + ) } /// Link two pins using the deadtime generator @@ -247,8 +243,23 @@ impl<'d, const OP: u8, PWM: PwmPeripheral> Operator<'d, OP, PWM> { pin_b: impl PeripheralOutput<'d>, config_b: PwmPinConfig, config_dt: DeadTimeCfg, - ) -> LinkedPins<'d, PWM, OP> { - LinkedPins::new(pin_a, config_a, pin_b, config_b, config_dt) + ) -> LinkedPins<'d> { + LinkedPins::new( + pin_a, + config_a, + pin_b, + config_b, + config_dt, + self.mcpwm_info, + self.number, + ) + } + + /// Unsafe access to the OPERATORx_TIMERSEL register + /// Caller must ensure they only write to the bits corresponding to their operator + unsafe fn timesel(&self) -> &'static pac::mcpwm0::OPERATOR_TIMERSEL { + let info = self.mcpwm_info; + info.regs().operator_timersel() } } @@ -284,27 +295,35 @@ impl PwmPinConfig { } /// A pin driven by an MCPWM operator -pub struct PwmPin<'d, PWM, const OP: u8, const IS_A: bool> { +pub struct PwmPin<'d, const IS_A: bool> { + mcpwm_info: &'static Info, + operator: u8, pin: OutputSignal<'d>, - phantom: PhantomData, _guard: PeripheralGuard, } -impl<'d, PWM: PwmPeripheral, const OP: u8, const IS_A: bool> PwmPin<'d, PWM, OP, IS_A> { - fn new(pin: impl PeripheralOutput<'d>, config: PwmPinConfig) -> Self { +impl<'d, const IS_A: bool> PwmPin<'d, IS_A> { + fn new( + pin: impl PeripheralOutput<'d>, + mcpwm_info: &'static Info, + operator: u8, + config: PwmPinConfig, + ) -> Self { let pin = pin.into(); - - let guard = PeripheralGuard::new(PWM::peripheral()); + let info = mcpwm_info; + let guard = PeripheralGuard::new(info.peripheral()); let mut pin = PwmPin { pin, - phantom: PhantomData, + mcpwm_info, + operator, _guard: guard, }; pin.set_actions(config.actions); pin.set_update_method(config.update_method); - PWM::output_signal::().connect_to(&pin.pin); + let signal = info.operator_output_signal(operator, IS_A); + signal.connect_to(&pin.pin); pin.pin.set_output_enable(true); pin @@ -314,7 +333,7 @@ impl<'d, PWM: PwmPeripheral, const OP: u8, const IS_A: bool> PwmPin<'d, PWM, OP, pub fn set_actions(&mut self, value: PwmActions) { // SAFETY: // We only write to our GENx_x register - let ch = unsafe { Self::ch() }; + let ch = unsafe { self.ch() }; let bits = value.0; // SAFETY: @@ -326,7 +345,7 @@ impl<'d, PWM: PwmPeripheral, const OP: u8, const IS_A: bool> PwmPin<'d, PWM, OP, pub fn set_update_method(&mut self, update_method: PwmUpdateMethod) { // SAFETY: // We only write to our GENx_x_UPMETHOD register - let ch = unsafe { Self::ch() }; + let ch = unsafe { self.ch() }; let bits = update_method.0; #[cfg(esp32s3)] @@ -349,7 +368,7 @@ impl<'d, PWM: PwmPeripheral, const OP: u8, const IS_A: bool> PwmPin<'d, PWM, OP, pub fn set_timestamp(&mut self, value: u16) { // SAFETY: // We only write to our GENx_TSTMP_x register - let ch = unsafe { Self::ch() }; + let ch = unsafe { self.ch() }; #[cfg(esp32s3)] if IS_A { @@ -372,7 +391,7 @@ impl<'d, PWM: PwmPeripheral, const OP: u8, const IS_A: bool> PwmPin<'d, PWM, OP, pub fn timestamp(&self) -> u16 { // SAFETY: // We only read to our GENx_TSTMP_x register - let ch = unsafe { Self::ch() }; + let ch = unsafe { self.ch() }; #[cfg(esp32s3)] if IS_A { @@ -393,10 +412,10 @@ impl<'d, PWM: PwmPeripheral, const OP: u8, const IS_A: bool> PwmPin<'d, PWM, OP, pub fn period(&self) -> u16 { // SAFETY: // We only grant access to our CFG0 register with the lifetime of &mut self - let block = unsafe { &*PWM::block() }; + let info = self.mcpwm_info; - let tim_select = block.operator_timersel().read(); - let tim = match OP { + let tim_select = info.regs().operator_timersel().read(); + let tim = match self.operator { 0 => tim_select.operator0_timersel().bits(), 1 => tim_select.operator1_timersel().bits(), 2 => tim_select.operator2_timersel().bits(), @@ -408,26 +427,29 @@ impl<'d, PWM: PwmPeripheral, const OP: u8, const IS_A: bool> PwmPin<'d, PWM, OP, // SAFETY: // The CFG0 registers are identical for all timers so we can pretend they're // TIMER0_CFG0 - block.timer(tim as usize).cfg0().read().period().bits() + info.regs() + .timer(tim as usize) + .cfg0() + .read() + .period() + .bits() } - unsafe fn ch() -> &'static pac::mcpwm0::CH { - let block = unsafe { &*PWM::block() }; - block.ch(OP as usize) + unsafe fn ch(&self) -> &'d pac::mcpwm0::CH { + // Unsafe since caller needs to ensure there isn't multiple references to the same + // operator + let info = self.mcpwm_info; + info.regs().ch(self.operator as usize) } } /// Implement no error type for the PwmPin because the method are infallible -impl embedded_hal::pwm::ErrorType - for PwmPin<'_, PWM, OP, IS_A> -{ +impl embedded_hal::pwm::ErrorType for PwmPin<'_, IS_A> { type Error = core::convert::Infallible; } /// Implement the trait SetDutyCycle for PwmPin -impl embedded_hal::pwm::SetDutyCycle - for PwmPin<'_, PWM, OP, IS_A> -{ +impl embedded_hal::pwm::SetDutyCycle for PwmPin<'_, IS_A> { /// Get the max duty of the PwmPin fn max_duty_cycle(&self) -> u16 { self.period() @@ -455,8 +477,11 @@ impl embedded_hal::pwm::SetD /// /// ```rust, no_run /// # {before_snippet} -/// # use esp_hal::mcpwm::{McPwm, PeripheralClockConfig}; +/// # use esp_hal::mcpwm::{McPwm, AnyMcPwm, PeripheralClockConfig}; /// # use esp_hal::mcpwm::operator::{DeadTimeCfg, PwmPinConfig, PWMStream}; +/// # use esp_hal::time::Rate; +/// # use core::convert::From; +/// /// // active high complementary using PWMA input /// let bridge_active = DeadTimeCfg::new_ahc(); /// @@ -464,7 +489,7 @@ impl embedded_hal::pwm::SetD /// let bridge_off = DeadTimeCfg::new_bypass().set_output_swap(PWMStream::PWMA, true); /// /// let mut mcpwm = McPwm::new( -/// peripherals.MCPWM0, +/// AnyMcPwm::from(peripherals.MCPWM0), /// PeripheralClockConfig::with_frequency(Rate::from_mhz(__mcpwm_clk__))?, /// ); /// @@ -486,30 +511,39 @@ impl embedded_hal::pwm::SetD /// // pin_b: ------_________-----------_________----- /// # {after_snippet} /// ``` -pub struct LinkedPins<'d, PWM, const OP: u8> { - pin_a: PwmPin<'d, PWM, OP, true>, - pin_b: PwmPin<'d, PWM, OP, false>, +pub struct LinkedPins<'d> { + mcpwm_info: &'static Info, + operator: u8, + pin_a: PwmPin<'d, true>, + pin_b: PwmPin<'d, false>, } -impl<'d, PWM: PwmPeripheral, const OP: u8> LinkedPins<'d, PWM, OP> { +impl<'d> LinkedPins<'d> { fn new( pin_a: impl PeripheralOutput<'d>, config_a: PwmPinConfig, pin_b: impl PeripheralOutput<'d>, config_b: PwmPinConfig, config_dt: DeadTimeCfg, + mcpwm_info: &'static Info, + operator: u8, ) -> Self { // setup deadtime config before enabling the pins #[cfg(esp32s3)] - let dt_cfg = unsafe { Self::ch() }.db_cfg(); + let dt_cfg = mcpwm_info.regs().ch(operator as usize).db_cfg(); #[cfg(not(esp32s3))] - let dt_cfg = unsafe { Self::ch() }.dt_cfg(); + let dt_cfg = mcpwm_info.regs().ch(operator as usize).dt_cfg(); dt_cfg.write(|w| unsafe { w.bits(config_dt.cfg_reg) }); - let pin_a = PwmPin::new(pin_a, config_a); - let pin_b = PwmPin::new(pin_b, config_b); + let pin_a = PwmPin::new(pin_a, mcpwm_info, operator, config_a); + let pin_b = PwmPin::new(pin_b, mcpwm_info, operator, config_b); - LinkedPins { pin_a, pin_b } + LinkedPins { + pin_a, + pin_b, + mcpwm_info, + operator, + } } /// Configure what actions should be taken on timing events @@ -546,32 +580,32 @@ impl<'d, PWM: PwmPeripheral, const OP: u8> LinkedPins<'d, PWM, OP> { /// Configure the deadtime generator pub fn set_deadtime_cfg(&mut self, config: DeadTimeCfg) { #[cfg(esp32s3)] - let dt_cfg = unsafe { Self::ch() }.db_cfg(); + let dt_cfg = unsafe { self.ch() }.db_cfg(); #[cfg(not(esp32s3))] - let dt_cfg = unsafe { Self::ch() }.dt_cfg(); + let dt_cfg = unsafe { self.ch() }.dt_cfg(); dt_cfg.write(|w| unsafe { w.bits(config.cfg_reg) }); } /// Set the deadtime generator rising edge delay pub fn set_rising_edge_deadtime(&mut self, dead_time: u16) { #[cfg(esp32s3)] - let dt_red = unsafe { Self::ch() }.db_red_cfg(); + let dt_red = unsafe { self.ch() }.db_red_cfg(); #[cfg(not(esp32s3))] - let dt_red = unsafe { Self::ch() }.dt_red_cfg(); + let dt_red = unsafe { self.ch() }.dt_red_cfg(); dt_red.write(|w| unsafe { w.red().bits(dead_time) }); } /// Set the deadtime generator falling edge delay pub fn set_falling_edge_deadtime(&mut self, dead_time: u16) { #[cfg(esp32s3)] - let dt_fed = unsafe { Self::ch() }.db_fed_cfg(); + let dt_fed = unsafe { self.ch() }.db_fed_cfg(); #[cfg(not(esp32s3))] - let dt_fed = unsafe { Self::ch() }.dt_fed_cfg(); + let dt_fed = unsafe { self.ch() }.dt_fed_cfg(); dt_fed.write(|w| unsafe { w.fed().bits(dead_time) }); } - unsafe fn ch() -> &'static pac::mcpwm0::CH { - let block = unsafe { &*PWM::block() }; - block.ch(OP as usize) + unsafe fn ch(&self) -> &'d pac::mcpwm0::CH { + let info = self.mcpwm_info; + info.regs().ch(self.operator as usize) } } diff --git a/esp-hal/src/mcpwm/sync.rs b/esp-hal/src/mcpwm/sync.rs new file mode 100644 index 00000000000..0f0ea838c63 --- /dev/null +++ b/esp-hal/src/mcpwm/sync.rs @@ -0,0 +1,135 @@ +#![cfg_attr(docsrs, procmacros::doc_replace( + "mcpwm_freq" => { + cfg(not(esp32h2)) => "40", + cfg(esp32h2) => "32" + } +))] +//! # MCPWM Sync Module +//! +//! ## Overview +//! The `Sync` is responsible for managing the different ways +//! MCPWM can listen to sync events. There are 2 different types of +//! sync sources. One is a [`SyncOut`] that comes from [`super::Timer::sync_out`], +//! or from a [`SyncLine`]. +//! +//! This module provides the flexibility to map any of the +//! MCPWM's [`SyncLine`] to any GPIO signal. +//! +//! ## Example +//! +//! ### Configuring a SyncLine signal +//! For configuring a [`SyncLine`] input signal, and then connecting +//! it to timer 0's sync in event. This is useful when you need to sync +//! the timers phase from an external signal, such as a zero-cross event +//! for 3-phase PWM. +//! +//! ```rust, no_run +//! # {before_snippet} +//! # use esp_hal::{ +//! # mcpwm::{McPwm, AnyMcPwm, PeripheralClockConfig}, +//! # time::Rate, +//! # }; +//! # let pin = peripherals.GPIO0; +//! +//! // initialize peripheral +//! let clock_cfg = PeripheralClockConfig::with_frequency(Rate::from_mhz(__mcpwm_freq__))?; +//! let mut mcpwm = McPwm::new(AnyMcPwm::from(peripherals.MCPWM0), clock_cfg); +//! +//! // connect sync line 0 to take input from `pin` +//! mcpwm.sync0.set_signal(pin); +//! +//! // connecting sync line 0 for a timer0's sync in +//! mcpwm.timer0.set_sync_in(mcpwm.sync0.get_sync_out()); +//! # {after_snippet} +//! ``` +//! +//! ### Chaining 2 or more timers sync events +//! This is useful when many timers require +//! to be phase aligned for proper timing. +//! +//! ```rust, no_run +//! # {before_snippet} +//! # use esp_hal::{ +//! # mcpwm::{McPwm, AnyMcPwm, PeripheralClockConfig}, +//! # time::Rate, +//! # }; +//! +//! // initialize peripheral +//! let clock_cfg = PeripheralClockConfig::with_frequency(Rate::from_mhz(__mcpwm_freq__))?; +//! let mut mcpwm = McPwm::new(AnyMcPwm::from(peripherals.MCPWM0), clock_cfg); +//! +//! // set timer1's sync in +//! mcpwm.timer1.set_sync_in(mcpwm.timer0.get_sync_out()); +//! // set timer2's sync in +//! mcpwm.timer2.set_sync_in(mcpwm.timer1.get_sync_out()); +//! # {after_snippet} +//! ``` + +use crate::{gpio::interconnect::PeripheralInput, mcpwm::Info}; + +/// Sync line for MCPWM +pub struct SyncLine { + number: u8, + mcpwm_info: &'static Info, +} + +impl SyncLine { + pub(crate) fn new(number: u8, mcpwm_info: &'static Info) -> Self { + Self { number, mcpwm_info } + } + + /// Set the input signal for the sync line + pub fn set_signal<'d>(&self, source: impl PeripheralInput<'d>) { + // configure GPIO matrix → SYNC + let info = self.mcpwm_info; + let signal = info.sync_input_signal(self.number); + + if signal as usize <= property!("gpio.input_signal_max") { + let source = source.into(); + source.set_input_enable(true); + signal.connect_to(&source); + } else { + warn!("Signal {:?} out of range", signal); + } + } + + /// Inverts the input signal from the supplied input source + /// If invert is true sync events are triggered on falling edges. + /// If invert is false sync events are triggered on rising edges. + pub fn set_invert(&self, invert: bool) { + self.mcpwm_info + .regs() + .timer_synci_cfg() + .modify(|_, w| w.external_synci_invert(self.number).variant(invert)); + } + + /// Get the sync out for this sync line + pub fn get_sync_out(&self) -> SyncKind { + match self.number { + 0 => SyncKind::SyncLine0, + 1 => SyncKind::SyncLine1, + 2 => SyncKind::SyncLine2, + _ => unreachable!(), + } + } +} + +/// Values for any of the sync selection fields in the timer configuration +#[repr(u8)] +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum SyncKind { + /// Select no sync input for the timer + None = 0, + /// Sync out from timer0 + Timer0Sync = 1, + /// Sync out from timer1 + Timer1Sync = 2, + /// Sync out from timer2 + Timer2Sync = 3, + /// Sync line 0 + SyncLine0 = 4, + /// Sync line 1 + SyncLine1 = 5, + /// Sync line 2 + SyncLine2 = 6, +} diff --git a/esp-hal/src/mcpwm/timer.rs b/esp-hal/src/mcpwm/timer.rs index 14053aefb94..ae0703b25b7 100644 --- a/esp-hal/src/mcpwm/timer.rs +++ b/esp-hal/src/mcpwm/timer.rs @@ -3,67 +3,103 @@ //! ## Overview //! The `timer` module provides an interface to configure and use timers for //! generating `PWM` signals used in motor control and other applications. +//! +//! * Timers can be configured with different clock frequencies, periods, and prescalers using the +//! [`TimerClockConfig`] struct. +//! +//! ## Sync Events +//! * Timers can be configured to generate sync events when the timer counter equals zero, equals +//! the period, or when a sync in is received. +//! * Timers can be configured to listen to sync events from different sources such as other timers +//! or external sync inputs. More information in [`crate::mcpwm::sync`]. +#![cfg_attr( + mcpwm_swsync_can_propagate, + doc = "\n * **Note:** Software triggered sync events from timers will propagate to their respective sync outputs on this chip." +)] +#![cfg_attr( + not(mcpwm_swsync_can_propagate), + doc = "\n * **Note:** Software triggered sync events do not propagate to other timers on this chip." +)] use core::marker::PhantomData; +use enumset::{EnumSet, EnumSetType}; + use super::PeripheralGuard; use crate::{ - mcpwm::{FrequencyError, PeripheralClockConfig, PwmClockGuard, PwmPeripheral}, + mcpwm::{Event, FrequencyError, Info, PeripheralClockConfig, PwmClockGuard, sync::SyncKind}, pac, time::Rate, }; +#[derive(Debug, EnumSetType)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +#[instability::unstable] +pub enum TimerEvent { + /// Event for when a timer stops + Stop, + /// Event for when a timer equals zero + EqualZero, + /// Event for when a timer equals period + EqualPeriod, +} + /// A MCPWM timer /// /// Every timer of a particular [`MCPWM`](super::McPwm) peripheral can be used /// as a timing reference for every /// [`Operator`](super::operator::Operator) of that peripheral -pub struct Timer { - pub(super) phantom: PhantomData, +pub struct Timer<'d> { + /// Sync out for the timer that can be connected to other timers sync in or operators sync in + number: u8, + mcpwm_info: &'static Info, + _phantom: PhantomData<&'d ()>, _guard: PeripheralGuard, _pwm_clock_guard: PwmClockGuard, + config: TimerClockConfig, } -impl Timer { - pub(super) fn new(guard: PeripheralGuard) -> Self { - Timer { - phantom: PhantomData, +impl<'d> Timer<'d> { + pub(super) fn new( + guard: PeripheralGuard, + mcpwm_info: &'static Info, + number: u8, + peripheral_clock: &PeripheralClockConfig, + ) -> Self { + // Default configuration for the timer + let config = TimerClockConfig::default(peripheral_clock); + + let mut timer = Timer { + number, + mcpwm_info, + _phantom: PhantomData, _guard: guard, - _pwm_clock_guard: PwmClockGuard::new::(), - } + _pwm_clock_guard: PwmClockGuard::new(mcpwm_info), + config, + }; + timer.configure(); + + timer } - /// Apply the given timer configuration. - /// - /// ### Note: - /// The prescaler and period configuration will be applied immediately by - /// default and before setting the [`PwmWorkingMode`]. - /// If the timer is already running you might want to call [`Timer::stop`] - /// and/or [`Timer::set_counter`] first - /// (if the new period is larger than the current counter value this will - /// cause weird behavior). - /// - /// If configured via [`TimerClockConfig::with_period_updating_method`], - /// another behavior can be applied. Currently, only - /// [`PeriodUpdatingMethod::Immediately`] - /// and [`PeriodUpdatingMethod::TimerEqualsZero`] are useful as the sync - /// method is not yet implemented. - /// - /// The hardware supports writing these settings in sync with certain timer - /// events but this HAL does not expose these for now. - pub fn start(&mut self, timer_config: TimerClockConfig) { - // write prescaler and period with immediate update method - self.cfg0().write(|w| unsafe { - w.prescale().bits(timer_config.prescaler); - w.period().bits(timer_config.period); - w.period_upmethod() - .bits(timer_config.period_updating_method as u8) - }); + /// Start the timer with the current configuration. + /// Refer to the documentation of [`StopCondition`] and + /// [`TimerClockConfig`] for more details on the + /// configuration of the timer. + pub fn start(&mut self) { + // set timer to run with a stop condition + let stop_condition = self.config.stop_condition as u8; + let mode = self.config.mode as u8; + + // Update sync counter direction and phase according to the configuration + // Since `Timer::set_counter` can change the phase and counter direction. + self.set_sync_counter_direction(self.config.sync_direction); + self.set_sync_phase(self.config.sync_phase); - // set timer to continuously run and set the timer working mode self.cfg1().write(|w| unsafe { - w.start().bits(2); - w.mod_().bits(timer_config.mode as u8) + w.start().bits(stop_condition); + w.mod_().bits(mode) }); } @@ -73,44 +109,268 @@ impl Timer { self.cfg1().write(|w| unsafe { w.mod_().bits(0) }); } + /// ### Note: + /// * The prescaler configuration will be applied immediately. + /// * The period configuration will be applied based on the [`PeriodUpdatingMethod`] set in the + /// [`TimerClockConfig`]. + /// + /// If the timer is already running you might want to call [`Timer::stop`] first. + /// + /// If your [`PeriodUpdatingMethod`] is set to [`PeriodUpdatingMethod::Immediately`] + /// and if your new period is smaller than the current counter value as this will cause weird + /// behavior. + /// + /// ### Config Constraints + /// The valid range for phase depends on the timer configuration: + /// - When the timer is in [`PwmWorkingMode::UpDown`] mode: + /// - The valid range for phase is `[0, period]` when the sync direction is + /// [`CounterDirection::Increasing`]. + /// - The valid range for phase is `[1, period+1]` when the sync direction is + /// [`CounterDirection::Decreasing`]. + /// - The valid range for phase is `[0, period+1]` when the timer is in another + /// [`PwmWorkingMode`]. + pub fn apply_config(&mut self, config: TimerClockConfig) -> Result<(), ConfigError> { + // Check for valid phase range + if config.sync_phase > config.period.saturating_add(1) { + return Err(ConfigError::InvalidPhaseRange); + } + + if config.mode == PwmWorkingMode::UpDown { + match config.sync_direction { + CounterDirection::Increasing if config.sync_phase > config.period => { + return Err(ConfigError::InvalidPhaseRange); + } + CounterDirection::Decreasing if config.sync_phase == 0 => { + return Err(ConfigError::InvalidPhaseRange); + } + _ => {} + } + } + + self.config = config; + self.configure(); + Ok(()) + } + /// Set the timer counter to the provided value + /// + /// ## Overview + /// Internally we set the timers phase and direction + /// Then trigger a software sync event. + #[cfg_attr( + docsrs, + doc(cfg(mcpwm_swsync_can_propagate)), + doc = "**Note:** Software triggered sync events from timers will propagate to their respective sync outputs on this chip." + )] pub fn set_counter(&mut self, phase: u16, direction: CounterDirection) { - // SAFETY: - // We only write to our TIMERx_SYNC register - let tmr = unsafe { Self::tmr() }; - let sw = tmr.sync().read().sw().bit_is_set(); - tmr.sync().write(|w| { - w.phase_direction().bit(direction as u8 != 0); - unsafe { - w.phase().bits(phase); - } - w.sw().bit(!sw) - }); + self.set_sync_phase(phase); + self.set_sync_counter_direction(direction); + self.trigger_sync(); + } + + /// Trigger a software sync event + #[cfg_attr( + docsrs, + doc(cfg(mcpwm_swsync_can_propagate)), + doc = "**Note:** Software triggered sync events from timers will propagate to their respective sync outputs on this chip." + )] + pub fn trigger_sync(&mut self) { + self.sync().modify(|r, w| w.sw().bit(!r.sw().bit())); } /// Read the counter value and counter direction of the timer pub fn status(&self) -> (u16, CounterDirection) { - // SAFETY: - // We only read from our TIMERx_STATUS register - let reg = unsafe { Self::tmr() }.status().read(); + // SAFETY: Only read TIMERx_STATUS; unique per TIM const + let reg = unsafe { self.tmr() }.status().read(); (reg.value().bits(), reg.direction().bit_is_set().into()) } + /// Sets the timers sync source. Refer to how sync events are + /// handled in the [`Timer`] documentation. + pub fn set_sync_in(&mut self, sync_sel: SyncKind) { + let info = self.mcpwm_info; + + // SAFETY: Only TIMER_SYNCI_CFG and TIMERx_SYNC accessed + info.regs() + .timer_synci_cfg() + .modify(|_r, w| unsafe { w.timer_syncisel(self.number).bits(sync_sel as u8) }); + self.sync() + .modify(|_r, w| w.synci_en().bit(sync_sel != SyncKind::None)); + } + + /// Clears the sync in for the timer and disables sync input + pub fn clear_sync_in(&mut self) { + self.set_sync_in(SyncKind::None); + } + + /// Get the sync out selection for the timer + pub fn get_sync_out(&self) -> SyncKind { + match self.number { + 0 => SyncKind::Timer0Sync, + 1 => SyncKind::Timer1Sync, + 2 => SyncKind::Timer2Sync, + _ => unreachable!(), + } + } + + #[instability::unstable] + pub fn listen(&mut self, events: EnumSet) { + self.enable_listen(events, true); + } + + #[instability::unstable] + pub fn unlisten(&mut self, events: EnumSet) { + self.enable_listen(events, false); + } + + #[instability::unstable] + pub fn interrupts(&self) -> EnumSet { + let mut res = EnumSet::new(); + let info = self.mcpwm_info; + + let ints = info.regs().int_st().read(); + if ints.timer_stop(self.number).bit() { + res.insert(TimerEvent::Stop); + } + if ints.timer_tep(self.number).bit() { + res.insert(TimerEvent::EqualPeriod); + } + if ints.timer_tez(self.number).bit() { + res.insert(TimerEvent::EqualZero); + } + + res + } + + #[instability::unstable] + pub fn clear_interrupts(&mut self, events: EnumSet) { + let info = self.mcpwm_info; + info.regs().int_clr().write(|w| { + for event in events { + match event { + TimerEvent::Stop => w.timer_stop(self.number).bit(true), + TimerEvent::EqualPeriod => w.timer_tep(self.number).bit(true), + TimerEvent::EqualZero => w.timer_tez(self.number).bit(true), + }; + } + w + }); + } + + /// Update period of the timer on the fly. How the new period is applied depends on the + /// [`PeriodUpdatingMethod`] set in the [`TimerClockConfig`]. + #[doc(hidden)] + pub fn update_period(&mut self, new_period: u16) { + self.cfg0() + .modify(|_r, w| unsafe { w.period().bits(new_period) }); + } + + /// Get the timer number + #[doc(hidden)] + pub fn number(&self) -> u8 { + self.number + } + + fn configure(&mut self) { + // write prescaler and period + let (prescaler, period, period_updating_method) = ( + self.config.prescaler, + self.config.period, + self.config.period_updating_method as u8, + ); + self.cfg0().write(|w| unsafe { + w.prescale().bits(prescaler); + w.period().bits(period); + w.period_upmethod().bits(period_updating_method) + }); + + // write sync configure + self.set_sync_counter_direction(self.config.sync_direction); + self.set_sync_out_selection(self.config.sync_out); + self.set_sync_phase(self.config.sync_phase); + } + + fn set_sync_phase(&mut self, sync_phase: u16) { + // SAFETY: Only TIMERx_SYNC accessed; unique per TIM const + self.sync() + .modify(|_r, w| unsafe { w.phase().bits(sync_phase) }); + } + + fn set_sync_counter_direction(&mut self, direction: CounterDirection) { + self.sync() + .modify(|_r, w| w.phase_direction().bit(direction as u8 != 0)); + } + + fn set_sync_out_selection(&mut self, sync_out: SyncOutSelect) { + self.sync() + .modify(|_r, w| unsafe { w.synco_sel().bits(sync_out as u8) }); + } + + fn enable_listen(&mut self, events: EnumSet, value: bool) { + let info = self.mcpwm_info; + let mut int_events = EnumSet::new(); + for timer_event in events { + int_events.insert(timer_event.into()); + } + + info.enable_listen(self.number, int_events, value); + } + fn cfg0(&mut self) -> &pac::mcpwm0::timer::CFG0 { - // SAFETY: - // We only grant access to our CFG0 register with the lifetime of &mut self - unsafe { Self::tmr() }.cfg0() + // SAFETY: Unique register access per TIM + unsafe { self.tmr().cfg0() } } fn cfg1(&mut self) -> &pac::mcpwm0::timer::CFG1 { - // SAFETY: - // We only grant access to our CFG1 register with the lifetime of &mut self - unsafe { Self::tmr() }.cfg1() + // SAFETY: Unique register access per TIM + unsafe { self.tmr().cfg1() } + } + + fn sync(&mut self) -> &pac::mcpwm0::timer::SYNC { + // SAFETY: Unique register access per TIM + unsafe { self.tmr().sync() } } - unsafe fn tmr() -> &'static pac::mcpwm0::TIMER { - let block = unsafe { &*PWM::block() }; - block.timer(TIM as usize) + // Marked unsafe as the caller must ensure that only one timer + // is accessing the registers for a given TIM const + unsafe fn tmr(&self) -> &'d pac::mcpwm0::TIMER { + let info = self.mcpwm_info; + info.regs().timer(self.number as usize) + } +} + +/// Sync out selection for the timer +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub enum SyncOutSelect { + /// Sync out is triggered when a timer receives a sync in + SyncIn = 0, + /// Sync out is triggered when the timer equals zero + WhenEqualZero = 1, + /// Sync out is triggered when the timer equals the period + WhenEqualPeriod = 2, +} + +/// Sync error for an invalid sync configuration +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum ConfigError { + /// Error is thrown when the provided phase is out of range for the timer configuration + InvalidPhaseRange, +} + +impl core::error::Error for ConfigError {} + +impl core::fmt::Display for ConfigError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + ConfigError::InvalidPhaseRange => write!( + f, + "Provided phase is out of range for the current timer configuration" + ), + } } } @@ -118,13 +378,18 @@ impl Timer { /// /// Use [`PeripheralClockConfig::timer_clock_with_prescaler`](super::PeripheralClockConfig::timer_clock_with_prescaler) or /// [`PeripheralClockConfig::timer_clock_with_frequency`](super::PeripheralClockConfig::timer_clock_with_frequency) to it. -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct TimerClockConfig { frequency: Rate, period: u16, period_updating_method: PeriodUpdatingMethod, + stop_condition: StopCondition, prescaler: u8, mode: PwmWorkingMode, + sync_out: SyncOutSelect, + sync_phase: u16, + sync_direction: CounterDirection, } impl TimerClockConfig { @@ -141,12 +406,16 @@ impl TimerClockConfig { }; let frequency = clock.frequency / (prescaler as u32 + 1) / cycle_period; - TimerClockConfig { + Self { frequency, prescaler, period, period_updating_method: PeriodUpdatingMethod::Immediately, + stop_condition: StopCondition::RunContinuously, mode, + sync_phase: 0, + sync_direction: CounterDirection::Increasing, + sync_out: SyncOutSelect::SyncIn, } } @@ -176,15 +445,43 @@ impl TimerClockConfig { } let frequency = clock.frequency / (prescaler + 1) / cycle_period; - Ok(TimerClockConfig { + Ok(Self { frequency, prescaler: prescaler as u8, period, period_updating_method: PeriodUpdatingMethod::Immediately, + stop_condition: StopCondition::RunContinuously, mode, + sync_phase: 0, + sync_direction: CounterDirection::Increasing, + sync_out: SyncOutSelect::SyncIn, }) } + /// Set the sync out selection for the timer. Refer to how sync events are + /// handled in the [`Timer`] documentation. + pub fn with_sync_out(self, sync_out: SyncOutSelect) -> Self { + Self { sync_out, ..self } + } + + /// Set the sync phase for the timer. Refer to how sync events are + /// handled in the [`Timer`] documentation. + pub fn with_phase(self, phase: u16) -> Self { + Self { + sync_phase: phase, + ..self + } + } + + /// Set the sync counter direction for the timer. Refer to how sync events are + /// handled in the [`Timer`] documentation. + pub fn with_direction(self, direction: CounterDirection) -> Self { + Self { + sync_direction: direction, + ..self + } + } + /// Set the method for updating the PWM period pub fn with_period_updating_method(self, method: PeriodUpdatingMethod) -> Self { Self { @@ -193,6 +490,14 @@ impl TimerClockConfig { } } + /// Sets the stop timer conditions + pub fn with_stop_condition(self, condition: StopCondition) -> Self { + Self { + stop_condition: condition, + ..self + } + } + /// Get the timer clock frequency. /// /// ### Note: @@ -200,10 +505,16 @@ impl TimerClockConfig { pub fn frequency(&self) -> Rate { self.frequency } + + /// Default configuration for the timer with the provided clock configuration + pub(super) fn default(clock: &PeripheralClockConfig) -> Self { + Self::with_prescaler(clock, u16::MAX, PwmWorkingMode::Increase, 0) + } } /// Method for updating the PWM period -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(u8)] pub enum PeriodUpdatingMethod { /// The period is updated immediately. @@ -217,8 +528,24 @@ pub enum PeriodUpdatingMethod { TimerEqualsZeroOrSync = 3, } +/// Method for stop conditions for timers +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub enum StopCondition { + /// Defines to start the timer now and run till [`Timer::stop`] is called. + RunContinuously = 2, + /// Defines to start the timer now and run till + /// the counter equals zero + StopAtZero = 3, + /// Defines to start the timer now and run till + /// the counter equals period + StopAtPeriod = 4, +} + /// PWM working mode -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(u8)] pub enum PwmWorkingMode { /// In this mode, the PWM timer increments from zero until reaching the @@ -238,9 +565,19 @@ pub enum PwmWorkingMode { UpDown = 3, } +impl From for CounterDirection { + fn from(bit: bool) -> Self { + match bit { + false => CounterDirection::Increasing, + true => CounterDirection::Decreasing, + } + } +} + /// The direction the timer counter is changing -#[derive(Debug)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(u8)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum CounterDirection { /// The timer counter is increasing Increasing = 0, @@ -248,11 +585,12 @@ pub enum CounterDirection { Decreasing = 1, } -impl From for CounterDirection { - fn from(bit: bool) -> Self { - match bit { - false => CounterDirection::Increasing, - true => CounterDirection::Decreasing, +impl From for Event { + fn from(value: TimerEvent) -> Self { + match value { + TimerEvent::Stop => Event::TimerStop, + TimerEvent::EqualZero => Event::TimerEqualZero, + TimerEvent::EqualPeriod => Event::TimerEqualPeriod, } } } diff --git a/esp-metadata-generated/src/_build_script_utils.rs b/esp-metadata-generated/src/_build_script_utils.rs index 8ee3ca919a4..c66357aea28 100644 --- a/esp-metadata-generated/src/_build_script_utils.rs +++ b/esp-metadata-generated/src/_build_script_utils.rs @@ -310,6 +310,8 @@ impl Chip { "i2c_master_i2c1", "i2s_i2s0", "i2s_i2s1", + "mcpwm_mcpwm0", + "mcpwm_mcpwm1", "spi_master_spi2", "spi_master_spi3", "spi_slave_spi2", @@ -550,6 +552,8 @@ impl Chip { "cargo:rustc-cfg=i2c_master_i2c1", "cargo:rustc-cfg=i2s_i2s0", "cargo:rustc-cfg=i2s_i2s1", + "cargo:rustc-cfg=mcpwm_mcpwm0", + "cargo:rustc-cfg=mcpwm_mcpwm1", "cargo:rustc-cfg=spi_master_spi2", "cargo:rustc-cfg=spi_master_spi3", "cargo:rustc-cfg=spi_slave_spi2", @@ -2834,6 +2838,7 @@ impl Chip { "i2c_master_i2c0", "i2c_slave_i2c0", "i2s_i2s0", + "mcpwm_mcpwm0", "spi_master_spi2", "spi_slave_spi2", "timergroup_timg0", @@ -2919,6 +2924,10 @@ impl Chip { "lp_i2c_master_fifo_size=\"16\"", "lp_io_version=\"v4\"", "lp_uart_ram_size=\"32\"", + "mcpwm_swsync_can_propagate", + "mcpwm_capture_clk_from_group", + "mcpwm_support_etm", + "mcpwm_support_sleep_retention", "mmu_page_size=\"65536\"", "mmu_entry_num=\"256\"", "parl_io_version=\"1\"", @@ -3143,6 +3152,7 @@ impl Chip { "cargo:rustc-cfg=i2c_master_i2c0", "cargo:rustc-cfg=i2c_slave_i2c0", "cargo:rustc-cfg=i2s_i2s0", + "cargo:rustc-cfg=mcpwm_mcpwm0", "cargo:rustc-cfg=spi_master_spi2", "cargo:rustc-cfg=spi_slave_spi2", "cargo:rustc-cfg=timergroup_timg0", @@ -3228,6 +3238,10 @@ impl Chip { "cargo:rustc-cfg=lp_i2c_master_fifo_size=\"16\"", "cargo:rustc-cfg=lp_io_version=\"v4\"", "cargo:rustc-cfg=lp_uart_ram_size=\"32\"", + "cargo:rustc-cfg=mcpwm_swsync_can_propagate", + "cargo:rustc-cfg=mcpwm_capture_clk_from_group", + "cargo:rustc-cfg=mcpwm_support_etm", + "cargo:rustc-cfg=mcpwm_support_sleep_retention", "cargo:rustc-cfg=mmu_page_size=\"65536\"", "cargo:rustc-cfg=mmu_entry_num=\"256\"", "cargo:rustc-cfg=parl_io_version=\"1\"", @@ -4213,6 +4227,7 @@ impl Chip { "i2c_master_i2c0", "i2c_master_i2c1", "i2s_i2s0", + "mcpwm_mcpwm0", "spi_master_spi2", "spi_slave_spi2", "timergroup_timg0", @@ -4300,6 +4315,10 @@ impl Chip { "ledc_version=\"3\"", "ledc_channel_count=\"6\"", "lp_io_version=\"esp32h2\"", + "mcpwm_swsync_can_propagate", + "mcpwm_capture_clk_from_group", + "mcpwm_support_etm", + "mcpwm_support_sleep_retention", "mmu_page_size=\"65536\"", "mmu_entry_num=\"256\"", "parl_io_version=\"2\"", @@ -4493,6 +4512,7 @@ impl Chip { "cargo:rustc-cfg=i2c_master_i2c0", "cargo:rustc-cfg=i2c_master_i2c1", "cargo:rustc-cfg=i2s_i2s0", + "cargo:rustc-cfg=mcpwm_mcpwm0", "cargo:rustc-cfg=spi_master_spi2", "cargo:rustc-cfg=spi_slave_spi2", "cargo:rustc-cfg=timergroup_timg0", @@ -4580,6 +4600,10 @@ impl Chip { "cargo:rustc-cfg=ledc_version=\"3\"", "cargo:rustc-cfg=ledc_channel_count=\"6\"", "cargo:rustc-cfg=lp_io_version=\"esp32h2\"", + "cargo:rustc-cfg=mcpwm_swsync_can_propagate", + "cargo:rustc-cfg=mcpwm_capture_clk_from_group", + "cargo:rustc-cfg=mcpwm_support_etm", + "cargo:rustc-cfg=mcpwm_support_sleep_retention", "cargo:rustc-cfg=mmu_page_size=\"65536\"", "cargo:rustc-cfg=mmu_entry_num=\"256\"", "cargo:rustc-cfg=parl_io_version=\"2\"", @@ -6401,6 +6425,8 @@ impl Chip { "i2c_master_i2c1", "i2s_i2s0", "i2s_i2s1", + "mcpwm_mcpwm0", + "mcpwm_mcpwm1", "spi_master_spi2", "spi_master_spi3", "spi_slave_spi2", @@ -6489,6 +6515,7 @@ impl Chip { "ledc_version=\"2\"", "ledc_channel_count=\"8\"", "lp_io_version=\"v2\"", + "mcpwm_swsync_can_propagate", "mmu_page_size=\"65536\"", "mmu_entry_num=\"384\"", "phy_combo_module", @@ -6701,6 +6728,8 @@ impl Chip { "cargo:rustc-cfg=i2c_master_i2c1", "cargo:rustc-cfg=i2s_i2s0", "cargo:rustc-cfg=i2s_i2s1", + "cargo:rustc-cfg=mcpwm_mcpwm0", + "cargo:rustc-cfg=mcpwm_mcpwm1", "cargo:rustc-cfg=spi_master_spi2", "cargo:rustc-cfg=spi_master_spi3", "cargo:rustc-cfg=spi_slave_spi2", @@ -6789,6 +6818,7 @@ impl Chip { "cargo:rustc-cfg=ledc_version=\"2\"", "cargo:rustc-cfg=ledc_channel_count=\"8\"", "cargo:rustc-cfg=lp_io_version=\"v2\"", + "cargo:rustc-cfg=mcpwm_swsync_can_propagate", "cargo:rustc-cfg=mmu_page_size=\"65536\"", "cargo:rustc-cfg=mmu_entry_num=\"384\"", "cargo:rustc-cfg=phy_combo_module", @@ -7891,6 +7921,8 @@ impl Chip { "i2c_master_i2c1", "i2s_i2s0", "i2s_i2s1", + "mcpwm_mcpwm0", + "mcpwm_mcpwm1", "spi_master_spi2", "spi_master_spi3", "spi_slave_spi2", @@ -8162,6 +8194,10 @@ impl Chip { "etm_driver_supported", "lp_uart_driver_supported", "ulp_riscv_driver_supported", + "mcpwm_swsync_can_propagate", + "mcpwm_capture_clk_from_group", + "mcpwm_support_etm", + "mcpwm_support_sleep_retention", "soc_has_clock_node_soc_root_clk", "soc_has_clock_node_cpu_hs_div", "soc_has_clock_node_cpu_ls_div", diff --git a/esp-metadata-generated/src/_generated_esp32.rs b/esp-metadata-generated/src/_generated_esp32.rs index df10aace5ae..4a5d396abae 100644 --- a/esp-metadata-generated/src/_generated_esp32.rs +++ b/esp-metadata-generated/src/_generated_esp32.rs @@ -256,6 +256,21 @@ macro_rules! property { ("lp_io.has_gpio_matrix") => { false }; + ("mcpwm.swsync_can_propagate") => { + false + }; + ("mcpwm.capture_clk_from_group") => { + false + }; + ("mcpwm.support_etm") => { + false + }; + ("mcpwm.support_sleep_retention") => { + false + }; + ("mcpwm.support_event_comparator") => { + false + }; ("mmu.page_size") => { 65536 }; @@ -4157,6 +4172,31 @@ macro_rules! for_each_uart { wakeup_source = false))); }; } +/// This macro can be used to generate code for each peripheral instance of the MCPWM driver. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($id:literal, $instance:ident, $sys:ident)` +/// +/// Macro fragments: +/// +/// - `$id`: the index of the MCPWM instance +/// - `$instance`: the name of the MCPWM instance +/// - `$sys`: the name of the instance as it is in the `esp_hal::system::Peripheral` enum. +/// +/// Example data: `(0, MCPWM0, Mcpwm0)` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_mcpwm { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_mcpwm { $(($pattern) => $code;)* ($other : tt) => {} + } _for_each_inner_mcpwm!((0, MCPWM0, Mcpwm0)); _for_each_inner_mcpwm!((1, MCPWM1, + Mcpwm1)); _for_each_inner_mcpwm!((all(0, MCPWM0, Mcpwm0), (1, MCPWM1, Mcpwm1))); + }; +} /// This macro can be used to generate code for each peripheral instance of the SPI master driver. /// /// For an explanation on the general syntax, as well as usage of individual/repeated diff --git a/esp-metadata-generated/src/_generated_esp32c5.rs b/esp-metadata-generated/src/_generated_esp32c5.rs index 1dd05a75b93..0652c2f3b7e 100644 --- a/esp-metadata-generated/src/_generated_esp32c5.rs +++ b/esp-metadata-generated/src/_generated_esp32c5.rs @@ -4330,6 +4330,30 @@ macro_rules! for_each_uart { U1TXD, U1CTS, U1RTS, wakeup_source = true))); }; } +/// This macro can be used to generate code for each peripheral instance of the MCPWM driver. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($id:literal, $instance:ident, $sys:ident)` +/// +/// Macro fragments: +/// +/// - `$id`: the index of the MCPWM instance +/// - `$instance`: the name of the MCPWM instance +/// - `$sys`: the name of the instance as it is in the `esp_hal::system::Peripheral` enum. +/// +/// Example data: `(0, MCPWM0, Mcpwm0)` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_mcpwm { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_mcpwm { $(($pattern) => $code;)* ($other : tt) => {} + } _for_each_inner_mcpwm!((all)); + }; +} /// This macro can be used to generate code for each peripheral instance of the SPI master driver. /// /// For an explanation on the general syntax, as well as usage of individual/repeated diff --git a/esp-metadata-generated/src/_generated_esp32c6.rs b/esp-metadata-generated/src/_generated_esp32c6.rs index 4379175fa22..54092d88eae 100644 --- a/esp-metadata-generated/src/_generated_esp32c6.rs +++ b/esp-metadata-generated/src/_generated_esp32c6.rs @@ -301,6 +301,21 @@ macro_rules! property { ("lp_uart.ram_size", str) => { stringify!(32) }; + ("mcpwm.swsync_can_propagate") => { + true + }; + ("mcpwm.capture_clk_from_group") => { + true + }; + ("mcpwm.support_etm") => { + true + }; + ("mcpwm.support_sleep_retention") => { + true + }; + ("mcpwm.support_event_comparator") => { + false + }; ("mmu.page_size") => { 65536 }; @@ -5246,6 +5261,31 @@ macro_rules! for_each_uart { U1TXD, U1CTS, U1RTS, wakeup_source = true))); }; } +/// This macro can be used to generate code for each peripheral instance of the MCPWM driver. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($id:literal, $instance:ident, $sys:ident)` +/// +/// Macro fragments: +/// +/// - `$id`: the index of the MCPWM instance +/// - `$instance`: the name of the MCPWM instance +/// - `$sys`: the name of the instance as it is in the `esp_hal::system::Peripheral` enum. +/// +/// Example data: `(0, MCPWM0, Mcpwm0)` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_mcpwm { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_mcpwm { $(($pattern) => $code;)* ($other : tt) => {} + } _for_each_inner_mcpwm!((0, MCPWM0, Mcpwm0)); _for_each_inner_mcpwm!((all(0, + MCPWM0, Mcpwm0))); + }; +} /// This macro can be used to generate code for each peripheral instance of the SPI master driver. /// /// For an explanation on the general syntax, as well as usage of individual/repeated diff --git a/esp-metadata-generated/src/_generated_esp32h2.rs b/esp-metadata-generated/src/_generated_esp32h2.rs index a1e9d6c810e..eb546c7321b 100644 --- a/esp-metadata-generated/src/_generated_esp32h2.rs +++ b/esp-metadata-generated/src/_generated_esp32h2.rs @@ -289,6 +289,21 @@ macro_rules! property { ("lp_io.has_gpio_matrix") => { false }; + ("mcpwm.swsync_can_propagate") => { + true + }; + ("mcpwm.capture_clk_from_group") => { + true + }; + ("mcpwm.support_etm") => { + true + }; + ("mcpwm.support_sleep_retention") => { + true + }; + ("mcpwm.support_event_comparator") => { + false + }; ("mmu.page_size") => { 65536 }; @@ -4185,6 +4200,31 @@ macro_rules! for_each_uart { U1TXD, U1CTS, U1RTS, wakeup_source = true))); }; } +/// This macro can be used to generate code for each peripheral instance of the MCPWM driver. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($id:literal, $instance:ident, $sys:ident)` +/// +/// Macro fragments: +/// +/// - `$id`: the index of the MCPWM instance +/// - `$instance`: the name of the MCPWM instance +/// - `$sys`: the name of the instance as it is in the `esp_hal::system::Peripheral` enum. +/// +/// Example data: `(0, MCPWM0, Mcpwm0)` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_mcpwm { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_mcpwm { $(($pattern) => $code;)* ($other : tt) => {} + } _for_each_inner_mcpwm!((0, MCPWM0, Mcpwm0)); _for_each_inner_mcpwm!((all(0, + MCPWM0, Mcpwm0))); + }; +} /// This macro can be used to generate code for each peripheral instance of the SPI master driver. /// /// For an explanation on the general syntax, as well as usage of individual/repeated diff --git a/esp-metadata-generated/src/_generated_esp32p4.rs b/esp-metadata-generated/src/_generated_esp32p4.rs index 1281e8c403e..c59e307065a 100644 --- a/esp-metadata-generated/src/_generated_esp32p4.rs +++ b/esp-metadata-generated/src/_generated_esp32p4.rs @@ -4961,6 +4961,30 @@ macro_rules! for_each_uart { wakeup_source = true))); }; } +/// This macro can be used to generate code for each peripheral instance of the MCPWM driver. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($id:literal, $instance:ident, $sys:ident)` +/// +/// Macro fragments: +/// +/// - `$id`: the index of the MCPWM instance +/// - `$instance`: the name of the MCPWM instance +/// - `$sys`: the name of the instance as it is in the `esp_hal::system::Peripheral` enum. +/// +/// Example data: `(0, MCPWM0, Mcpwm0)` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_mcpwm { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_mcpwm { $(($pattern) => $code;)* ($other : tt) => {} + } _for_each_inner_mcpwm!((all)); + }; +} /// This macro can be used to generate code for each peripheral instance of the SPI master driver. /// /// For an explanation on the general syntax, as well as usage of individual/repeated diff --git a/esp-metadata-generated/src/_generated_esp32s3.rs b/esp-metadata-generated/src/_generated_esp32s3.rs index 4257413a05a..e2bcc5b09d1 100644 --- a/esp-metadata-generated/src/_generated_esp32s3.rs +++ b/esp-metadata-generated/src/_generated_esp32s3.rs @@ -271,6 +271,21 @@ macro_rules! property { ("lp_io.has_gpio_matrix") => { false }; + ("mcpwm.swsync_can_propagate") => { + true + }; + ("mcpwm.capture_clk_from_group") => { + false + }; + ("mcpwm.support_etm") => { + false + }; + ("mcpwm.support_sleep_retention") => { + false + }; + ("mcpwm.support_event_comparator") => { + false + }; ("mmu.page_size") => { 65536 }; @@ -4956,6 +4971,31 @@ macro_rules! for_each_uart { wakeup_source = false))); }; } +/// This macro can be used to generate code for each peripheral instance of the MCPWM driver. +/// +/// For an explanation on the general syntax, as well as usage of individual/repeated +/// matchers, refer to [the crate-level documentation][crate#for_each-macros]. +/// +/// This macro has one option for its "Individual matcher" case: +/// +/// Syntax: `($id:literal, $instance:ident, $sys:ident)` +/// +/// Macro fragments: +/// +/// - `$id`: the index of the MCPWM instance +/// - `$instance`: the name of the MCPWM instance +/// - `$sys`: the name of the instance as it is in the `esp_hal::system::Peripheral` enum. +/// +/// Example data: `(0, MCPWM0, Mcpwm0)` +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))] +macro_rules! for_each_mcpwm { + ($($pattern:tt => $code:tt;)*) => { + macro_rules! _for_each_inner_mcpwm { $(($pattern) => $code;)* ($other : tt) => {} + } _for_each_inner_mcpwm!((0, MCPWM0, Mcpwm0)); _for_each_inner_mcpwm!((1, MCPWM1, + Mcpwm1)); _for_each_inner_mcpwm!((all(0, MCPWM0, Mcpwm0), (1, MCPWM1, Mcpwm1))); + }; +} /// This macro can be used to generate code for each peripheral instance of the SPI master driver. /// /// For an explanation on the general syntax, as well as usage of individual/repeated diff --git a/esp-metadata/devices/esp32/soc.toml b/esp-metadata/devices/esp32/soc.toml index f1673f30767..bec0985c855 100644 --- a/esp-metadata/devices/esp32/soc.toml +++ b/esp-metadata/devices/esp32/soc.toml @@ -294,6 +294,13 @@ wakeup_sources = { Bt = 10, } +[device.mcpwm] +support_status = "partial" +instances = [ + { name = "mcpwm0", sys_instance = "Mcpwm0" }, + { name = "mcpwm1", sys_instance = "Mcpwm1" }, +] + # Other drivers which are partially supported but have no other configuration: ## Interfaces @@ -333,7 +340,6 @@ instances = [ [device.ledc] version = 1 channel_count = 8 -[device.mcpwm] [device.pcnt] [device.sdmmc] diff --git a/esp-metadata/devices/esp32c5/soc.toml b/esp-metadata/devices/esp32c5/soc.toml index 27b323ea224..25f008d369a 100644 --- a/esp-metadata/devices/esp32c5/soc.toml +++ b/esp-metadata/devices/esp32c5/soc.toml @@ -390,6 +390,12 @@ support_status = { status = "not_supported", issue = 5161 } [device.mcpwm] support_status = { status = "not_supported", issue = 5154 } +# Although mcpwm is not supported these features are worth noting for future implementation +swsync_can_propagate = true +capture_clk_from_group = true +support_etm = true +support_event_comparator = true +support_sleep_retention = true [device.sleep] support_status = "partial" diff --git a/esp-metadata/devices/esp32c6/soc.toml b/esp-metadata/devices/esp32c6/soc.toml index f790845f51e..d945e01d87c 100644 --- a/esp-metadata/devices/esp32c6/soc.toml +++ b/esp-metadata/devices/esp32c6/soc.toml @@ -372,7 +372,16 @@ instances = [ [device.ledc] version = 3 channel_count = 6 + [device.mcpwm] +support_status = "partial" +swsync_can_propagate = true +capture_clk_from_group = true +support_etm = true +support_sleep_retention = true +instances = [ + { name = "mcpwm0", sys_instance = "Mcpwm0" }, +] [device.pcnt] [device.sd_slave] diff --git a/esp-metadata/devices/esp32h2/soc.toml b/esp-metadata/devices/esp32h2/soc.toml index f0e025a7692..5d2254f686d 100644 --- a/esp-metadata/devices/esp32h2/soc.toml +++ b/esp-metadata/devices/esp32h2/soc.toml @@ -327,6 +327,16 @@ wakeup_sources = { Usb = 14, } +[device.mcpwm] +support_status = "partial" +swsync_can_propagate = true +capture_clk_from_group = true +support_etm = true +support_sleep_retention = true +instances = [ + { name = "mcpwm0", sys_instance = "Mcpwm0" }, +] + # Other drivers which are partially supported but have no other configuration: ## Crypto @@ -359,7 +369,6 @@ instances = [ [device.ledc] version = 3 channel_count = 6 -[device.mcpwm] [device.pcnt] [device.twai] [device.usb_serial_jtag] diff --git a/esp-metadata/devices/esp32s3/soc.toml b/esp-metadata/devices/esp32s3/soc.toml index e9f25f283c8..64f72a367ac 100644 --- a/esp-metadata/devices/esp32s3/soc.toml +++ b/esp-metadata/devices/esp32s3/soc.toml @@ -383,7 +383,15 @@ instances = [ [device.ledc] version = 2 channel_count = 8 + [device.mcpwm] +support_status = "partial" +swsync_can_propagate = true +instances = [ + { name = "mcpwm0", sys_instance = "Mcpwm0" }, + { name = "mcpwm1", sys_instance = "Mcpwm1" }, +] + [device.pcnt] [device.sdmmc] diff --git a/esp-metadata/src/cfg.rs b/esp-metadata/src/cfg.rs index 4e5a76fab32..608c72d40e2 100644 --- a/esp-metadata/src/cfg.rs +++ b/esp-metadata/src/cfg.rs @@ -6,6 +6,7 @@ pub(crate) mod i2c_master; pub(crate) mod i2s; pub(crate) mod interrupt; pub(crate) mod lp_io; +pub(crate) mod mcpwm; pub(crate) mod rmt; pub(crate) mod rsa; pub(crate) mod sdmmc; @@ -25,6 +26,7 @@ pub(crate) use i2c_master::*; pub(crate) use i2s::*; pub(crate) use interrupt::*; pub(crate) use lp_io::*; +pub(crate) use mcpwm::*; pub(crate) use rmt::*; pub(crate) use sdmmc::*; pub(crate) use sha::*; @@ -558,10 +560,21 @@ driver_configs![ ram_size: u32, } }, - McpwmProperties { + McpwmProperties { driver: mcpwm, name: "MCPWM", - properties: {} + properties: { + #[serde(default)] + swsync_can_propagate: bool, + #[serde(default)] + capture_clk_from_group: bool, + #[serde(default)] + support_etm: bool, + #[serde(default)] + support_sleep_retention: bool, + #[serde(default)] + support_event_comparator: bool, + } }, MmuProperties { driver: mmu, diff --git a/esp-metadata/src/cfg/mcpwm.rs b/esp-metadata/src/cfg/mcpwm.rs new file mode 100644 index 00000000000..adf314a01d3 --- /dev/null +++ b/esp-metadata/src/cfg/mcpwm.rs @@ -0,0 +1,57 @@ +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; + +use crate::{cfg::McpwmProperties, generate_for_each_macro}; + +/// Instance configuration, used in [device.mcpwm.instances] +#[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)] +pub(crate) struct McpwmInstanceConfig { + /// The name of the instance in the `esp_hal::system::Peripheral` enum + pub sys_instance: String, +} + +/// Generates `for_each_mcpwm!` which can be used to implement the MCPWM +/// Instance trait for the relevant peripherals. The macro generates code +/// for each [device.mcpwm.instances[X]] instance. +pub(crate) fn generate_mcpwm_peripherals(mcpwm: &McpwmProperties) -> TokenStream { + let mcpwm_instance_cfgs = mcpwm + .instances + .iter() + .enumerate() + .map(|(index, instance)| { + let instance_config = &instance.instance_config; + + let id = crate::number(index); + + let instance = format_ident!("{}", instance.name.to_uppercase()); + let sys = format_ident!("{}", instance_config.sys_instance); + + // The order and meaning of these tokens must match their use in the + // `for_each_mcpwm!` call. + quote! { + #id, #instance, #sys + } + }) + .collect::>(); + + let for_each = generate_for_each_macro("mcpwm", &[("all", &mcpwm_instance_cfgs)]); + quote! { + /// This macro can be used to generate code for each peripheral instance of the MCPWM driver. + /// + /// For an explanation on the general syntax, as well as usage of individual/repeated + /// matchers, refer to [the crate-level documentation][crate#for_each-macros]. + /// + /// This macro has one option for its "Individual matcher" case: + /// + /// Syntax: `($id:literal, $instance:ident, $sys:ident)` + /// + /// Macro fragments: + /// + /// - `$id`: the index of the MCPWM instance + /// - `$instance`: the name of the MCPWM instance + /// - `$sys`: the name of the instance as it is in the `esp_hal::system::Peripheral` enum. + /// + /// Example data: `(0, MCPWM0, Mcpwm0)` + #for_each + } +} diff --git a/esp-metadata/src/lib.rs b/esp-metadata/src/lib.rs index e7af50809a6..c1e88bff84e 100644 --- a/esp-metadata/src/lib.rs +++ b/esp-metadata/src/lib.rs @@ -724,6 +724,9 @@ impl Config { if let Some(peri) = self.device.peri_config.uart.as_ref() { tokens.extend(cfg::generate_uart_peripherals(peri)); } + if let Some(peri) = self.device.peri_config.mcpwm.as_ref() { + tokens.extend(cfg::generate_mcpwm_peripherals(peri)); + } if let Some(peri) = self.device.peri_config.spi_master.as_ref() { tokens.extend(cfg::generate_spi_master_peripherals(peri)); }; diff --git a/hil-test/Cargo.toml b/hil-test/Cargo.toml index c81cd2d88e3..f4cca45401b 100644 --- a/hil-test/Cargo.toml +++ b/hil-test/Cargo.toml @@ -49,6 +49,10 @@ harness = false name = "lcd_cam" harness = false +[[bin]] +name = "mcpwm" +harness = false + [[bin]] name = "misc_drivers" harness = false diff --git a/hil-test/src/bin/mcpwm.rs b/hil-test/src/bin/mcpwm.rs new file mode 100644 index 00000000000..4f8fa7ab910 --- /dev/null +++ b/hil-test/src/bin/mcpwm.rs @@ -0,0 +1,304 @@ +//! MCPWM test + +//% CHIP_FILTER: mcpwm_driver_supported +//% FEATURES: unstable + +#![no_std] +#![no_main] + +use hil_test as _; + +#[cfg(mcpwm_driver_supported)] +#[embedded_test::tests(default_timeout = 3)] +mod mcpwm { + use esp_hal::{ + self, + delay::Delay, + gpio::{AnyPin, Level, Output, OutputConfig, Pin}, + mcpwm::{ + AnyMcPwm, + McPwm, + PeripheralClockConfig, + capture::{CaptureEdge, CaptureMode, CaptureTimerConfig}, + timer::{ConfigError, CounterDirection, PwmWorkingMode, SyncOutSelect}, + }, + time::Rate, + }; + + struct Context<'d> { + mcpwm: McPwm<'static>, + input: AnyPin<'d>, + output: AnyPin<'d>, + delay: Delay, + clock_cfg: PeripheralClockConfig, + } + + #[init] + fn init() -> Context<'static> { + let peripherals = esp_hal::init(esp_hal::Config::default()); + + let (din, dout) = hil_test::common_test_pins!(peripherals); + + let din = din.degrade(); + let dout = dout.degrade(); + + let clock_cfg = PeripheralClockConfig::with_frequency(Rate::from_mhz(1)); + assert!(clock_cfg.is_ok(), "Failed to set MCPWM clock frequency"); + let clock_cfg = clock_cfg.unwrap(); + + Context { + mcpwm: McPwm::new(AnyMcPwm::from(peripherals.MCPWM0), clock_cfg), + input: din, + output: dout, + delay: Delay::new(), + clock_cfg, + } + } + + #[test] + fn test_capture_any_edge(mut ctx: Context<'static>) { + // Setup capture 0 to capture either falling or rising edges + let mut output = Output::new(ctx.output, Level::Low, OutputConfig::default()); + + ctx.mcpwm.capture_timer.start(); + let mut capture = ctx.mcpwm.capture0.with_signal_input(ctx.input); + capture.set_enable(true); + capture.listen(CaptureMode::AnyEdge); + + output.set_high(); + ctx.delay.delay_micros(1); + assert_eq!(CaptureEdge::Rising, capture.events().edge()); + + output.set_low(); + ctx.delay.delay_micros(1); + assert_eq!(CaptureEdge::Falling, capture.events().edge()); + + output.set_high(); + ctx.delay.delay_micros(1); + assert_eq!(CaptureEdge::Rising, capture.events().edge()); + + output.set_low(); + ctx.delay.delay_micros(1); + assert_eq!(CaptureEdge::Falling, capture.events().edge()); + } + + #[test] + fn test_timer_set_counter(ctx: Context<'static>) { + // create timer but don't start it + let mut timer = ctx.mcpwm.timer0; + + timer.set_counter(0, CounterDirection::Increasing); + ctx.delay.delay_micros(1); + assert_eq!((0, CounterDirection::Increasing), timer.status()); + + timer.set_counter(1234, CounterDirection::Increasing); + ctx.delay.delay_micros(1); + assert_eq!((1234, CounterDirection::Increasing), timer.status()); + + timer.set_counter(5553, CounterDirection::Increasing); + ctx.delay.delay_micros(1); + assert_eq!((5553, CounterDirection::Increasing), timer.status()); + } + + #[test] + fn test_timer_sync_phase_from_sync_line(ctx: Context<'static>) { + // setup sync line to be controlled by output pin + let mut output = Output::new(ctx.output, Level::Low, OutputConfig::default()); + ctx.mcpwm.sync0.set_signal(ctx.input); + + let timer_cfg = ctx + .clock_cfg + .timer_clock_with_prescaler(u16::MAX, PwmWorkingMode::Increase, 0) + .with_phase(25000); + + // create timer but don't start it + let mut timer = ctx.mcpwm.timer0; + timer.set_sync_in(ctx.mcpwm.sync0.get_sync_out()); + timer.set_counter(0, CounterDirection::Increasing); + assert!(timer.apply_config(timer_cfg).is_ok()); + + // before sync + assert_eq!(0, timer.status().0); + + // sync the timer + output.set_high(); + ctx.delay.delay_micros(1); + output.set_low(); + ctx.delay.delay_micros(1); + assert_eq!(25000, timer.status().0); + + // apply new sync phase + let timer_cfg = timer_cfg.with_phase(12345); + assert!(timer.apply_config(timer_cfg).is_ok()); + + // sync the timer + output.set_high(); + ctx.delay.delay_micros(1); + output.set_low(); + ctx.delay.delay_micros(1); + assert_eq!(12345, timer.status().0); + } + + #[test] + fn test_cap_timer_sync_phase_from_sync_line(ctx: Context<'static>) { + // setup sync line to be controlled by output pin + let mut output = Output::new(ctx.output, Level::Low, OutputConfig::default()); + ctx.mcpwm.sync0.set_signal(ctx.input); + + let mut cap_timer = ctx.mcpwm.capture_timer; + cap_timer.set_sync_in(ctx.mcpwm.sync0.get_sync_out()); + + let mut capture = ctx.mcpwm.capture0; + capture.set_enable(true); + + // before sync initial phase == 0 + capture.trigger_capture(); + ctx.delay.delay_micros(1); + assert_eq!(0, capture.events().time()); + + cap_timer.apply_config(CaptureTimerConfig::default().with_sync_phase(1234)); + + // sync the timer + output.set_high(); + ctx.delay.delay_micros(1); + output.set_low(); + ctx.delay.delay_micros(1); + + // Compare capture phase + capture.trigger_capture(); + ctx.delay.delay_micros(1); + assert_eq!(1234, capture.events().time()); + + // apply a new sync phase + cap_timer.apply_config(CaptureTimerConfig::default().with_sync_phase(5324)); + + // sync the timer + output.set_high(); + ctx.delay.delay_micros(1); + output.set_low(); + ctx.delay.delay_micros(1); + + // Compare capture phase + capture.trigger_capture(); + ctx.delay.delay_micros(1); + assert_eq!(5324, capture.events().time()); + } + + #[test] + fn test_timer_sync_out_propagating_sync(ctx: Context<'static>) { + // setup sync line to be controlled by output pin + ctx.mcpwm.sync0.set_signal(ctx.input); + + // Small prescaler to ensure sync will fire often ~ 5uS + let timer0_cfg = ctx + .clock_cfg + .timer_clock_with_prescaler(5, PwmWorkingMode::Increase, 0) + .with_sync_out(SyncOutSelect::SyncWhenEqualPeriod); + + // Create timer0 with sync out and start it + let mut timer0 = ctx.mcpwm.timer0; + assert!(timer0.apply_config(timer0_cfg).is_ok()); + timer0.start(); + + let timer1_cfg = ctx + .clock_cfg + .timer_clock_with_prescaler(u16::MAX, PwmWorkingMode::Increase, 0) + .with_phase(25000); + + // timer 1 listens to sync out of timer 0 + let mut timer1 = ctx.mcpwm.timer1; + assert!(timer1.apply_config(timer1_cfg).is_ok()); + + // before sync + assert_eq!(0, timer1.status().0); + + // timer 1 should be synced from timer 0 + timer1.set_sync_in(timer0.get_sync_out()); + ctx.delay.delay_millis(1); + assert_eq!(25000, timer1.status().0); + + let timer1_cfg = timer1_cfg.with_phase(12345); + assert!(timer1.apply_config(timer1_cfg).is_ok()); + + // timer 1 should be synced from timer 0 with new phase + ctx.delay.delay_millis(1); + assert_eq!(12345, timer1.status().0); + } + + #[test] + fn test_timer_apply_config_rejects_invalid_phase(ctx: Context<'static>) { + let mut timer = ctx.mcpwm.timer0; + + let invalid_increase = ctx + .clock_cfg + .timer_clock_with_prescaler(10, PwmWorkingMode::Increase, 0) + .with_phase(12); + assert_eq!( + Err(ConfigError::InvalidPhaseRange), + timer.apply_config(invalid_increase) + ); + + let invalid_updown_increasing = ctx + .clock_cfg + .timer_clock_with_prescaler(10, PwmWorkingMode::UpDown, 0) + .with_phase(11); + assert_eq!( + Err(ConfigError::InvalidPhaseRange), + timer.apply_config(invalid_updown_increasing) + ); + + let invalid_updown_decreasing = ctx + .clock_cfg + .timer_clock_with_prescaler(10, PwmWorkingMode::UpDown, 0) + .with_direction(CounterDirection::Decreasing) + .with_phase(0); + assert_eq!( + Err(ConfigError::InvalidPhaseRange), + timer.apply_config(invalid_updown_decreasing) + ); + } + + #[test] + fn test_sync_line_invert_triggers_on_falling_edge(ctx: Context<'static>) { + // testing sync line invert + let mut output = Output::new(ctx.output, Level::Low, OutputConfig::default()); + ctx.mcpwm.sync0.set_signal(ctx.input); + ctx.mcpwm.sync0.set_invert(true); + + // configure timer with sync phase of 2468 + let timer_cfg = ctx + .clock_cfg + .timer_clock_with_prescaler(u16::MAX, PwmWorkingMode::Increase, 0) + .with_phase(2468); + + // setup timer but not running + let mut timer = ctx.mcpwm.timer1; + timer.set_sync_in(ctx.mcpwm.sync0.get_sync_out()); + timer.set_counter(0, CounterDirection::Increasing); + assert!(timer.apply_config(timer_cfg).is_ok()); + + assert_eq!(0, timer.status().0); + + // sync generated on falling edge + // should stay zero + output.set_high(); + ctx.delay.delay_micros(1); + assert_eq!(0, timer.status().0); + + // should update to sync phase of 2468 + output.set_low(); + ctx.delay.delay_micros(1); + assert_eq!(2468, timer.status().0); + + // Same test just different phase + let timer_cfg = timer_cfg.with_phase(1357); + assert!(timer.apply_config(timer_cfg).is_ok()); + + output.set_high(); + ctx.delay.delay_micros(1); + output.set_low(); + ctx.delay.delay_micros(1); + + assert_eq!(1357, timer.status().0); + } +}