diff --git a/esp-hal/src/ledc/channel.rs b/esp-hal/src/ledc/channel.rs index fff2c411a59..21188dc3019 100644 --- a/esp-hal/src/ledc/channel.rs +++ b/esp-hal/src/ledc/channel.rs @@ -1,58 +1,83 @@ //! # LEDC channel //! //! ## Overview -//! The LEDC Channel module provides a high-level interface to +//! The LEDC Channel module provides a high-level interface to //! configure and control individual PWM channels of the LEDC peripheral. //! //! ## Configuration //! The module allows precise and flexible control over LED lighting and other -//! `Pulse-Width Modulation (PWM)` applications by offering configurable duty +//! Pulse-Width Modulation (PWM) applications by offering configurable duty //! cycles and frequencies. +//! +//! For more information, please refer to the +#![doc = crate::trm_markdown_link!("ledpwm")] -use super::{ - low_level, - timer::{TimerIFace, TimerSpeed}, -}; +use core::{fmt::Display, marker::PhantomData, sync::atomic::Ordering}; + +use super::low_level; use crate::{ - gpio::{ - DriveMode, - OutputConfig, - interconnect::{self, PeripheralOutput}, + DriverMode, + gpio::{PinGuard, interconnect::PeripheralOutput}, + ledc::{ + Speed, + timer::{self, TIMER_FREQS, Timer}, }, - pac::ledc::RegisterBlock, peripherals::LEDC, + system::{Peripheral, PeripheralGuard}, }; -/// Fade parameter sub-errors -#[derive(Debug, Clone, Copy, PartialEq)] +/// Duty fade errors +#[instability::unstable] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum FadeError { - /// Start duty % out of range - StartDuty, - /// End duty % out of range - EndDuty, - /// Duty % change from start to end is out of range + /// Duty change from start to end is out of range DutyRange, /// Duration too long for timer frequency and duty resolution Duration, } -/// Channel errors -#[derive(Debug, Clone, Copy, PartialEq)] +impl Display for FadeError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::DutyRange => write!(f, "Duty change from start to end is out of range"), + Self::Duration => write!( + f, + "Duration too long for timer frequency and duty resolution" + ), + } + } +} + +impl core::error::Error for FadeError {} + +/// Channel configuration errors +#[instability::unstable] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum ConfigError {} + +impl core::fmt::Display for ConfigError { + fn fmt(&self, _f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match *self {} + } +} + +impl core::error::Error for ConfigError {} + +/// Channel configuration +#[instability::unstable] +#[derive(Default, Copy, Clone, Debug, PartialEq, Eq, Hash, procmacros::BuilderLite)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Error { - /// Invalid duty % value - Duty, - /// Timer not configured - Timer, - /// Channel not configured - Channel, - /// Fade parameters invalid - Fade(FadeError), +pub struct Config { + /// The duty value + duty: u32, } /// Channel number -#[derive(PartialEq, Eq, Copy, Clone, Debug)] +#[instability::unstable] +#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Number { /// Channel 0 @@ -75,345 +100,287 @@ pub enum Number { Channel7 = 7, } -/// Channel configuration -pub mod config { - use crate::{ - gpio::DriveMode, - ledc::timer::{TimerIFace, TimerSpeed}, - }; - - /// Channel configuration - #[derive(Copy, Clone)] - pub struct Config<'a, S: TimerSpeed> { - /// A reference to the timer associated with this channel. - pub timer: &'a dyn TimerIFace, - /// The duty cycle percentage (0-100). - pub duty_pct: u8, - /// The pin configuration (PushPull or OpenDrain). - pub drive_mode: DriveMode, +impl Number { + const fn from_u8(n: u8) -> Number { + // until rust adds const enum generics + match n { + 0 => Number::Channel0, + 1 => Number::Channel1, + 2 => Number::Channel2, + 3 => Number::Channel3, + 4 => Number::Channel4, + 5 => Number::Channel5, + #[cfg(ledc_channel_count = "8")] + 6 => Number::Channel6, + #[cfg(ledc_channel_count = "8")] + 7 => Number::Channel7, + _ => core::unreachable!(), // defmt::unreachable!() fails const eval + } } } -/// Channel interface -pub trait ChannelIFace<'a, S: TimerSpeed + 'a> -where - Channel<'a, S>: ChannelHW, -{ - /// Configure channel - fn configure(&mut self, config: config::Config<'a, S>) -> Result<(), Error>; - - /// Set channel duty HW - fn set_duty(&self, duty_pct: u8) -> Result<(), Error>; +/// Channel creator +#[instability::unstable] +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ChannelCreator<'d, const CHANNEL: u8, Dm: DriverMode, S: Speed> { + guard: Option, + _phantom: PhantomData<(&'d (), Dm, S)>, +} - /// Start a duty-cycle fade - fn start_duty_fade( - &self, - start_duty_pct: u8, - end_duty_pct: u8, - duration_ms: u16, - ) -> Result<(), Error>; +impl<'d, const CHANNEL: u8, Dm: DriverMode, S: Speed> ChannelCreator<'d, CHANNEL, Dm, S> { + /// Reborrow this channel creator for a shorter lifetime `'a`. + /// + /// Use this method if you would like to keep working with this channel after you drop the + /// configured one. + #[instability::unstable] + #[inline] + pub fn reborrow(&mut self) -> ChannelCreator<'_, CHANNEL, Dm, S> { + Self { + guard: None, + _phantom: PhantomData, + } + } - /// Check whether a duty-cycle fade is running - fn is_duty_fade_running(&self) -> bool; -} + /// Configures the channel. + #[instability::unstable] + pub fn configure( + self, + timer: &Timer<'d, S>, + config: Config, + ) -> Result, ConfigError> { + let mut channel = Channel { + number: Number::from_u8(CHANNEL), + timer: timer.number(), + pin: PinGuard::new_unconnected(), + _guard: self.guard, + _phantom: PhantomData, + } + .with_timer(timer); + channel.apply_config(&config)?; -/// Channel HW interface -pub trait ChannelHW { - /// Configure Channel HW except for the duty which is set via - /// [`Self::set_duty_hw`]. - fn configure_hw(&mut self) -> Result<(), Error>; - /// Configure the hardware for the channel with a specific pin - /// configuration. - fn configure_hw_with_drive_mode(&mut self, cfg: DriveMode) -> Result<(), Error>; + Ok(channel) + } - /// Set channel duty HW - fn set_duty_hw(&self, duty: u32); + /// Unsafely steal a channel creator instance. + /// + /// # Safety + /// + /// The caller must ensure that only one instance of a channel is in use at one time. + #[instability::unstable] + #[inline] + pub unsafe fn steal() -> Self { + Self { + guard: Some(PeripheralGuard::new(Peripheral::Ledc)), + _phantom: PhantomData, + } + } - /// Start a duty-cycle fade HW - fn start_duty_fade_hw( - &self, - start_duty: u32, - duty_inc: bool, - duty_steps: u16, - cycles_per_step: u16, - duty_per_cycle: u16, - ); - - /// Check whether a duty-cycle fade is running HW - fn is_duty_fade_running_hw(&self) -> bool; + /// Unsafely clone a channel creator instance. + /// + /// # Safety + /// + /// The caller must ensure that only one instance of a channel is in use at one time. + #[instability::unstable] + #[inline] + pub unsafe fn clone_unchecked(&self) -> Self { + unsafe { Self::steal() } + } } /// Channel struct -pub struct Channel<'a, S: TimerSpeed> { - ledc: &'a RegisterBlock, - timer: Option<&'a dyn TimerIFace>, +#[instability::unstable] +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Channel<'d, Dm: DriverMode, S: Speed> { number: Number, - output_pin: interconnect::OutputSignal<'a>, + timer: timer::Number, + pin: PinGuard, + _guard: Option, + _phantom: PhantomData<(&'d (), Dm, S)>, } -impl<'a, S: TimerSpeed> Channel<'a, S> { - /// Return a new channel - pub fn new(number: Number, output_pin: impl PeripheralOutput<'a>) -> Self { +impl<'d, Dm: DriverMode, S: Speed> Channel<'d, Dm, S> { + #[procmacros::doc_replace] + /// Attaches a new timer to the channel and returns the updated channel. + /// + /// ## Example + /// + /// ```rust, no_run + /// # {before_snippet} + /// # use esp_hal::ledc::{self, Ledc}; + /// # use esp_hal::time::Rate; + /// # let mut ledc = Ledc::new(peripherals.LEDC, Default::default())?; + /// # let timer0 = ledc.timer0.configure(ledc::timer::Config::default().with_frequency(Rate::from_khz(24)))?; + /// # let timer1 = ledc.timer1.configure(ledc::timer::Config::default().with_frequency(Rate::from_khz(12)))?; + /// let channel0 = ledc.channel0.configure(&timer0, ledc::channel::Config::default())?; + /// // timer0 is borrowed + /// + /// let channel0 = channel0.with_timer(&timer1); + /// // timer0 is no longer borrowed, but timer1 is + /// # {after_snippet} + /// ``` + #[instability::unstable] + pub fn with_timer(mut self, new_timer: &Timer<'d, S>) -> Self { let ledc = LEDC::regs(); - Channel { - ledc, - timer: None, - number, - output_pin: output_pin.into(), - } - } -} + low_level::set_channel(ledc, self.number, new_timer.number() as u8, S::IS_HS); -impl<'a, S: TimerSpeed> ChannelIFace<'a, S> for Channel<'a, S> -where - Channel<'a, S>: ChannelHW, -{ - /// Configure channel - fn configure(&mut self, config: config::Config<'a, S>) -> Result<(), Error> { - self.timer = Some(config.timer); + self.timer = new_timer.number(); + self + } - self.set_duty(config.duty_pct)?; - self.configure_hw_with_drive_mode(config.drive_mode)?; + /// Attaches a new PeripheralOutput to the channel. + #[instability::unstable] + pub fn with_pin(mut self, pin: impl PeripheralOutput<'d>) -> Self { + let output_signal = low_level::output_signal(self.number, S::IS_HS); + let pin_out = pin.into(); + pin_out.apply_output_config(&Default::default()); + pin_out.set_output_enable(true); + self.pin = pin_out.connect_with_guard(output_signal); + self + } + /// Changes the configuration. + #[instability::unstable] + pub fn apply_config(&mut self, config: &Config) -> Result<(), ConfigError> { + self.set_duty_cycle(config.duty); Ok(()) } - /// Set duty % of channel - fn set_duty(&self, duty_pct: u8) -> Result<(), Error> { - let duty_exp; - if let Some(timer) = self.timer { - if let Some(timer_duty) = timer.duty() { - duty_exp = timer_duty as u32; - } else { - return Err(Error::Timer); - } - } else { - return Err(Error::Channel); - } - - let duty_range = 2u32.pow(duty_exp); - let duty_value = (duty_range * duty_pct as u32) / 100; - - if duty_pct > 100u8 { - // duty_pct greater than 100% - return Err(Error::Duty); - } + /// Returns the maximum duty cycle. + #[instability::unstable] + pub fn max_duty_cycle(&self) -> u32 { + let ledc = LEDC::regs(); + let duty_res = low_level::get_duty_res(ledc, self.timer, S::IS_HS); + 1u32 << duty_res + } - self.set_duty_hw(duty_value); + /// Sets the duty cycle of the channel. + /// + /// The duty cycle value will be clamped to the maximum duty cycle configured for the [`Timer`] + #[instability::unstable] + pub fn set_duty_cycle(&self, duty: u32) { + let max_duty = self.max_duty_cycle(); + let duty_value = duty.min(max_duty); - Ok(()) + let ledc = LEDC::regs(); + low_level::set_duty_hw(ledc, self.number, S::IS_HS, duty_value); + low_level::start_duty_without_fading(ledc, self.number, S::IS_HS); + low_level::update_channel(ledc, self.number, S::IS_HS); } - /// Start a duty fade from one % to another. + #[procmacros::doc_replace] + /// Starts a duty fade from one duty value to another. /// /// There's a constraint on the combination of timer frequency, timer PWM /// duty resolution (the bit count), the fade "range" (abs(start-end)), and /// the duration: /// - /// frequency * duration / ((1< Result<(), Error> { - let duty_exp; - let frequency; - if start_duty_pct > 100u8 { - return Err(Error::Fade(FadeError::StartDuty)); - } - if end_duty_pct > 100u8 { - return Err(Error::Fade(FadeError::EndDuty)); - } - if let Some(timer) = self.timer { - if let Some(timer_duty) = timer.duty() { - if timer.frequency() > 0 { - duty_exp = timer_duty as u32; - frequency = timer.frequency(); - } else { - return Err(Error::Timer); - } - } else { - return Err(Error::Timer); - } - } else { - return Err(Error::Channel); - } + ) -> Result<(), FadeError> { + let max_duty = self.max_duty_cycle(); + let start_duty_value = start_duty.min(max_duty); + let end_duty_value = end_duty.min(max_duty); + + let timer_index = if S::IS_HS { 4 } else { 0 } + self.timer as usize; + let frequency = TIMER_FREQS[timer_index].load(Ordering::Acquire); + let pwm_cycles = duration_ms as u32 * frequency / 1000; + let abs_duty_diff = end_duty_value.abs_diff(start_duty_value); + let duty_steps: u32 = u16::try_from(abs_duty_diff).unwrap_or(65535).into(); + let duty_steps = duty_steps.min(pwm_cycles).max(1); - let duty_range = (1u32 << duty_exp) - 1; - let start_duty_value = (duty_range * start_duty_pct as u32) / 100; - let end_duty_value = (duty_range * end_duty_pct as u32) / 100; + let cycles_per_step = + u16::try_from(pwm_cycles / duty_steps).map_err(|_| FadeError::Duration)?; + if cycles_per_step > 1023 { + return Err(FadeError::Duration); + } - // NB: since we do the multiplication first here, there's no loss of - // precision from using milliseconds instead of (e.g.) nanoseconds. - let pwm_cycles = (duration_ms as u32) * frequency / 1000; + let duty_per_cycle = + u16::try_from(abs_duty_diff / duty_steps).map_err(|_| FadeError::DutyRange)?; - let abs_duty_diff = end_duty_value.abs_diff(start_duty_value); - let duty_steps: u32 = u16::try_from(abs_duty_diff).unwrap_or(65535).into(); - // This conversion may fail if duration_ms is too big, and if either - // duty_steps gets truncated, or the fade is over a short range of duty - // percentages, so it's too small. Returning an Err in either case is - // fine: shortening the duration_ms will sort things out. - let cycles_per_step: u16 = (pwm_cycles / duty_steps) - .try_into() - .map_err(|_| Error::Fade(FadeError::Duration)) - .and_then(|res| { - if res > 1023 { - Err(Error::Fade(FadeError::Duration)) - } else { - Ok(res) - } - })?; - // This can't fail unless abs_duty_diff is bigger than 65536*65535-1, - // and so duty_steps gets truncated. But that requires duty_exp to be - // at least 32, and the hardware only supports up to 20. Still, handle - // it in case something changes in the future. - let duty_per_cycle: u16 = (abs_duty_diff / duty_steps) - .try_into() - .map_err(|_| Error::Fade(FadeError::DutyRange))?; - - self.start_duty_fade_hw( + let ledc = LEDC::regs(); + low_level::start_duty_fade_hw( + ledc, + self.number, + S::IS_HS, start_duty_value, end_duty_value > start_duty_value, - duty_steps.try_into().unwrap(), + duty_steps as u16, cycles_per_step, duty_per_cycle, ); + low_level::update_channel(ledc, self.number, S::IS_HS); Ok(()) } - fn is_duty_fade_running(&self) -> bool { - self.is_duty_fade_running_hw() + /// Returns true if a duty-cycle fade is running. + #[instability::unstable] + pub fn is_duty_fade_running(&self) -> bool { + let ledc = LEDC::regs(); + low_level::is_duty_fade_running_hw(ledc, self.number, S::IS_HS) } } mod ehal1 { - use embedded_hal::pwm::{self, ErrorKind, ErrorType, SetDutyCycle}; + use embedded_hal::pwm::{ErrorType, SetDutyCycle}; - use super::{Channel, ChannelHW, Error}; - use crate::ledc::timer::TimerSpeed; + use super::Channel; + use crate::{DriverMode, ledc::Speed}; - impl pwm::Error for Error { - fn kind(&self) -> pwm::ErrorKind { - ErrorKind::Other - } - } - - impl ErrorType for Channel<'_, S> { - type Error = Error; + impl ErrorType for Channel<'_, Dm, S> { + type Error = core::convert::Infallible; } - impl<'a, S: TimerSpeed> SetDutyCycle for Channel<'a, S> - where - Channel<'a, S>: ChannelHW, - { + impl SetDutyCycle for Channel<'_, Dm, S> { fn max_duty_cycle(&self) -> u16 { - let duty_exp; - - if let Some(timer_duty) = self.timer.and_then(|timer| timer.duty()) { - duty_exp = timer_duty as u32; + let max_hw = Self::max_duty_cycle(self); // inherent method + if max_hw > u16::MAX as u32 { + u16::MAX } else { - return 0; + max_hw as u16 } - - let duty_range = 2u32.pow(duty_exp); - - duty_range as u16 } - fn set_duty_cycle(&mut self, mut duty: u16) -> Result<(), Self::Error> { - let max = self.max_duty_cycle(); - duty = if duty > max { max } else { duty }; - self.set_duty_hw(duty.into()); + fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> { + let max_duty = Self::max_duty_cycle(self); // inherent method + let duty_to_set = if max_duty > u16::MAX as u32 { + // Scale the u16 duty fraction to the hardware's internal u32 resolution + ((duty as u64 * max_duty as u64) / u16::MAX as u64) as u32 + } else { + duty as u32 + }; + Self::set_duty_cycle(self, duty_to_set); // inherent method Ok(()) } } } - -impl Channel<'_, S> { - fn set_channel(&mut self, timer_number: u8) { - low_level::set_channel(self.ledc, self.number, timer_number, S::IS_HS); - low_level::start_duty_without_fading(self.ledc, self.number, S::IS_HS); - } - - fn start_duty_without_fading(&self) { - low_level::start_duty_without_fading(self.ledc, self.number, S::IS_HS); - } - - fn update_channel(&self) { - low_level::update_channel(self.ledc, self.number, S::IS_HS); - } -} - -impl ChannelHW for Channel<'_, S> -where - S: crate::ledc::timer::TimerSpeed, -{ - /// Configure Channel HW - fn configure_hw(&mut self) -> Result<(), Error> { - self.configure_hw_with_drive_mode(DriveMode::PushPull) - } - - fn configure_hw_with_drive_mode(&mut self, cfg: DriveMode) -> Result<(), Error> { - if let Some(timer) = self.timer { - if !timer.is_configured() { - return Err(Error::Timer); - } - - self.output_pin - .apply_output_config(&OutputConfig::default().with_drive_mode(cfg)); - self.output_pin.set_output_enable(true); - - let timer_number = timer.number() as u8; - - self.set_channel(timer_number); - self.update_channel(); - - let signal = low_level::output_signal(self.number, S::IS_HS); - signal.connect_to(&self.output_pin); - } else { - return Err(Error::Timer); - } - - Ok(()) - } - - /// Set duty in channel HW - fn set_duty_hw(&self, duty: u32) { - low_level::set_duty_hw(self.ledc, self.number, S::IS_HS, duty); - self.start_duty_without_fading(); - self.update_channel(); - } - - /// Start a duty-cycle fade HW - fn start_duty_fade_hw( - &self, - start_duty: u32, - duty_inc: bool, - duty_steps: u16, - cycles_per_step: u16, - duty_per_cycle: u16, - ) { - low_level::start_duty_fade_hw( - self.ledc, - self.number, - S::IS_HS, - start_duty, - duty_inc, - duty_steps, - cycles_per_step, - duty_per_cycle, - ); - self.update_channel(); - } - - fn is_duty_fade_running_hw(&self) -> bool { - low_level::is_duty_fade_running_hw(self.ledc, self.number, S::IS_HS) - } -} diff --git a/esp-hal/src/ledc/low_level/mod.rs b/esp-hal/src/ledc/low_level/mod.rs index 07f8e5a6caa..a3f8e538a0a 100644 --- a/esp-hal/src/ledc/low_level/mod.rs +++ b/esp-hal/src/ledc/low_level/mod.rs @@ -3,22 +3,25 @@ #[cfg_attr(ledc_version = "3", path = "v3.rs")] mod version; -#[cfg(ledc_version = "1")] -use super::timer::HSClockSource; use super::{ - LSGlobalClkSource, + LowSpeedGlobalClockSource, channel::Number as ChannelNumber, - timer::{LSClockSource, Number as TimerNumber}, + timer::Number as TimerNumber, }; -use crate::{gpio::OutputSignal, pac::ledc::RegisterBlock, time::Rate}; +use crate::{gpio::OutputSignal, ledc::timer::ClockSource, pac::ledc::RegisterBlock, time::Rate}; #[inline(always)] -pub(super) fn set_global_slow_clock(ledc: &RegisterBlock, clock_source: LSGlobalClkSource) { +pub(super) fn set_global_slow_clock(ledc: &RegisterBlock, clock_source: LowSpeedGlobalClockSource) { version::set_global_slow_clock(ledc, clock_source) } #[inline(always)] -pub(super) fn ls_freq_hw(clock_source: LSClockSource) -> Rate { +pub(super) fn get_duty_res(ledc: &RegisterBlock, number: TimerNumber, is_hs: bool) -> u8 { + version::get_duty_res(ledc, number, is_hs) +} + +#[inline(always)] +pub(super) fn ls_freq_hw(clock_source: ClockSource) -> Rate { version::ls_freq_hw(clock_source) } @@ -40,7 +43,7 @@ pub(super) fn ls_update_hw(ledc: &RegisterBlock, number: TimerNumber) { #[cfg(ledc_version = "1")] #[inline(always)] -pub(super) fn hs_freq_hw(clock_source: HSClockSource) -> Rate { +pub(super) fn hs_freq_hw(clock_source: ClockSource) -> Rate { version::hs_freq_hw(clock_source) } @@ -51,7 +54,7 @@ pub(super) fn hs_configure_hw( number: TimerNumber, divisor: u32, duty: u8, - clock_source: HSClockSource, + clock_source: ClockSource, ) { version::hs_configure_hw(ledc, number, divisor, duty, clock_source) } diff --git a/esp-hal/src/ledc/low_level/v1.rs b/esp-hal/src/ledc/low_level/v1.rs index 1e5f6b1f370..8717ce7cac7 100644 --- a/esp-hal/src/ledc/low_level/v1.rs +++ b/esp-hal/src/ledc/low_level/v1.rs @@ -1,20 +1,42 @@ use super::super::{ - LSGlobalClkSource, + LowSpeedGlobalClockSource, channel::Number as ChannelNumber, - timer::{HSClockSource, LSClockSource, Number as TimerNumber}, + timer::Number as TimerNumber, +}; +use crate::{ + gpio::OutputSignal, + ledc::timer::ClockSource, + pac::ledc::RegisterBlock, + soc::clocks, + time::Rate, }; -use crate::{gpio::OutputSignal, pac::ledc::RegisterBlock, soc::clocks, time::Rate}; -pub(super) fn set_global_slow_clock(ledc: &RegisterBlock, clock_source: LSGlobalClkSource) { +pub(super) fn set_global_slow_clock(ledc: &RegisterBlock, clock_source: LowSpeedGlobalClockSource) { match clock_source { - LSGlobalClkSource::APBClk => { + LowSpeedGlobalClockSource::APBClock => { ledc.conf().write(|w| w.apb_clk_sel().set_bit()); } } ledc.lstimer(0).conf().modify(|_, w| w.para_up().set_bit()); } -pub(super) fn ls_freq_hw(_clock_source: LSClockSource) -> Rate { +pub(super) fn get_duty_res(ledc: &RegisterBlock, number: TimerNumber, is_hs: bool) -> u8 { + if is_hs { + ledc.hstimer(number as usize) + .conf() + .read() + .duty_res() + .bits() + } else { + ledc.lstimer(number as usize) + .conf() + .read() + .duty_res() + .bits() + } +} + +pub(super) fn ls_freq_hw(_clock_source: ClockSource) -> Rate { Rate::from_hz(clocks::apb_clk_frequency()) } @@ -41,7 +63,7 @@ pub(super) fn ls_update_hw(ledc: &RegisterBlock, number: TimerNumber) { .modify(|_, w| w.para_up().set_bit()); } -pub(super) fn hs_freq_hw(_clock_source: HSClockSource) -> Rate { +pub(super) fn hs_freq_hw(_clock_source: ClockSource) -> Rate { Rate::from_hz(clocks::apb_clk_frequency()) } @@ -50,9 +72,9 @@ pub(super) fn hs_configure_hw( number: TimerNumber, divisor: u32, duty: u8, - clock_source: HSClockSource, + clock_source: ClockSource, ) { - let sel_hstimer = clock_source == HSClockSource::APBClk; + let sel_hstimer = clock_source == ClockSource::APBClock; ledc.hstimer(number as usize).conf().modify(|_, w| unsafe { w.tick_sel().bit(sel_hstimer); w.rst().clear_bit(); diff --git a/esp-hal/src/ledc/low_level/v2.rs b/esp-hal/src/ledc/low_level/v2.rs index 61e7bbe7de7..f23f147b399 100644 --- a/esp-hal/src/ledc/low_level/v2.rs +++ b/esp-hal/src/ledc/low_level/v2.rs @@ -1,20 +1,30 @@ use super::super::{ - LSGlobalClkSource, + LowSpeedGlobalClockSource, channel::Number as ChannelNumber, - timer::{LSClockSource, Number as TimerNumber}, + timer::Number as TimerNumber, +}; +use crate::{ + gpio::OutputSignal, + ledc::timer::ClockSource, + pac::ledc::RegisterBlock, + soc::clocks, + time::Rate, }; -use crate::{gpio::OutputSignal, pac::ledc::RegisterBlock, soc::clocks, time::Rate}; -pub(super) fn set_global_slow_clock(ledc: &RegisterBlock, clock_source: LSGlobalClkSource) { +pub(super) fn set_global_slow_clock(ledc: &RegisterBlock, clock_source: LowSpeedGlobalClockSource) { match clock_source { - LSGlobalClkSource::APBClk => { + LowSpeedGlobalClockSource::APBClock => { ledc.conf().write(|w| unsafe { w.apb_clk_sel().bits(1) }); } } ledc.timer(0).conf().modify(|_, w| w.para_up().set_bit()); } -pub(super) fn ls_freq_hw(_clock_source: LSClockSource) -> Rate { +pub(super) fn get_duty_res(ledc: &RegisterBlock, number: TimerNumber, _is_hs: bool) -> u8 { + ledc.timer(number as usize).conf().read().duty_res().bits() +} + +pub(super) fn ls_freq_hw(_clock_source: ClockSource) -> Rate { Rate::from_hz(clocks::apb_clk_frequency()) } @@ -26,10 +36,11 @@ pub(super) fn ls_configure_hw( use_ref_tick: bool, ) { ledc.timer(number as usize).conf().modify(|_, w| unsafe { - #[cfg(soc_has_clock_node_ref_tick)] - w.tick_sel().bit(use_ref_tick); - #[cfg(not(soc_has_clock_node_ref_tick))] - let _ = use_ref_tick; + if cfg!(soc_has_clock_node_ref_tick) { + w.tick_sel().bit(use_ref_tick); + } else { + let _ = use_ref_tick; + } w.rst().clear_bit(); w.pause().clear_bit(); w.clk_div().bits(divisor); diff --git a/esp-hal/src/ledc/low_level/v3.rs b/esp-hal/src/ledc/low_level/v3.rs index 50917742858..7da11ce978f 100644 --- a/esp-hal/src/ledc/low_level/v3.rs +++ b/esp-hal/src/ledc/low_level/v3.rs @@ -1,27 +1,34 @@ use super::super::{ - LSGlobalClkSource, + LowSpeedGlobalClockSource, channel::Number as ChannelNumber, - timer::{LSClockSource, Number as TimerNumber}, + timer::Number as TimerNumber, +}; +use crate::{ + gpio::OutputSignal, + ledc::timer::ClockSource, + pac::ledc::RegisterBlock, + soc::clocks, + time::Rate, }; -use crate::{gpio::OutputSignal, pac::ledc::RegisterBlock, soc::clocks, time::Rate}; -pub(super) fn set_global_slow_clock(ledc: &RegisterBlock, clock_source: LSGlobalClkSource) { +pub(super) fn set_global_slow_clock(ledc: &RegisterBlock, clock_source: LowSpeedGlobalClockSource) { let pcr = unsafe { &*crate::peripherals::PCR::ptr() }; pcr.ledc_sclk_conf().write(|w| w.ledc_sclk_en().set_bit()); match clock_source { - LSGlobalClkSource::APBClk => { - #[cfg(esp32c6)] - pcr.ledc_sclk_conf() - .write(|w| unsafe { w.ledc_sclk_sel().bits(1) }); - #[cfg(esp32h2)] + LowSpeedGlobalClockSource::APBClock => { + let sel = if cfg!(esp32c6) { 1 } else { 0 }; pcr.ledc_sclk_conf() - .write(|w| unsafe { w.ledc_sclk_sel().bits(0) }); + .write(|w| unsafe { w.ledc_sclk_sel().bits(sel) }); } } ledc.timer(0).conf().modify(|_, w| w.para_up().set_bit()); } -pub(super) fn ls_freq_hw(_clock_source: LSClockSource) -> Rate { +pub(super) fn get_duty_res(ledc: &RegisterBlock, number: TimerNumber, _is_hs: bool) -> u8 { + ledc.timer(number as usize).conf().read().duty_res().bits() +} + +pub(super) fn ls_freq_hw(_clock_source: ClockSource) -> Rate { Rate::from_hz(clocks::apb_clk_frequency()) } diff --git a/esp-hal/src/ledc/mod.rs b/esp-hal/src/ledc/mod.rs index d3b2c34d91c..919d2ee923b 100644 --- a/esp-hal/src/ledc/mod.rs +++ b/esp-hal/src/ledc/mod.rs @@ -11,71 +11,73 @@ //! The PWM controller can automatically increase or decrease the duty cycle //! gradually, allowing for fades without any processor interference. //! +//! For more information, please refer to the +#![doc = crate::trm_markdown_link!("ledpwm")] //! ## Configuration //! Currently only supports fixed-frequency output. High Speed channels are //! available for the ESP32 only, while Low Speed channels are available for all //! supported chips. //! +//! ## Usage +//! +//! The LEDC driver implements the `SetDutyCycle` trait from `embedded-hal` for +//! the `Channel`s. +//! //! ## Examples //! //! ### Low Speed Channel //! //! The following example will configure the Low Speed Channel0 to 24kHz output -//! with 10% duty using the ABPClock and turn on LED with the option to change -//! LED intensity depending on `duty` value. Possible values (`u32`) are in -//! range 0..100. +//! using the APB clock and turn on an LED, then initiate a hardware-controlled +//! fade effect. //! //! ```rust, no_run //! # {before_snippet} -//! # use esp_hal::ledc::Ledc; -//! # use esp_hal::ledc::LSGlobalClkSource; -//! # use esp_hal::ledc::timer::{self, TimerIFace}; -//! # use esp_hal::ledc::LowSpeed; -//! # use esp_hal::ledc::channel::{self, ChannelIFace}; -//! # use esp_hal::gpio::DriveMode; -//! # let led = peripherals.GPIO0; -//! -//! let mut ledc = Ledc::new(peripherals.LEDC); -//! ledc.set_global_slow_clock(LSGlobalClkSource::APBClk); -//! -//! let mut lstimer0 = ledc.timer::(timer::Number::Timer0); -//! lstimer0.configure(timer::config::Config { -//! duty: timer::config::Duty::Duty5Bit, -//! clock_source: timer::LSClockSource::APBClk, -//! frequency: Rate::from_khz(24), -//! })?; -//! -//! let mut channel0 = ledc.channel(channel::Number::Channel0, led); -//! channel0.configure(channel::config::Config { -//! timer: &lstimer0, -//! duty_pct: 10, -//! drive_mode: DriveMode::PushPull, -//! })?; +//! # use esp_hal::ledc::{self, Ledc}; +//! # use esp_hal::time::Rate; +//! +//! // Create a new Ledc driver and initialize the global slow clock. +//! let mut ledc = Ledc::new(peripherals.LEDC, ledc::Config::default())?; +//! +//! // Initialize a new timer. +//! let timer0 = ledc +//! .timer0 +//! .configure(ledc::timer::Config::default().with_frequency(Rate::from_khz(24)))?; +//! +//! // Initialize a new channel connected to the timer. +//! // The initial duty cycle is set to 0 (fully off). +//! let mut channel0 = ledc +//! .channel0 +//! .configure(&timer0, ledc::channel::Config::default())? +//! .with_pin(peripherals.GPIO0); +//! +//! // Get the maximum duty cycle for the configured timer resolution. +//! let max_duty = channel0.max_duty_cycle(); //! //! loop { //! // Set up a breathing LED: fade from off to on over a second, then -//! // from on back off over the next second. Then loop. -//! channel0.start_duty_fade(0, 100, 1000)?; +//! // from on back off over the next second. Then loop. +//! channel0.start_duty_fade(0, max_duty, 1000)?; //! while channel0.is_duty_fade_running() {} -//! channel0.start_duty_fade(100, 0, 1000)?; +//! channel0.start_duty_fade(max_duty, 0, 1000)?; //! while channel0.is_duty_fade_running() {} //! } -//! # } +//! # {after_snippet} //! ``` //! //! ## Implementation State //! - Source clock selection is not supported //! - Interrupts are not supported -use self::{ - channel::Channel, - timer::{Timer, TimerSpeed}, -}; +use core::marker::PhantomData; + use crate::{ - gpio::interconnect::PeripheralOutput, - pac, + Blocking, + DriverMode, + ledc::{channel::ChannelCreator, timer::TimerCreator}, peripherals::LEDC, - system::{Peripheral as PeripheralEnable, PeripheralClockControl}, + private::Sealed, + system::{Peripheral, PeripheralGuard}, }; pub mod channel; @@ -83,33 +85,70 @@ mod low_level; pub mod timer; /// Global slow clock source -#[derive(PartialEq, Eq, Copy, Clone, Debug)] -pub enum LSGlobalClkSource { - /// APB clock. - APBClk, +#[instability::unstable] +#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum LowSpeedGlobalClockSource { + /// APB clock + APBClock, } -/// LEDC (LED PWM Controller) -pub struct Ledc<'d> { - _instance: LEDC<'d>, - ledc: &'d pac::ledc::RegisterBlock, +/// Ledc configuration errors +#[instability::unstable] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ConfigError {} + +impl core::fmt::Display for ConfigError { + fn fmt(&self, _f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match *self {} + } +} + +impl core::error::Error for ConfigError {} + +/// Ledc configuration +#[instability::unstable] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, procmacros::BuilderLite)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Config { + /// Global slow clock source + clock_source: LowSpeedGlobalClockSource, +} + +impl Default for Config { + fn default() -> Self { + Self { + clock_source: LowSpeedGlobalClockSource::APBClock, + } + } } #[cfg(ledc_version = "1")] -#[derive(Clone, Copy)] /// Used to specify HighSpeed Timer/Channel -pub struct HighSpeed {} +#[instability::unstable] +#[derive(PartialEq, Eq, Clone, Copy, Debug, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct HighSpeed; -#[derive(Clone, Copy)] /// Used to specify LowSpeed Timer/Channel -pub struct LowSpeed {} +#[instability::unstable] +#[derive(PartialEq, Eq, Clone, Copy, Debug, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct LowSpeed; -/// Trait representing the speed mode of a clock or peripheral. -pub trait Speed { - /// Boolean constant indicating whether the speed is high-speed. +/// Trait representing the speed mode of a clock or peripheral +#[instability::unstable] +pub trait Speed: Sealed { + /// Boolean constant indicating whether the speed is high-speed const IS_HS: bool; } +#[cfg(ledc_version = "1")] +impl Sealed for HighSpeed {} + +impl Sealed for LowSpeed {} + #[cfg(ledc_version = "1")] impl Speed for HighSpeed { const IS_HS: bool = true; @@ -119,37 +158,145 @@ impl Speed for LowSpeed { const IS_HS: bool = false; } -impl<'d> Ledc<'d> { - /// Return a new LEDC - pub fn new(_instance: LEDC<'d>) -> Self { - if PeripheralClockControl::enable(PeripheralEnable::Ledc) { - PeripheralClockControl::reset(PeripheralEnable::Ledc); - } else { - // Refcount was more than 0. Decrement to avoid overflow because we don't handle - // dropping the driver. - PeripheralClockControl::disable(PeripheralEnable::Ledc); - } +/// LEDC (LED PWM Controller). +#[instability::unstable] +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Ledc<'d, Dm: DriverMode> { + _instance: LEDC<'d>, + _guard: PeripheralGuard, + _phantom: PhantomData, + /// Low Speed Timer 0 + pub timer0: TimerCreator<'d, 0, LowSpeed>, + /// Low Speed Timer 1 + pub timer1: TimerCreator<'d, 1, LowSpeed>, + /// Low Speed Timer 2 + pub timer2: TimerCreator<'d, 2, LowSpeed>, + /// Low Speed Timer 3 + pub timer3: TimerCreator<'d, 3, LowSpeed>, + #[cfg(ledc_version = "1")] + /// High Speed Timer 0 + pub hs_timer0: TimerCreator<'d, 0, HighSpeed>, + #[cfg(ledc_version = "1")] + /// High Speed Timer 1 + pub hs_timer1: TimerCreator<'d, 1, HighSpeed>, + #[cfg(ledc_version = "1")] + /// High Speed Timer 2 + pub hs_timer2: TimerCreator<'d, 2, HighSpeed>, + #[cfg(ledc_version = "1")] + /// High Speed Timer 3 + pub hs_timer3: TimerCreator<'d, 3, HighSpeed>, - let ledc = LEDC::regs(); - Ledc { _instance, ledc } - } + /// Low Speed Channel 0 + pub channel0: ChannelCreator<'d, 0, Dm, LowSpeed>, + /// Low Speed Channel 1 + pub channel1: ChannelCreator<'d, 1, Dm, LowSpeed>, + /// Low Speed Channel 2 + pub channel2: ChannelCreator<'d, 2, Dm, LowSpeed>, + /// Low Speed Channel 3 + pub channel3: ChannelCreator<'d, 3, Dm, LowSpeed>, + /// Low Speed Channel 4 + pub channel4: ChannelCreator<'d, 4, Dm, LowSpeed>, + /// Low Speed Channel 5 + pub channel5: ChannelCreator<'d, 5, Dm, LowSpeed>, + #[cfg(ledc_channel_count = "8")] + /// Low Speed Channel 6 + pub channel6: ChannelCreator<'d, 6, Dm, LowSpeed>, + #[cfg(ledc_channel_count = "8")] + /// Low Speed Channel 7 + pub channel7: ChannelCreator<'d, 7, Dm, LowSpeed>, - /// Set global slow clock source - pub fn set_global_slow_clock(&mut self, clock_source: LSGlobalClkSource) { - low_level::set_global_slow_clock(self.ledc, clock_source); - } + #[cfg(ledc_version = "1")] + /// High Speed Channel 0 + pub hs_channel0: ChannelCreator<'d, 0, Dm, HighSpeed>, + #[cfg(ledc_version = "1")] + /// High Speed Channel 1 + pub hs_channel1: ChannelCreator<'d, 1, Dm, HighSpeed>, + #[cfg(ledc_version = "1")] + /// High Speed Channel 2 + pub hs_channel2: ChannelCreator<'d, 2, Dm, HighSpeed>, + #[cfg(ledc_version = "1")] + /// High Speed Channel 3 + pub hs_channel3: ChannelCreator<'d, 3, Dm, HighSpeed>, + #[cfg(ledc_version = "1")] + /// High Speed Channel 4 + pub hs_channel4: ChannelCreator<'d, 4, Dm, HighSpeed>, + #[cfg(ledc_version = "1")] + /// High Speed Channel 5 + pub hs_channel5: ChannelCreator<'d, 5, Dm, HighSpeed>, + #[cfg(ledc_version = "1")] + /// High Speed Channel 6 + pub hs_channel6: ChannelCreator<'d, 6, Dm, HighSpeed>, + #[cfg(ledc_version = "1")] + /// High Speed Channel 7 + pub hs_channel7: ChannelCreator<'d, 7, Dm, HighSpeed>, +} - /// Return a new timer - pub fn timer(&self, number: timer::Number) -> Timer<'d, S> { - Timer::new(self.ledc, number) +impl<'d> Ledc<'d, Blocking> { + /// Creates a new `Ledc` instance. + #[instability::unstable] + pub fn new(instance: LEDC<'d>, config: Config) -> Result { + let guard = PeripheralGuard::new(Peripheral::Ledc); + + let mut ledc = Self { + _instance: instance, + _guard: guard, + _phantom: PhantomData, + + timer0: unsafe { TimerCreator::steal() }, + timer1: unsafe { TimerCreator::steal() }, + timer2: unsafe { TimerCreator::steal() }, + timer3: unsafe { TimerCreator::steal() }, + + #[cfg(ledc_version = "1")] + hs_timer0: unsafe { TimerCreator::steal() }, + #[cfg(ledc_version = "1")] + hs_timer1: unsafe { TimerCreator::steal() }, + #[cfg(ledc_version = "1")] + hs_timer2: unsafe { TimerCreator::steal() }, + #[cfg(ledc_version = "1")] + hs_timer3: unsafe { TimerCreator::steal() }, + + channel0: unsafe { ChannelCreator::steal() }, + channel1: unsafe { ChannelCreator::steal() }, + channel2: unsafe { ChannelCreator::steal() }, + channel3: unsafe { ChannelCreator::steal() }, + channel4: unsafe { ChannelCreator::steal() }, + channel5: unsafe { ChannelCreator::steal() }, + + #[cfg(ledc_channel_count = "8")] + channel6: unsafe { ChannelCreator::steal() }, + #[cfg(ledc_channel_count = "8")] + channel7: unsafe { ChannelCreator::steal() }, + + #[cfg(ledc_version = "1")] + hs_channel0: unsafe { ChannelCreator::steal() }, + #[cfg(ledc_version = "1")] + hs_channel1: unsafe { ChannelCreator::steal() }, + #[cfg(ledc_version = "1")] + hs_channel2: unsafe { ChannelCreator::steal() }, + #[cfg(ledc_version = "1")] + hs_channel3: unsafe { ChannelCreator::steal() }, + #[cfg(ledc_version = "1")] + hs_channel4: unsafe { ChannelCreator::steal() }, + #[cfg(ledc_version = "1")] + hs_channel5: unsafe { ChannelCreator::steal() }, + #[cfg(ledc_version = "1")] + hs_channel6: unsafe { ChannelCreator::steal() }, + #[cfg(ledc_version = "1")] + hs_channel7: unsafe { ChannelCreator::steal() }, + }; + ledc.apply_config(&config)?; + + Ok(ledc) } +} - /// Return a new channel - pub fn channel( - &self, - number: channel::Number, - output_pin: impl PeripheralOutput<'d>, - ) -> Channel<'d, S> { - Channel::new(number, output_pin) +impl<'d, Dm: DriverMode> Ledc<'d, Dm> { + /// Changes the configuration. + #[instability::unstable] + pub fn apply_config(&mut self, config: &Config) -> Result<(), ConfigError> { + low_level::set_global_slow_clock(LEDC::regs(), config.clock_source); + Ok(()) } } diff --git a/esp-hal/src/ledc/timer.rs b/esp-hal/src/ledc/timer.rs index 26f9f8b33ad..cf62039bd46 100644 --- a/esp-hal/src/ledc/timer.rs +++ b/esp-hal/src/ledc/timer.rs @@ -10,350 +10,378 @@ //! (PWM) applications and LED lighting control. //! //! LEDC uses APB as clock source. +//! +//! For more information, please refer to the +#![doc = crate::trm_markdown_link!("ledpwm")] -#[cfg(ledc_version = "1")] -use super::HighSpeed; -use super::{LowSpeed, Speed, low_level}; -use crate::{pac, time::Rate}; +use core::{fmt::Display, marker::PhantomData, sync::atomic::Ordering}; -const LEDC_TIMER_DIV_NUM_MAX: u64 = 0x3FFFF; +use portable_atomic::AtomicU32; -/// Timer errors -#[derive(Debug, Clone, Copy, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Error { - /// Invalid Divisor - Divisor, - /// Frequency unset - FrequencyUnset, -} +use crate::{ + ledc::{Speed, low_level}, + peripherals::LEDC, + system::{Peripheral, PeripheralGuard}, + time::Rate, +}; -#[cfg(ledc_version = "1")] -/// Clock source for HS Timers -#[derive(PartialEq, Eq, Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum HSClockSource { - /// APB clock. - APBClk, - // TODO RefTick, -} +const LEDC_TIMER_DIV_NUM_MAX: u64 = 0x3FFFF; -/// Clock source for LS Timers -#[derive(PartialEq, Eq, Copy, Clone, Debug)] +/// Clock source for LEDC Timers +#[instability::unstable] +#[derive(Default, PartialEq, Eq, Copy, Clone, Debug, Hash)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum LSClockSource { - /// APB clock. - APBClk, - // TODO SLOWClk +pub enum ClockSource { + /// APB clock + #[default] + APBClock, + // TODO: SLOWClk, REF_TICK } /// Timer number -#[derive(PartialEq, Eq, Copy, Clone, Debug)] +#[instability::unstable] +#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Number { - /// Timer 0. + /// Timer 0 Timer0 = 0, - /// Timer 1. + /// Timer 1 Timer1 = 1, - /// Timer 2. + /// Timer 2 Timer2 = 2, - /// Timer 3. + /// Timer 3 Timer3 = 3, } -/// Timer configuration -pub mod config { - use crate::time::Rate; - - /// Number of bits reserved for duty cycle adjustment - #[derive(PartialEq, Eq, Copy, Clone, Debug)] - #[cfg_attr(feature = "defmt", derive(defmt::Format))] - #[allow(clippy::enum_variant_names)] // FIXME: resolve before stabilizing this driver - pub enum Duty { - /// 1-bit resolution for duty cycle adjustment. - Duty1Bit = 1, - /// 2-bit resolution for duty cycle adjustment. - Duty2Bit, - /// 3-bit resolution for duty cycle adjustment. - Duty3Bit, - /// 4-bit resolution for duty cycle adjustment. - Duty4Bit, - /// 5-bit resolution for duty cycle adjustment. - Duty5Bit, - /// 6-bit resolution for duty cycle adjustment. - Duty6Bit, - /// 7-bit resolution for duty cycle adjustment. - Duty7Bit, - /// 8-bit resolution for duty cycle adjustment. - Duty8Bit, - /// 9-bit resolution for duty cycle adjustment. - Duty9Bit, - /// 10-bit resolution for duty cycle adjustment. - Duty10Bit, - /// 11-bit resolution for duty cycle adjustment. - Duty11Bit, - /// 12-bit resolution for duty cycle adjustment. - Duty12Bit, - /// 13-bit resolution for duty cycle adjustment. - Duty13Bit, - /// 14-bit resolution for duty cycle adjustment. - Duty14Bit, - #[cfg(ledc_version = "1")] - /// 15-bit resolution for duty cycle adjustment. - Duty15Bit, - #[cfg(ledc_version = "1")] - /// 16-bit resolution for duty cycle adjustment. - Duty16Bit, - #[cfg(ledc_version = "1")] - /// 17-bit resolution for duty cycle adjustment. - Duty17Bit, - #[cfg(ledc_version = "1")] - /// 18-bit resolution for duty cycle adjustment. - Duty18Bit, - #[cfg(ledc_version = "1")] - /// 19-bit resolution for duty cycle adjustment. - Duty19Bit, - #[cfg(ledc_version = "1")] - /// 20-bit resolution for duty cycle adjustment. - Duty20Bit, - } - - impl TryFrom for Duty { - type Error = (); - - fn try_from(value: u32) -> Result { - Ok(match value { - 1 => Self::Duty1Bit, - 2 => Self::Duty2Bit, - 3 => Self::Duty3Bit, - 4 => Self::Duty4Bit, - 5 => Self::Duty5Bit, - 6 => Self::Duty6Bit, - 7 => Self::Duty7Bit, - 8 => Self::Duty8Bit, - 9 => Self::Duty9Bit, - 10 => Self::Duty10Bit, - 11 => Self::Duty11Bit, - 12 => Self::Duty12Bit, - 13 => Self::Duty13Bit, - 14 => Self::Duty14Bit, - #[cfg(ledc_version = "1")] - 15 => Self::Duty15Bit, - #[cfg(ledc_version = "1")] - 16 => Self::Duty16Bit, - #[cfg(ledc_version = "1")] - 17 => Self::Duty17Bit, - #[cfg(ledc_version = "1")] - 18 => Self::Duty18Bit, - #[cfg(ledc_version = "1")] - 19 => Self::Duty19Bit, - #[cfg(ledc_version = "1")] - 20 => Self::Duty20Bit, - _ => Err(())?, - }) +impl Number { + const fn from_u8(n: u8) -> Self { + // until rust adds const enum generics + match n { + 0 => Number::Timer0, + 1 => Number::Timer1, + 2 => Number::Timer2, + 3 => Number::Timer3, + _ => core::unreachable!(), // defmt::unreachable!() fails const eval } } - - /// Timer configuration - #[derive(Copy, Clone)] - pub struct Config { - /// The duty cycle resolution. - pub duty: Duty, - /// The clock source for the timer. - pub clock_source: CS, - /// The frequency of the PWM signal in Hertz. - pub frequency: Rate, - } } -/// Trait defining the type of timer source -pub trait TimerSpeed: Speed { - /// The type of clock source used by the timer in this speed mode. - type ClockSourceType; +/// Number of bits reserved for duty cycle adjustment +#[instability::unstable] +#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Duty { + /// 1-bit resolution for duty cycle adjustment. + Bit1 = 1, + /// 2-bit resolution for duty cycle adjustment. + Bit2, + /// 3-bit resolution for duty cycle adjustment. + Bit3, + /// 4-bit resolution for duty cycle adjustment. + Bit4, + /// 5-bit resolution for duty cycle adjustment. + Bit5, + /// 6-bit resolution for duty cycle adjustment. + Bit6, + /// 7-bit resolution for duty cycle adjustment. + Bit7, + /// 8-bit resolution for duty cycle adjustment. + Bit8, + /// 9-bit resolution for duty cycle adjustment. + Bit9, + /// 10-bit resolution for duty cycle adjustment. + Bit10, + /// 11-bit resolution for duty cycle adjustment. + Bit11, + /// 12-bit resolution for duty cycle adjustment. + Bit12, + /// 13-bit resolution for duty cycle adjustment. + Bit13, + /// 14-bit resolution for duty cycle adjustment. + Bit14, + #[cfg(ledc_version = "1")] + /// 15-bit resolution for duty cycle adjustment. + Bit15, + #[cfg(ledc_version = "1")] + /// 16-bit resolution for duty cycle adjustment. + Bit16, + #[cfg(ledc_version = "1")] + /// 17-bit resolution for duty cycle adjustment. + Bit17, + #[cfg(ledc_version = "1")] + /// 18-bit resolution for duty cycle adjustment. + Bit18, + #[cfg(ledc_version = "1")] + /// 19-bit resolution for duty cycle adjustment. + Bit19, + #[cfg(ledc_version = "1")] + /// 20-bit resolution for duty cycle adjustment. + Bit20, } -/// Timer source type for LowSpeed timers -impl TimerSpeed for LowSpeed { - /// The clock source type for low-speed timers. - type ClockSourceType = LSClockSource; +impl TryFrom for Duty { + type Error = (); + + fn try_from(value: u32) -> Result { + Ok(match value { + 1 => Self::Bit1, + 2 => Self::Bit2, + 3 => Self::Bit3, + 4 => Self::Bit4, + 5 => Self::Bit5, + 6 => Self::Bit6, + 7 => Self::Bit7, + 8 => Self::Bit8, + 9 => Self::Bit9, + 10 => Self::Bit10, + 11 => Self::Bit11, + 12 => Self::Bit12, + 13 => Self::Bit13, + 14 => Self::Bit14, + #[cfg(ledc_version = "1")] + 15 => Self::Bit15, + #[cfg(ledc_version = "1")] + 16 => Self::Bit16, + #[cfg(ledc_version = "1")] + 17 => Self::Bit17, + #[cfg(ledc_version = "1")] + 18 => Self::Bit18, + #[cfg(ledc_version = "1")] + 19 => Self::Bit19, + #[cfg(ledc_version = "1")] + 20 => Self::Bit20, + _ => Err(())?, + }) + } } -#[cfg(ledc_version = "1")] -/// Timer source type for HighSpeed timers -impl TimerSpeed for HighSpeed { - /// The clock source type for high-speed timers. - type ClockSourceType = HSClockSource; +/// Timer configuration errors +#[instability::unstable] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum ConfigError { + /// Invalid Divisor + Divisor, } -/// Interface for Timers -pub trait TimerIFace { - /// Return the frequency of the timer - fn freq(&self) -> Option; - - /// Configure the timer - fn configure(&mut self, config: config::Config) -> Result<(), Error>; - - /// Check if the timer has been configured - fn is_configured(&self) -> bool; - - /// Return the duty resolution of the timer - fn duty(&self) -> Option; - - /// Return the timer number - fn number(&self) -> Number; - - /// Return the timer frequency, or 0 if not configured - fn frequency(&self) -> u32; +impl Display for ConfigError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Divisor => write!(f, "Invalid Divisor"), + } + } } -/// Interface for HW configuration of timer -pub trait TimerHW { - /// Get the current source timer frequency from the HW - fn freq_hw(&self) -> Option; +impl core::error::Error for ConfigError {} - /// Configure the HW for the timer - fn configure_hw(&self, divisor: u32); +/// Timer configuration +#[instability::unstable] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, procmacros::BuilderLite)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Config { + /// The duty cycle resolution + duty: Duty, + /// The clock source for the timer + clock_source: ClockSource, + /// The frequency of the PWM signal in Hertz + frequency: Rate, +} - /// Update the timer in HW - fn update_hw(&self); +impl Default for Config { + fn default() -> Self { + Self { + duty: Duty::Bit8, + clock_source: ClockSource::default(), + frequency: Rate::from_khz(1), + } + } } -/// Timer struct -pub struct Timer<'a, S: TimerSpeed> { - ledc: &'a pac::ledc::RegisterBlock, - number: Number, - duty: Option, - frequency: u32, - configured: bool, - #[cfg(soc_has_clock_node_ref_tick)] - use_ref_tick: bool, - clock_source: Option, +/// Timer creator +#[instability::unstable] +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct TimerCreator<'d, const TIMER: u8, S: Speed> { + guard: Option, + _phantom: PhantomData<(&'d (), S)>, } -impl<'a, S: TimerSpeed> TimerIFace for Timer<'a, S> -where - Timer<'a, S>: TimerHW, -{ - /// Return the frequency of the timer - fn freq(&self) -> Option { - self.freq_hw() +impl<'d, const TIMER: u8, S: Speed> TimerCreator<'d, TIMER, S> { + /// Reborrow this timer creator for a shorter lifetime `'a`. + /// + /// Use this method if you would like to keep working with this timer after you drop the + /// configured one. + #[instability::unstable] + #[inline] + pub fn reborrow(&mut self) -> TimerCreator<'_, TIMER, S> { + Self { + guard: None, + _phantom: PhantomData, + } } - /// Configure the timer - fn configure(&mut self, config: config::Config) -> Result<(), Error> { - self.duty = Some(config.duty); - self.clock_source = Some(config.clock_source); + /// Configures the timer. + #[instability::unstable] + pub fn configure(self, config: Config) -> Result, ConfigError> { + let number = Number::from_u8(TIMER); - let src_freq: u32 = self.freq().ok_or(Error::FrequencyUnset)?.as_hz(); - let precision = 1 << config.duty as u32; - let frequency: u32 = config.frequency.as_hz(); - self.frequency = frequency; + let mut timer = Timer { + number, + config, + #[cfg(soc_has_clock_node_ref_tick)] + use_ref_tick: false, + _guard: self.guard, + _phantom: PhantomData, + }; - #[cfg_attr(not(soc_has_clock_node_ref_tick), expect(unused_mut))] - let mut divisor = ((src_freq as u64) << 8) / frequency as u64 / precision as u64; + timer.apply_config(&config)?; - #[cfg(soc_has_clock_node_ref_tick)] - if divisor > LEDC_TIMER_DIV_NUM_MAX { - // APB_CLK results in divisor which too high. Try using REF_TICK as clock - // source. - self.use_ref_tick = true; - divisor = (1_000_000u64 << 8) / frequency as u64 / precision as u64; - } + Ok(timer) + } - if !(256..=LEDC_TIMER_DIV_NUM_MAX).contains(&divisor) { - return Err(Error::Divisor); + /// Unsafely steal a timer creator instance. + /// + /// # Safety + /// + /// The caller must ensure that only one instance of a timer is in use at one time. + #[instability::unstable] + #[inline] + pub unsafe fn steal() -> Self { + Self { + guard: Some(PeripheralGuard::new(Peripheral::Ledc)), + _phantom: PhantomData, } + } - self.configure_hw(divisor as u32); - self.update_hw(); + /// Unsafely clone a timer creator instance. + /// + /// # Safety + /// + /// The caller must ensure that only one instance of a timer is in use at one time. + #[instability::unstable] + #[inline] + pub unsafe fn clone_unchecked(&self) -> Self { + unsafe { Self::steal() } + } +} - self.configured = true; +fn apply_config_ls(number: Number, config: &Config) -> Result { + let src_freq: u32 = low_level::ls_freq_hw(config.clock_source).as_hz(); + let precision = 1 << config.duty as u32; + let frequency: u32 = config.frequency.as_hz(); - Ok(()) - } + #[cfg_attr(not(soc_has_clock_node_ref_tick), expect(unused_mut))] + let mut divisor = ((src_freq as u64) << 8) / frequency as u64 / precision as u64; - /// Check if the timer has been configured - fn is_configured(&self) -> bool { - self.configured - } + #[cfg_attr(not(soc_has_clock_node_ref_tick), expect(unused_mut))] + let mut use_ref_tick = false; - /// Return the duty resolution of the timer - fn duty(&self) -> Option { - self.duty + #[cfg(soc_has_clock_node_ref_tick)] + if divisor > LEDC_TIMER_DIV_NUM_MAX { + // APB_CLK results in divisor which is too high. Try using REF_TICK as clock + // source. + use_ref_tick = true; + divisor = (1_000_000u64 << 8) / frequency as u64 / precision as u64; } - /// Return the timer number - fn number(&self) -> Number { - self.number + if !(256..=LEDC_TIMER_DIV_NUM_MAX).contains(&divisor) { + return Err(ConfigError::Divisor); } - /// Return the timer frequency - fn frequency(&self) -> u32 { - self.frequency - } + let ledc = LEDC::regs(); + low_level::ls_configure_hw( + ledc, + number, + divisor as u32, + config.duty as u8, + use_ref_tick, + ); + low_level::ls_update_hw(ledc, number); + + Ok(use_ref_tick) } -impl<'a, S: TimerSpeed> Timer<'a, S> { - /// Create a new instance of a timer - pub fn new(ledc: &'a pac::ledc::RegisterBlock, number: Number) -> Self { - Timer { - ledc, - number, - duty: None, - frequency: 0u32, - configured: false, - #[cfg(soc_has_clock_node_ref_tick)] - use_ref_tick: false, - clock_source: None, - } - } -} +#[cfg(ledc_version = "1")] +fn apply_config_hs(number: Number, config: &Config) -> Result { + let src_freq: u32 = low_level::hs_freq_hw(config.clock_source).as_hz(); + let precision = 1 << config.duty as u32; + let frequency: u32 = config.frequency.as_hz(); -/// Timer HW implementation for LowSpeed timers -impl TimerHW for Timer<'_, LowSpeed> { - /// Get the current source timer frequency from the HW - fn freq_hw(&self) -> Option { - self.clock_source.map(low_level::ls_freq_hw) - } + let divisor = ((src_freq as u64) << 8) / frequency as u64 / precision as u64; - /// Configure the HW for the timer - fn configure_hw(&self, divisor: u32) { - let duty = unwrap!(self.duty) as u8; - #[cfg(soc_has_clock_node_ref_tick)] - let use_ref_tick = self.use_ref_tick; - #[cfg(not(soc_has_clock_node_ref_tick))] - let use_ref_tick = false; - low_level::ls_configure_hw(self.ledc, self.number, divisor, duty, use_ref_tick); + if !(256..=LEDC_TIMER_DIV_NUM_MAX).contains(&divisor) { + return Err(ConfigError::Divisor); } - /// Update the timer in HW - fn update_hw(&self) { - low_level::ls_update_hw(self.ledc, self.number); - } + let ledc = LEDC::regs(); + low_level::hs_configure_hw( + ledc, + number, + divisor as u32, + config.duty as u8, + config.clock_source, + ); + low_level::hs_update_hw(); + + Ok(false) } -#[cfg(ledc_version = "1")] -/// Timer HW implementation for HighSpeed timers -impl TimerHW for Timer<'_, HighSpeed> { - /// Get the current source timer frequency from the HW - fn freq_hw(&self) -> Option { - self.clock_source.map(low_level::hs_freq_hw) +const TIMER_COUNT: usize = if cfg!(ledc_version = "1") { 8 } else { 4 }; +// First 4 timers are LS, then 4 HS (if supported) +pub(super) static TIMER_FREQS: [AtomicU32; TIMER_COUNT] = + [const { AtomicU32::new(0) }; TIMER_COUNT]; + +/// Timer struct +#[instability::unstable] +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Timer<'d, S: Speed> { + number: Number, + config: Config, + #[cfg(soc_has_clock_node_ref_tick)] + use_ref_tick: bool, + _guard: Option, + _phantom: PhantomData<(&'d (), S)>, +} + +impl<'d, S: Speed> Timer<'d, S> { + /// Changes the configuration. + #[instability::unstable] + pub fn apply_config(&mut self, config: &Config) -> Result<(), ConfigError> { + #[cfg_attr(not(soc_has_clock_node_ref_tick), expect(unused))] + let use_ref_tick = if S::IS_HS { + cfg_select! { + ledc_version = "1" => apply_config_hs(self.number, config), + _ => unreachable!() + } + } else { + apply_config_ls(self.number, config) + }?; + + let timer_index = if S::IS_HS { 4 } else { 0 } + self.number as usize; + TIMER_FREQS[timer_index].store(config.frequency.as_hz(), Ordering::Release); + + self.config = *config; + #[cfg(soc_has_clock_node_ref_tick)] + { + self.use_ref_tick = use_ref_tick; + } + + Ok(()) } - /// Configure the HW for the timer - fn configure_hw(&self, divisor: u32) { - let duty = unwrap!(self.duty) as u8; - low_level::hs_configure_hw( - self.ledc, - self.number, - divisor, - duty, - unwrap!(self.clock_source), - ); + /// Returns the configuration of the timer. + #[instability::unstable] + #[inline] + pub fn config(&self) -> &Config { + &self.config } - /// Update the timer in HW - fn update_hw(&self) { - low_level::hs_update_hw(); + /// Returns the number of the timer. + #[instability::unstable] + #[inline] + pub fn number(&self) -> Number { + self.number } } diff --git a/hil-test/Cargo.toml b/hil-test/Cargo.toml index 122bc62a9f0..62b357a7bbc 100644 --- a/hil-test/Cargo.toml +++ b/hil-test/Cargo.toml @@ -78,6 +78,10 @@ harness = false name = "uart" harness = false +[[bin]] +name = "ledc" +harness = false + [dependencies] allocator-api2 = { version = "0.3.0", default-features = false, features = ["alloc"] } critical-section = "1" diff --git a/hil-test/src/bin/ledc.rs b/hil-test/src/bin/ledc.rs new file mode 100644 index 00000000000..10a5ec0e9c5 --- /dev/null +++ b/hil-test/src/bin/ledc.rs @@ -0,0 +1,136 @@ +//! LEDC Test +//% CHIP_FILTER: pcnt_driver_supported +//% FEATURES: unstable + +#![no_std] +#![no_main] + +#[embedded_test::tests(default_timeout = 3)] +mod tests { + use esp_hal::{ + Blocking, + delay::Delay, + gpio::{AnyPin, Flex, Pin}, + ledc::{ + Config as LedcConfig, + Ledc, + channel::Config as ChannelConfig, + timer::{Config as TimerConfig, Duty}, + }, + pcnt::{Pcnt, channel::EdgeMode}, + time::{Instant, Rate}, + }; + + struct Context { + ledc: Ledc<'static, Blocking>, + pcnt: Pcnt<'static>, + pin1: AnyPin<'static>, + pin2: AnyPin<'static>, + } + + #[init] + fn init() -> Context { + let peripherals = esp_hal::init(esp_hal::Config::default()); + + let (pin1, pin2) = hil_test::common_test_pins!(peripherals); + + let pin1 = pin1.degrade(); + let pin2 = pin2.degrade(); + + Context { + ledc: Ledc::new(peripherals.LEDC, LedcConfig::default()).unwrap(), + pcnt: Pcnt::new(peripherals.PCNT), + pin1, + pin2, + } + } + + #[test] + fn test_ledc_pwm(ctx: Context) { + let ledc = ctx.ledc; + let pcnt = ctx.pcnt; + + let timer0_config = TimerConfig::default() + .with_duty(Duty::Bit10) + .with_frequency(Rate::from_khz(1)); + + let timer0 = ledc.timer0.configure(timer0_config).unwrap(); + let mut channel0 = ledc + .channel0 + .configure(&timer0, ChannelConfig::default()) + .unwrap() + .with_pin(ctx.pin1); + + let unit = pcnt.unit0; + + let mut pin2_flex = Flex::new(ctx.pin2); + pin2_flex.set_input_enable(true); + let pin2_in = pin2_flex.peripheral_input(); + + unit.channel0.set_edge_signal(pin2_in); + unit.channel0 + .set_input_mode(EdgeMode::Hold, EdgeMode::Increment); + + let delay = Delay::new(); + unit.clear(); + unit.resume(); + + let max_duty = channel0.max_duty_cycle(); + + // 50% Duty + channel0.set_duty_cycle(max_duty / 2); + delay.delay_millis(100); + + let count = unit.value(); + assert!(count >= 90 && count <= 110, "{count}"); + + // Fading to 0 duty + let start_time = Instant::now(); + let fade_time_ms = 250; + channel0 + .start_duty_fade(max_duty / 2, 0, fade_time_ms) + .unwrap(); + + while channel0.is_duty_fade_running() { + delay.delay_millis(1); + } + + let elapsed = start_time.elapsed().as_millis(); + + assert!(elapsed >= 240 && elapsed <= 260, "{elapsed}"); + + // 0% Duty + channel0.set_duty_cycle(0); + delay.delay_millis(10); + unit.clear(); + delay.delay_millis(100); + + let value = unit.value(); + assert_eq!(value, 0, "{value}"); + + // 100% Duty + channel0.set_duty_cycle(max_duty); + delay.delay_millis(10); + unit.clear(); + delay.delay_millis(100); + + let value = unit.value(); + assert_eq!(value, 0, "{value}"); + + // Updating timer to 2kHz + let timer1_config = TimerConfig::default() + .with_duty(Duty::Bit10) + .with_frequency(Rate::from_khz(2)); + let timer1 = ledc.timer1.configure(timer1_config).unwrap(); + + let mut channel0 = channel0.with_timer(&timer1); + + channel0.set_duty_cycle(max_duty / 2); + delay.delay_millis(10); + unit.clear(); + delay.delay_millis(100); + + let count = unit.value(); + assert!(count >= 190 && count <= 210, "{count}"); + } +}