esp-hal 1.2.0

Bare-metal HAL for Espressif devices
Documentation
//! # LEDC timer
//!
//! ## Overview
//! The LEDC Timer provides a high-level interface to configure and control
//! individual timers of the `LEDC` peripheral.
//!
//! ## Configuration
//! The module allows precise and flexible control over timer configurations,
//! duty cycles and frequencies, making it ideal for Pulse-Width Modulation
//! (PWM) applications and LED lighting control.
//!
//! LEDC uses APB as clock source.

#[cfg(ledc_version = "1")]
use super::HighSpeed;
use super::{LowSpeed, Speed, low_level};
use crate::{pac, time::Rate};

const LEDC_TIMER_DIV_NUM_MAX: u64 = 0x3FFFF;

/// Timer errors
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error {
    /// Invalid Divisor
    Divisor,
    /// Frequency unset
    FrequencyUnset,
}

#[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,
}

/// Clock source for LS Timers.
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum LSClockSource {
    /// APB clock.
    APBClk,
    // TODO SLOWClk
}

/// Timer number
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Number {
    /// Timer 0.
    Timer0 = 0,
    /// Timer 1.
    Timer1 = 1,
    /// Timer 2.
    Timer2 = 2,
    /// 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<u32> for Duty {
        type Error = ();

        fn try_from(value: u32) -> Result<Self, Self::Error> {
            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(())?,
            })
        }
    }

    /// Timer configuration
    #[derive(Copy, Clone)]
    pub struct Config<CS> {
        /// 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;
}

/// Timer source type for LowSpeed timers
impl TimerSpeed for LowSpeed {
    /// The clock source type for low-speed timers.
    type ClockSourceType = LSClockSource;
}

#[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;
}

/// Interface for Timers
pub trait TimerIFace<S: TimerSpeed> {
    /// Returns the frequency of the timer.
    fn freq(&self) -> Option<Rate>;

    /// Configures the timer.
    fn configure(&mut self, config: config::Config<S::ClockSourceType>) -> Result<(), Error>;

    /// Returns whether the timer has been configured.
    fn is_configured(&self) -> bool;

    /// Returns the duty resolution of the timer.
    fn duty(&self) -> Option<config::Duty>;

    /// Returns the timer number.
    fn number(&self) -> Number;

    /// Returns the timer frequency, or 0 if not configured.
    fn frequency(&self) -> u32;
}

/// Interface for HW configuration of timer
pub trait TimerHW<S: TimerSpeed> {
    /// Returns the current source timer frequency from the HW.
    fn freq_hw(&self) -> Option<Rate>;

    /// Configures the HW for the timer.
    fn configure_hw(&self, divisor: u32);

    /// Updates the timer in HW.
    fn update_hw(&self);
}

/// Timer struct
pub struct Timer<'a, S: TimerSpeed> {
    ledc: &'a pac::ledc::RegisterBlock,
    number: Number,
    duty: Option<config::Duty>,
    frequency: u32,
    configured: bool,
    #[cfg(soc_has_clock_node_ref_tick)]
    use_ref_tick: bool,
    clock_source: Option<S::ClockSourceType>,
}

impl<'a, S: TimerSpeed> TimerIFace<S> for Timer<'a, S>
where
    Timer<'a, S>: TimerHW<S>,
{
    /// Returns the frequency of the timer.
    fn freq(&self) -> Option<Rate> {
        self.freq_hw()
    }

    /// Configures the timer.
    fn configure(&mut self, config: config::Config<S::ClockSourceType>) -> Result<(), Error> {
        self.duty = Some(config.duty);
        self.clock_source = Some(config.clock_source);

        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;

        #[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;

        #[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;
        }

        if !(256..=LEDC_TIMER_DIV_NUM_MAX).contains(&divisor) {
            return Err(Error::Divisor);
        }

        self.configure_hw(divisor as u32);
        self.update_hw();

        self.configured = true;

        Ok(())
    }

    /// Returns whether the timer has been configured.
    fn is_configured(&self) -> bool {
        self.configured
    }

    /// Returns the duty resolution of the timer.
    fn duty(&self) -> Option<config::Duty> {
        self.duty
    }

    /// Returns the timer number.
    fn number(&self) -> Number {
        self.number
    }

    /// Returns the timer frequency.
    fn frequency(&self) -> u32 {
        self.frequency
    }
}

impl<'a, S: TimerSpeed> Timer<'a, S> {
    /// Creates 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,
        }
    }
}

/// Timer HW implementation for LowSpeed timers
impl TimerHW<LowSpeed> for Timer<'_, LowSpeed> {
    /// Returns the current source timer frequency from the HW.
    fn freq_hw(&self) -> Option<Rate> {
        self.clock_source.map(low_level::ls_freq_hw)
    }

    /// Configures 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);
    }

    /// Updates the timer in HW.
    fn update_hw(&self) {
        low_level::ls_update_hw(self.ledc, self.number);
    }
}

#[cfg(ledc_version = "1")]
/// Timer HW implementation for HighSpeed timers
impl TimerHW<HighSpeed> for Timer<'_, HighSpeed> {
    /// Returns the current source timer frequency from the HW.
    fn freq_hw(&self) -> Option<Rate> {
        self.clock_source.map(low_level::hs_freq_hw)
    }

    /// Configures 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),
        );
    }

    /// Updates the timer in HW.
    fn update_hw(&self) {
        low_level::hs_update_hw();
    }
}