diff --git a/esp-hal-common/src/gpio/esp32.rs b/esp-hal-common/src/gpio/esp32.rs index ab0d49b54..118396ffb 100644 --- a/esp-hal-common/src/gpio/esp32.rs +++ b/esp-hal-common/src/gpio/esp32.rs @@ -298,22 +298,22 @@ pub enum OutputSignal { VSPICS0 = 68, VSPICS1 = 69, VSPICS2 = 70, - LEDC_HS_SIG_0 = 71, - LEDC_HS_SIG_1 = 72, - LEDC_HS_SIG_2 = 73, - LEDC_HS_SIG_3 = 74, - LEDC_HS_SIG_4 = 75, - LEDC_HS_SIG_5 = 76, - LEDC_HS_SIG_6 = 77, - LEDC_HS_SIG_7 = 78, - LEDC_LS_SIG_0 = 79, - LEDC_LS_SIG_1 = 80, - LEDC_LS_SIG_2 = 81, - LEDC_LS_SIG_3 = 82, - LEDC_LS_SIG_4 = 83, - LEDC_LS_SIG_5 = 84, - LEDC_LS_SIG_6 = 85, - LEDC_LS_SIG_7 = 86, + LEDC_HS_SIG0 = 71, + LEDC_HS_SIG1 = 72, + LEDC_HS_SIG2 = 73, + LEDC_HS_SIG3 = 74, + LEDC_HS_SIG4 = 75, + LEDC_HS_SIG5 = 76, + LEDC_HS_SIG6 = 77, + LEDC_HS_SIG7 = 78, + LEDC_LS_SIG0 = 79, + LEDC_LS_SIG1 = 80, + LEDC_LS_SIG2 = 81, + LEDC_LS_SIG3 = 82, + LEDC_LS_SIG4 = 83, + LEDC_LS_SIG5 = 84, + LEDC_LS_SIG6 = 85, + LEDC_LS_SIG7 = 86, RMT_SIG_0 = 87, RMT_SIG_1 = 88, RMT_SIG_2 = 89, diff --git a/esp-hal-common/src/gpio/esp32s2.rs b/esp-hal-common/src/gpio/esp32s2.rs index d14589c47..e4e775c49 100644 --- a/esp-hal-common/src/gpio/esp32s2.rs +++ b/esp-hal-common/src/gpio/esp32s2.rs @@ -102,6 +102,14 @@ pub enum OutputSignal { SPI3_CS0 = 76, SPI3_CS1 = 77, SPI3_CS2 = 78, + LEDC_LS_SIG0 = 79, + LEDC_LS_SIG1 = 80, + LEDC_LS_SIG2 = 81, + LEDC_LS_SIG3 = 82, + LEDC_LS_SIG4 = 83, + LEDC_LS_SIG5 = 84, + LEDC_LS_SIG6 = 85, + LEDC_LS_SIG7 = 86, RMT_SIG_OUT0 = 87, RMT_SIG_OUT1 = 88, RMT_SIG_OUT2 = 89, diff --git a/esp-hal-common/src/gpio/esp32s3.rs b/esp-hal-common/src/gpio/esp32s3.rs index f7b448744..151d549c9 100644 --- a/esp-hal-common/src/gpio/esp32s3.rs +++ b/esp-hal-common/src/gpio/esp32s3.rs @@ -143,6 +143,14 @@ pub enum OutputSignal { SPI3_WP = 70, SPI3_CS0 = 71, SPI3_CS1 = 72, + LEDC_LS_SIG0 = 73, + LEDC_LS_SIG1 = 74, + LEDC_LS_SIG2 = 75, + LEDC_LS_SIG3 = 76, + LEDC_LS_SIG4 = 77, + LEDC_LS_SIG5 = 78, + LEDC_LS_SIG6 = 79, + LEDC_LS_SIG7 = 80, RMT_SIG_OUT0 = 81, RMT_SIG_OUT1 = 82, RMT_SIG_OUT2 = 83, diff --git a/esp-hal-common/src/ledc/esp32/channel.rs b/esp-hal-common/src/ledc/esp32/channel.rs new file mode 100644 index 000000000..e44bff3f8 --- /dev/null +++ b/esp-hal-common/src/ledc/esp32/channel.rs @@ -0,0 +1,348 @@ +use paste::paste; + +use super::{ + timer::{TimerIFace, TimerSpeed}, + HighSpeed, + LowSpeed, +}; +use crate::{ + gpio::{types::OutputSignal, OutputPin}, + pac::ledc::RegisterBlock, +}; +/// Channel errors +#[derive(Debug)] +pub enum Error { + /// Invalid duty % value + Duty, + /// Timer not configured + Timer, + /// Channel not configured + Channel, +} + +/// Channel number +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +pub enum Number { + Channel0, + Channel1, + Channel2, + Channel3, + Channel4, + Channel5, + Channel6, + Channel7, +} + +/// Channel configuration +pub mod config { + use crate::ledc::timer::{TimerIFace, TimerSpeed}; + + /// Channel configuration + #[derive(Copy, Clone)] + pub struct Config<'a, S: TimerSpeed> { + pub timer: &'a dyn TimerIFace, + pub duty_pct: f32, + } +} + +/// Channel interface +pub trait ChannelIFace<'a, S: TimerSpeed + 'a, O: OutputPin> +where + Channel<'a, S, O>: ChannelHW, +{ + /// Configure channel + fn configure(&mut self, config: config::Config<'a, S>) -> Result<(), Error>; + + /// Set channel duty HW + fn set_duty(&self, duty_pct: f32) -> Result<(), Error>; +} + +/// 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>; + + /// Set channel duty HW + fn set_duty_hw(&self, duty: u32); +} + +/// Channel struct +pub struct Channel<'a, S: TimerSpeed, O: OutputPin> { + ledc: &'a RegisterBlock, + timer: Option<&'a dyn TimerIFace>, + number: Number, + output_pin: O, +} + +impl<'a, S: TimerSpeed, O: OutputPin> Channel<'a, S, O> { + /// Return a new channel + pub fn new(number: Number, output_pin: O) -> Self { + let ledc = unsafe { &*crate::pac::LEDC::ptr() }; + Channel { + ledc, + timer: None, + number, + output_pin, + } + } +} + +impl<'a, S: TimerSpeed, O: OutputPin> ChannelIFace<'a, S, O> for Channel<'a, S, O> +where + Channel<'a, S, O>: ChannelHW, +{ + /// Configure channel + fn configure(&mut self, config: config::Config<'a, S>) -> Result<(), Error> { + self.timer = Some(config.timer); + + self.set_duty(config.duty_pct)?; + self.configure_hw()?; + + Ok(()) + } + + /// Set duty % of channel + fn set_duty(&self, duty_pct: f32) -> Result<(), Error> { + let duty_exp; + if let Some(timer) = self.timer { + if let Some(timer_duty) = timer.get_duty() { + duty_exp = timer_duty as u32; + } else { + return Err(Error::Timer); + } + } else { + return Err(Error::Channel); + } + + let duty_range = 2_u32.pow(duty_exp); + let duty_value = (duty_range as f32 * duty_pct) as u32; + + if duty_value == 0 || duty_pct > 1.0 { + // Not enough bits to represent the requested duty % or duty_pct greater than + // 1.0 + return Err(Error::Duty); + } + + self.set_duty_hw(duty_value); + + Ok(()) + } +} + +/// Macro to configure channel parameters in hw +macro_rules! set_channel { + ( $self: ident, $speed: ident, $num: literal, $channel_number: ident ) => { + paste! { + $self.ledc.[<$speed sch $num _hpoint>] + .write(|w| unsafe { w.[]().bits(0x0) }); + $self.ledc.[<$speed sch $num _conf0>].modify(|_, w| unsafe { + w.[]() + .set_bit() + .[]() + .bits($channel_number) + }); + $self.ledc.[<$speed sch $num _conf1>].write(|w| unsafe { + w.[]() + .set_bit() + .[]() + .set_bit() + .[]() + .bits(0x1) + .[]() + .bits(0x1) + .[]() + .bits(0x0) + }); + } + }; +} + +/// Macro to set duty parameters in hw +macro_rules! set_duty { + ( $self: ident, $speed: ident, $num: literal, $duty: ident ) => { + paste! { + $self.ledc + .[<$speed sch $num _duty>] + .write(|w| unsafe { w.[]().bits($duty << 4) }) + } + }; +} + +/// Macro to update channel configuration (only for LowSpeed channels) +macro_rules! update_channel { + ( $self: ident, $num: literal) => { + paste! { + $self.ledc + .[] + .modify(|_, w| w.[]().set_bit()); + } + }; +} + +/// Channel HW interface for HighSpeed channels +impl<'a, O> ChannelHW for Channel<'a, HighSpeed, O> +where + O: OutputPin, +{ + /// Configure Channel HW except for the duty which is set via + /// [`Self::set_duty_hw`]. + fn configure_hw(&mut self) -> Result<(), Error> { + if let Some(timer) = self.timer { + if !timer.is_configured() { + return Err(Error::Timer); + } + + self.output_pin.set_to_push_pull_output(); + + let channel_number = timer.get_number() as u8; + match self.number { + Number::Channel0 => { + set_channel!(self, h, 0, channel_number); + self.output_pin + .connect_peripheral_to_output(OutputSignal::LEDC_HS_SIG0); + } + Number::Channel1 => { + set_channel!(self, h, 1, channel_number); + self.output_pin + .connect_peripheral_to_output(OutputSignal::LEDC_HS_SIG1); + } + Number::Channel2 => { + set_channel!(self, h, 2, channel_number); + self.output_pin + .connect_peripheral_to_output(OutputSignal::LEDC_HS_SIG2); + } + Number::Channel3 => { + set_channel!(self, h, 3, channel_number); + self.output_pin + .connect_peripheral_to_output(OutputSignal::LEDC_HS_SIG3); + } + Number::Channel4 => { + set_channel!(self, h, 4, channel_number); + self.output_pin + .connect_peripheral_to_output(OutputSignal::LEDC_HS_SIG4); + } + Number::Channel5 => { + set_channel!(self, h, 5, channel_number); + self.output_pin + .connect_peripheral_to_output(OutputSignal::LEDC_HS_SIG5); + } + Number::Channel6 => { + set_channel!(self, h, 6, channel_number); + self.output_pin + .connect_peripheral_to_output(OutputSignal::LEDC_HS_SIG6); + } + Number::Channel7 => { + set_channel!(self, h, 7, channel_number); + self.output_pin + .connect_peripheral_to_output(OutputSignal::LEDC_HS_SIG7); + } + } + } else { + return Err(Error::Timer); + } + + Ok(()) + } + + /// Set duty in channel HW + fn set_duty_hw(&self, duty: u32) { + match self.number { + Number::Channel0 => set_duty!(self, h, 0, duty), + Number::Channel1 => set_duty!(self, h, 1, duty), + Number::Channel2 => set_duty!(self, h, 2, duty), + Number::Channel3 => set_duty!(self, h, 3, duty), + Number::Channel4 => set_duty!(self, h, 4, duty), + Number::Channel5 => set_duty!(self, h, 5, duty), + Number::Channel6 => set_duty!(self, h, 6, duty), + Number::Channel7 => set_duty!(self, h, 7, duty), + }; + } +} + +/// Channel HW interface for LowSpeed channels +impl<'a, O: OutputPin> ChannelHW for Channel<'a, LowSpeed, O> +where + O: OutputPin, +{ + /// Configure Channel HW + fn configure_hw(&mut self) -> Result<(), Error> { + if let Some(timer) = self.timer { + if !timer.is_configured() { + return Err(Error::Timer); + } + + self.output_pin.set_to_push_pull_output(); + + let channel_number = timer.get_number() as u8; + match self.number { + Number::Channel0 => { + set_channel!(self, l, 0, channel_number); + update_channel!(self, 0); + self.output_pin + .connect_peripheral_to_output(OutputSignal::LEDC_LS_SIG0); + } + Number::Channel1 => { + set_channel!(self, l, 1, channel_number); + update_channel!(self, 1); + self.output_pin + .connect_peripheral_to_output(OutputSignal::LEDC_LS_SIG1); + } + Number::Channel2 => { + set_channel!(self, l, 2, channel_number); + update_channel!(self, 2); + self.output_pin + .connect_peripheral_to_output(OutputSignal::LEDC_LS_SIG2); + } + Number::Channel3 => { + set_channel!(self, l, 3, channel_number); + update_channel!(self, 3); + self.output_pin + .connect_peripheral_to_output(OutputSignal::LEDC_LS_SIG3); + } + Number::Channel4 => { + set_channel!(self, l, 4, channel_number); + update_channel!(self, 4); + self.output_pin + .connect_peripheral_to_output(OutputSignal::LEDC_LS_SIG4); + } + Number::Channel5 => { + set_channel!(self, l, 5, channel_number); + update_channel!(self, 5); + self.output_pin + .connect_peripheral_to_output(OutputSignal::LEDC_LS_SIG5); + } + Number::Channel6 => { + set_channel!(self, l, 6, channel_number); + update_channel!(self, 6); + self.output_pin + .connect_peripheral_to_output(OutputSignal::LEDC_LS_SIG6); + } + Number::Channel7 => { + set_channel!(self, l, 7, channel_number); + update_channel!(self, 7); + self.output_pin + .connect_peripheral_to_output(OutputSignal::LEDC_LS_SIG7); + } + } + } else { + return Err(Error::Timer); + } + + Ok(()) + } + + /// Set duty in channel HW + fn set_duty_hw(&self, duty: u32) { + match self.number { + Number::Channel0 => set_duty!(self, l, 0, duty), + Number::Channel1 => set_duty!(self, l, 1, duty), + Number::Channel2 => set_duty!(self, l, 2, duty), + Number::Channel3 => set_duty!(self, l, 3, duty), + Number::Channel4 => set_duty!(self, l, 4, duty), + Number::Channel5 => set_duty!(self, l, 5, duty), + Number::Channel6 => set_duty!(self, l, 6, duty), + Number::Channel7 => set_duty!(self, l, 7, duty), + }; + } +} diff --git a/esp-hal-common/src/ledc/esp32/mod.rs b/esp-hal-common/src/ledc/esp32/mod.rs new file mode 100644 index 000000000..ece3cc15f --- /dev/null +++ b/esp-hal-common/src/ledc/esp32/mod.rs @@ -0,0 +1,126 @@ +//! LEDC (LED PWM Controller) peripheral control +//! +//! Currently only supports fixed frequency output. Hardware fade support and +//! interrupts are not currently implemented. High Speed (only ESP32) and Low +//! Speed channels are available. +//! +//! # LowSpeed Example: +//! The following will configure the Low Speed Channel0 to 24kHz output with +//! 10% duty using the ABPClock ``` +//! let mut ledc = LEDC::new(&clock_control, &mut +//! system.peripheral_clock_control); +//! +//! ledc.set_global_slow_clock(LSGlobalClkSource::APBClk); +//! let mut lstimer0 = ledc.get_timer::(timer::Number::Timer0); +//! lstimer0 +//! .configure(timer::config::Config { +//! duty: timer::config::Duty::Duty5Bit, +//! clock_source: timer::LSClockSource::APBClk, +//! frequency: 24u32.kHz(), +//! }) +//! .unwrap(); +//! +//! let mut channel0 = ledc.get_channel(channel::Number::Channel0, led); +//! channel0 +//! .configure(channel::config::Config { +//! timer: &lstimer0, +//! duty: 0.1, +//! }) +//! .unwrap(); +//! ``` +//! # HighSpeed (only ESP32) Example: +//! The following will configure the High Speed Channel0 to 24kHz output with +//! 10% duty using the ABPClock ``` +//! let ledc = LEDC::new(&clock_control, &mut +//! system.peripheral_clock_control); +//! let mut hstimer0 = ledc.get_timer::(timer::Number::Timer0); +//! hstimer0 +//! .configure(timer::config::Config { +//! duty: timer::config::Duty::Duty5Bit, +//! clock_source: timer::HSClockSource::APBClk, +//! frequency: 24u32.kHz(), +//! }) +//! .unwrap(); +//! +//! let mut channel0 = ledc.get_channel(channel::Number::Channel0, led); +//! channel0 +//! .configure(channel::config::Config { +//! timer: &hstimer0, +//! duty: 0.1, +//! }) +//! .unwrap(); +//! ``` +//! # TODO +//! - Hardware fade support +//! - Interrupts + +use channel::Channel; +use timer::Timer; + +use self::timer::TimerSpeed; +use crate::{ + clock::Clocks, + gpio::OutputPin, + system::{Peripheral, PeripheralClockControl}, +}; + +pub mod channel; +pub mod timer; + +/// Global slow clock source +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +pub enum LSGlobalClkSource { + APBClk, +} + +/// LEDC (LED PWM Controller) +pub struct LEDC<'a> { + ledc: &'a crate::pac::ledc::RegisterBlock, + clock_control_config: &'a Clocks, +} + +/// Used to specify HighSpeed Timer/Channel +pub struct HighSpeed {} +/// Used to specify LowSpeed Timer/Channel +pub struct LowSpeed {} + +pub trait Speed {} + +impl Speed for HighSpeed {} + +impl Speed for LowSpeed {} + +impl<'a> LEDC<'a> { + /// Return a new LEDC + pub fn new(clock_control_config: &'a Clocks, system: &mut PeripheralClockControl) -> Self { + system.enable(Peripheral::Ledc); + + let ledc = unsafe { &*crate::pac::LEDC::ptr() }; + LEDC { + ledc, + clock_control_config, + } + } + + /// Set global slow clock source + pub fn set_global_slow_clock(&mut self, _clock_source: LSGlobalClkSource) { + self.ledc.conf.write(|w| w.apb_clk_sel().set_bit()); + self.ledc + .lstimer0_conf + .modify(|_, w| w.para_up().set_bit()); + } + + /// Return a new timer + pub fn get_timer(&self, number: timer::Number) -> Timer { + Timer::new(self.ledc, self.clock_control_config, number) + } + + /// Return a new channel + pub fn get_channel( + &self, + number: channel::Number, + output_pin: O, + ) -> Channel { + Channel::new(number, output_pin) + } +} diff --git a/esp-hal-common/src/ledc/esp32/timer.rs b/esp-hal-common/src/ledc/esp32/timer.rs new file mode 100644 index 000000000..3dc0eeb68 --- /dev/null +++ b/esp-hal-common/src/ledc/esp32/timer.rs @@ -0,0 +1,355 @@ +use super::{LowSpeed, Speed}; +use crate::{clock::Clocks, pac::ledc, prelude::_fugit_MegahertzU32}; +/// Timer errors +#[derive(Debug)] +pub enum Error { + /// Invalid Divisor + Divisor, +} + +use super::HighSpeed; + +/// Clock source for HS Timers +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +pub enum HSClockSource { + // TODO RefTick, + APBClk, +} + +/// Clock source for LS Timers +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +pub enum LSClockSource { + APBClk, + // TODO SLOWClk + // SLOWClk, +} + +/// Timer number +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +pub enum Number { + Timer0, + Timer1, + Timer2, + Timer3, +} + +/// Timer configuration +pub mod config { + + use crate::prelude::_fugit_HertzU32; + + /// Number of bits reserved for duty cycle adjustment + #[derive(PartialEq, Eq, Copy, Clone, Debug)] + pub enum Duty { + Duty1Bit = 1, + Duty2Bit, + Duty3Bit, + Duty4Bit, + Duty5Bit, + Duty6Bit, + Duty7Bit, + Duty8Bit, + Duty9Bit, + Duty10Bit, + Duty11Bit, + Duty12Bit, + Duty13Bit, + Duty14Bit, + Duty15Bit, + Duty16Bit, + Duty17Bit, + Duty18Bit, + Duty19Bit, + Duty20Bit, + } + + /// Timer configuration + #[derive(Copy, Clone)] + pub struct Config { + pub duty: Duty, + pub clock_source: CS, + pub frequency: _fugit_HertzU32, + } +} + +/// Trait defining the type of timer source +pub trait TimerSpeed: Speed { + type ClockSourceType; +} + +/// Timer source type for LowSpeed timers +impl TimerSpeed for LowSpeed { + type ClockSourceType = LSClockSource; +} + +/// Timer source type for HighSpeed timers +impl TimerSpeed for HighSpeed { + type ClockSourceType = HSClockSource; +} + +/// Interface for Timers +pub trait TimerIFace { + /// Return the frequency of the timer + fn get_freq(&self) -> Option<_fugit_MegahertzU32>; + + /// 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 get_duty(&self) -> Option; + + /// Return the timer number + fn get_number(&self) -> Number; +} + +/// Interface for HW configuration of timer +pub trait TimerHW { + /// Get the current source timer frequency from the HW + fn get_freq_hw(&self) -> Option<_fugit_MegahertzU32>; + + /// Configure the HW for the timer + fn configure_hw(&self, divisor: u32); + + /// Update the timer in HW + fn update_hw(&self); +} + +/// Timer struct +pub struct Timer<'a, S: TimerSpeed> { + ledc: &'a crate::pac::ledc::RegisterBlock, + clock_control_config: &'a Clocks, + number: Number, + duty: Option, + configured: bool, + clock_source: Option, +} + +impl<'a, S: TimerSpeed> TimerIFace for Timer<'a, S> +where + Timer<'a, S>: TimerHW, +{ + /// Return the frequency of the timer + fn get_freq(&self) -> Option<_fugit_MegahertzU32> { + self.get_freq_hw() + } + + /// Configure the timer + fn configure(&mut self, config: config::Config) -> Result<(), Error> { + self.duty = Some(config.duty); + self.clock_source = Some(config.clock_source); + + let src_freq: u32 = self.get_freq().unwrap().to_Hz(); + let precision = 2_u64.pow(config.duty as u32); + let frequency: u32 = config.frequency.raw(); + + let divisor = (((src_freq as u64) << 8) + ((frequency as u64 * precision) / 2)) + / (frequency as u64 * precision); + + if divisor >= 0x10_0000 || divisor == 0 { + return Err(Error::Divisor); + } + + self.configure_hw(divisor as u32); + self.update_hw(); + + self.configured = true; + + Ok(()) + } + + /// Check if the timer has been configured + fn is_configured(&self) -> bool { + self.configured + } + + /// Return the duty resolution of the timer + fn get_duty(&self) -> Option { + self.duty + } + + /// Return the timer number + fn get_number(&self) -> Number { + self.number + } +} + +impl<'a, S: TimerSpeed> Timer<'a, S> { + /// Create a new intance of a timer + pub fn new( + ledc: &'a ledc::RegisterBlock, + clock_control_config: &'a Clocks, + number: Number, + ) -> Self { + Timer { + ledc, + clock_control_config, + number, + duty: None, + configured: false, + clock_source: None, + } + } + + // TODO This function will be relevant when we add other clk sources + // Helper function that return the current frequency of the LowSpeed global + // source + // fn get_slow_clock_freq(&self) -> _fugit_MegahertzU32 { + // if self.ledc.conf.read().apb_clk_sel().bit_is_clear() { + // 8u32.MHz().into() + // } else { + // // 80u32.MHz().into() + // self.clock_control_config.apb_clock + // } + // } +} + +/// Timer HW implementation for LowSpeed timers +impl<'a> TimerHW for Timer<'a, LowSpeed> { + /// Get the current source timer frequency from the HW + fn get_freq_hw(&self) -> Option<_fugit_MegahertzU32> { + self.clock_source.map(|cs| match cs { + LSClockSource::APBClk => self.clock_control_config.apb_clock, + }) + } + + /// Configure the HW for the timer + fn configure_hw(&self, divisor: u32) { + let duty = self.duty.unwrap() as u8; + let sel_lstimer = self.clock_source.unwrap() == LSClockSource::APBClk; + match self.number { + Number::Timer0 => self.ledc.lstimer0_conf.modify(|_, w| unsafe { + w.tick_sel() + .bit(sel_lstimer) + .rst() + .clear_bit() + .pause() + .clear_bit() + .div_num() + .bits(divisor) + .duty_res() + .bits(duty) + }), + Number::Timer1 => self.ledc.lstimer1_conf.modify(|_, w| unsafe { + w.tick_sel() + .bit(sel_lstimer) + .rst() + .clear_bit() + .pause() + .clear_bit() + .div_num() + .bits(divisor) + .duty_res() + .bits(duty) + }), + Number::Timer2 => self.ledc.lstimer2_conf.modify(|_, w| unsafe { + w.tick_sel() + .bit(sel_lstimer) + .rst() + .clear_bit() + .pause() + .clear_bit() + .div_num() + .bits(divisor) + .duty_res() + .bits(duty) + }), + Number::Timer3 => self.ledc.lstimer3_conf.modify(|_, w| unsafe { + w.tick_sel() + .bit(sel_lstimer) + .rst() + .clear_bit() + .pause() + .clear_bit() + .div_num() + .bits(divisor) + .duty_res() + .bits(duty) + }), + }; + } + + /// Update the timer in HW + fn update_hw(&self) { + match self.number { + Number::Timer0 => self.ledc.lstimer0_conf.modify(|_, w| w.para_up().set_bit()), + Number::Timer1 => self.ledc.lstimer1_conf.modify(|_, w| w.para_up().set_bit()), + Number::Timer2 => self.ledc.lstimer2_conf.modify(|_, w| w.para_up().set_bit()), + Number::Timer3 => self.ledc.lstimer3_conf.modify(|_, w| w.para_up().set_bit()), + }; + } +} + +/// Timer HW implementation for HighSpeed timers +impl<'a> TimerHW for Timer<'a, HighSpeed> { + /// Get the current source timer frequency from the HW + fn get_freq_hw(&self) -> Option<_fugit_MegahertzU32> { + self.clock_source.map(|cs| match cs { + // TODO RefTick HSClockSource::RefTick => self.clock_control_config.apb_clock, + HSClockSource::APBClk => self.clock_control_config.apb_clock, + }) + } + + /// Configure the HW for the timer + fn configure_hw(&self, divisor: u32) { + let duty = self.duty.unwrap() as u8; + let sel_hstimer = self.clock_source.unwrap() == HSClockSource::APBClk; + match self.number { + Number::Timer0 => self.ledc.hstimer0_conf.modify(|_, w| unsafe { + w.tick_sel() + .bit(sel_hstimer) + .rst() + .clear_bit() + .pause() + .clear_bit() + .div_num() + .bits(divisor) + .duty_res() + .bits(duty) + }), + Number::Timer1 => self.ledc.hstimer1_conf.modify(|_, w| unsafe { + w.tick_sel() + .bit(sel_hstimer) + .rst() + .clear_bit() + .pause() + .clear_bit() + .div_num() + .bits(divisor) + .duty_res() + .bits(duty) + }), + Number::Timer2 => self.ledc.hstimer2_conf.modify(|_, w| unsafe { + w.tick_sel() + .bit(sel_hstimer) + .rst() + .clear_bit() + .pause() + .clear_bit() + .div_num() + .bits(divisor) + .duty_res() + .bits(duty) + }), + Number::Timer3 => self.ledc.hstimer3_conf.modify(|_, w| unsafe { + w.tick_sel() + .bit(sel_hstimer) + .rst() + .clear_bit() + .pause() + .clear_bit() + .div_num() + .bits(divisor) + .duty_res() + .bits(duty) + }), + }; + } + + /// Update the timer in HW + fn update_hw(&self) { + // Nothing to do for HS timers + } +} diff --git a/esp-hal-common/src/ledc/others/channel.rs b/esp-hal-common/src/ledc/others/channel.rs new file mode 100644 index 000000000..c875cfe6f --- /dev/null +++ b/esp-hal-common/src/ledc/others/channel.rs @@ -0,0 +1,273 @@ +use paste::paste; + +use super::{ + timer::{TimerIFace, TimerSpeed}, + LowSpeed, +}; +use crate::{ + gpio::{types::OutputSignal, OutputPin}, + pac::ledc::RegisterBlock, +}; +/// Channel errors +#[derive(Debug)] +pub enum Error { + /// Invalid duty % value + Duty, + /// Timer not configured + Timer, + /// Channel not configured + Channel, +} + +/// Channel number +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +pub enum Number { + Channel0, + Channel1, + Channel2, + Channel3, + Channel4, + Channel5, + #[cfg(not(feature = "esp32c3"))] + Channel6, + #[cfg(not(feature = "esp32c3"))] + Channel7, +} + +/// Channel configuration +pub mod config { + use crate::ledc::timer::{TimerIFace, TimerSpeed}; + + /// Channel configuration + #[derive(Copy, Clone)] + pub struct Config<'a, S: TimerSpeed> { + pub timer: &'a dyn TimerIFace, + pub duty_pct: f32, + } +} + +/// Channel interface +pub trait ChannelIFace<'a, S: TimerSpeed + 'a, O: OutputPin> +where + Channel<'a, S, O>: ChannelHW, +{ + /// Configure channel + fn configure(&mut self, config: config::Config<'a, S>) -> Result<(), Error>; + + /// Set channel duty HW + fn set_duty(&self, duty_pct: f32) -> Result<(), Error>; +} + +/// 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>; + + /// Set channel duty HW + fn set_duty_hw(&self, duty: u32); +} + +/// Channel struct +pub struct Channel<'a, S: TimerSpeed, O: OutputPin> { + ledc: &'a RegisterBlock, + timer: Option<&'a dyn TimerIFace>, + number: Number, + output_pin: O, +} + +impl<'a, S: TimerSpeed, O: OutputPin> Channel<'a, S, O> { + /// Return a new channel + pub fn new(number: Number, output_pin: O) -> Self { + let ledc = unsafe { &*crate::pac::LEDC::ptr() }; + Channel { + ledc, + timer: None, + number, + output_pin, + } + } +} + +impl<'a, S: TimerSpeed, O: OutputPin> ChannelIFace<'a, S, O> for Channel<'a, S, O> +where + Channel<'a, S, O>: ChannelHW, +{ + /// Configure channel + fn configure(&mut self, config: config::Config<'a, S>) -> Result<(), Error> { + self.timer = Some(config.timer); + + self.set_duty(config.duty_pct)?; + self.configure_hw()?; + + Ok(()) + } + + /// Set duty % of channel + fn set_duty(&self, duty_pct: f32) -> Result<(), Error> { + let duty_exp; + if let Some(timer) = self.timer { + if let Some(timer_duty) = timer.get_duty() { + duty_exp = timer_duty as u32; + } else { + return Err(Error::Timer); + } + } else { + return Err(Error::Channel); + } + + let duty_range = 2_u32.pow(duty_exp); + let duty_value = (duty_range as f32 * duty_pct) as u32; + + if duty_value == 0 || duty_pct > 1.0 { + // Not enough bits to represent the requested duty % or duty_pct greater than + // 1.0 + return Err(Error::Duty); + } + + self.set_duty_hw(duty_value); + + Ok(()) + } +} + +/// Macro to configure channel parameters in hw +macro_rules! set_channel { + ( $self: ident, $speed: ident, $num: literal, $channel_number: ident ) => { + paste! { + $self.ledc.[] + .write(|w| unsafe { w.[]().bits(0x0) }); + $self.ledc.[].modify(|_, w| unsafe { + w.[]() + .set_bit() + .[]() + .bits($channel_number) + }); + $self.ledc.[].write(|w| unsafe { + w.[]() + .set_bit() + .[]() + .set_bit() + .[]() + .bits(0x1) + .[]() + .bits(0x1) + .[]() + .bits(0x0) + }); + } + }; +} + +/// Macro to set duty parameters in hw +macro_rules! set_duty { + ( $self: ident, $speed: ident, $num: literal, $duty: ident ) => { + paste! { + $self.ledc + .[] + .write(|w| unsafe { w.[]().bits($duty << 4) }) + } + }; +} + +/// Macro to update channel configuration (only for LowSpeed channels) +macro_rules! update_channel { + ( $self: ident, $num: literal) => { + paste! { + $self.ledc + .[] + .modify(|_, w| w.[]().set_bit()); + } + }; +} + +/// Channel HW interface for LowSpeed channels +impl<'a, O: OutputPin> ChannelHW for Channel<'a, LowSpeed, O> +where + O: OutputPin, +{ + /// Configure Channel HW + fn configure_hw(&mut self) -> Result<(), Error> { + if let Some(timer) = self.timer { + if !timer.is_configured() { + return Err(Error::Timer); + } + + self.output_pin.set_to_push_pull_output(); + + let channel_number = timer.get_number() as u8; + match self.number { + Number::Channel0 => { + set_channel!(self, l, 0, channel_number); + update_channel!(self, 0); + self.output_pin + .connect_peripheral_to_output(OutputSignal::LEDC_LS_SIG0); + } + Number::Channel1 => { + set_channel!(self, l, 1, channel_number); + update_channel!(self, 1); + self.output_pin + .connect_peripheral_to_output(OutputSignal::LEDC_LS_SIG1); + } + Number::Channel2 => { + set_channel!(self, l, 2, channel_number); + update_channel!(self, 2); + self.output_pin + .connect_peripheral_to_output(OutputSignal::LEDC_LS_SIG2); + } + Number::Channel3 => { + set_channel!(self, l, 3, channel_number); + update_channel!(self, 3); + self.output_pin + .connect_peripheral_to_output(OutputSignal::LEDC_LS_SIG3); + } + Number::Channel4 => { + set_channel!(self, l, 4, channel_number); + update_channel!(self, 4); + self.output_pin + .connect_peripheral_to_output(OutputSignal::LEDC_LS_SIG4); + } + Number::Channel5 => { + set_channel!(self, l, 5, channel_number); + update_channel!(self, 5); + self.output_pin + .connect_peripheral_to_output(OutputSignal::LEDC_LS_SIG5); + } + #[cfg(not(feature = "esp32c3"))] + Number::Channel6 => { + set_channel!(self, l, 6, channel_number); + update_channel!(self, 6); + self.output_pin + .connect_peripheral_to_output(OutputSignal::LEDC_LS_SIG6); + } + #[cfg(not(feature = "esp32c3"))] + Number::Channel7 => { + set_channel!(self, l, 7, channel_number); + update_channel!(self, 7); + self.output_pin + .connect_peripheral_to_output(OutputSignal::LEDC_LS_SIG7); + } + } + } else { + return Err(Error::Timer); + } + + Ok(()) + } + + /// Set duty in channel HW + fn set_duty_hw(&self, duty: u32) { + match self.number { + Number::Channel0 => set_duty!(self, l, 0, duty), + Number::Channel1 => set_duty!(self, l, 1, duty), + Number::Channel2 => set_duty!(self, l, 2, duty), + Number::Channel3 => set_duty!(self, l, 3, duty), + Number::Channel4 => set_duty!(self, l, 4, duty), + Number::Channel5 => set_duty!(self, l, 5, duty), + #[cfg(not(feature = "esp32c3"))] + Number::Channel6 => set_duty!(self, l, 6, duty), + #[cfg(not(feature = "esp32c3"))] + Number::Channel7 => set_duty!(self, l, 7, duty), + }; + } +} diff --git a/esp-hal-common/src/ledc/others/mod.rs b/esp-hal-common/src/ledc/others/mod.rs new file mode 100644 index 000000000..60e64f71c --- /dev/null +++ b/esp-hal-common/src/ledc/others/mod.rs @@ -0,0 +1,104 @@ +//! LEDC (LED PWM Controller) peripheral control +//! +//! Currently only supports fixed frequency output. Hardware fade support and +//! interrupts are not currently implemented. Low Speed channels are available. +//! +//! # LowSpeed Example: +//! The following will configure the Low Speed Channel0 to 24kHz output with +//! 10% duty using the ABPClock ``` +//! let mut ledc = LEDC::new(&clock_control, &mut +//! system.peripheral_clock_control); +//! +//! ledc.set_global_slow_clock(LSGlobalClkSource::APBClk); +//! let mut lstimer0 = ledc.get_timer::(timer::Number::Timer0); +//! lstimer0 +//! .configure(timer::config::Config { +//! duty: timer::config::Duty::Duty5Bit, +//! clock_source: timer::LSClockSource::APBClk, +//! frequency: 24u32.kHz(), +//! }) +//! .unwrap(); +//! +//! let mut channel0 = ledc.get_channel(channel::Number::Channel0, led); +//! channel0 +//! .configure(channel::config::Config { +//! timer: &lstimer0, +//! duty: 0.1, +//! }) +//! .unwrap(); +//! ``` +//! # TODO +//! - Hardware fade support +//! - Interrupts + +use channel::Channel; +use timer::Timer; + +use self::timer::TimerSpeed; +use crate::{ + clock::Clocks, + gpio::OutputPin, + system::{Peripheral, PeripheralClockControl}, +}; + +pub mod channel; +pub mod timer; + +/// Global slow clock source +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +pub enum LSGlobalClkSource { + APBClk, +} + +/// LEDC (LED PWM Controller) +pub struct LEDC<'a> { + ledc: &'a crate::pac::ledc::RegisterBlock, + clock_control_config: &'a Clocks, +} + +/// Used to specify LowSpeed Timer/Channel +pub struct LowSpeed {} + +pub trait Speed {} + + +impl Speed for LowSpeed {} + +impl<'a> LEDC<'a> { + /// Return a new LEDC + pub fn new(clock_control_config: &'a Clocks, system: &mut PeripheralClockControl) -> Self { + system.enable(Peripheral::Ledc); + + let ledc = unsafe { &*crate::pac::LEDC::ptr() }; + LEDC { + ledc, + clock_control_config, + } + } + + /// Set global slow clock source + pub fn set_global_slow_clock(&mut self, _clock_source: LSGlobalClkSource) { + match _clock_source { + LSGlobalClkSource::APBClk => { + self.ledc.conf.write(|w| unsafe { w.apb_clk_sel().bits(1) }) + } /* LSGlobalClkSource::XTALClk => { + * self.ledc.conf.write(|w| unsafe { w.apb_clk_sel().bits(3) }) + * } */ + } + self.ledc.timer0_conf.modify(|_, w| w.para_up().set_bit()); + } + + /// Return a new timer + pub fn get_timer(&self, number: timer::Number) -> Timer { + Timer::new(self.ledc, self.clock_control_config, number) + } + + /// Return a new channel + pub fn get_channel( + &self, + number: channel::Number, + output_pin: O, + ) -> Channel { + Channel::new(number, output_pin) + } +} diff --git a/esp-hal-common/src/ledc/others/timer.rs b/esp-hal-common/src/ledc/others/timer.rs new file mode 100644 index 000000000..644c68d80 --- /dev/null +++ b/esp-hal-common/src/ledc/others/timer.rs @@ -0,0 +1,263 @@ +use super::{LowSpeed, Speed}; +use crate::{clock::Clocks, pac::ledc, prelude::_fugit_MegahertzU32}; +/// Timer errors +#[derive(Debug)] +pub enum Error { + /// Invalid Divisor + Divisor, +} + +/// Clock source for LS Timers +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +pub enum LSClockSource { + APBClk, + // TODO SLOWClk + // SLOWClk, +} + +/// Timer number +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +pub enum Number { + Timer0, + Timer1, + Timer2, + Timer3, +} + +/// Timer configuration +pub mod config { + + use crate::prelude::_fugit_HertzU32; + + /// Number of bits reserved for duty cycle adjustment + #[derive(PartialEq, Eq, Copy, Clone, Debug)] + pub enum Duty { + Duty1Bit = 1, + Duty2Bit, + Duty3Bit, + Duty4Bit, + Duty5Bit, + Duty6Bit, + Duty7Bit, + Duty8Bit, + Duty9Bit, + Duty10Bit, + Duty11Bit, + Duty12Bit, + Duty13Bit, + Duty14Bit, + } + + /// Timer configuration + #[derive(Copy, Clone)] + pub struct Config { + pub duty: Duty, + pub clock_source: CS, + pub frequency: _fugit_HertzU32, + } +} + +/// Trait defining the type of timer source +pub trait TimerSpeed: Speed { + type ClockSourceType; +} + +/// Timer source type for LowSpeed timers +impl TimerSpeed for LowSpeed { + type ClockSourceType = LSClockSource; +} + +/// Interface for Timers +pub trait TimerIFace { + /// Return the frequency of the timer + fn get_freq(&self) -> Option<_fugit_MegahertzU32>; + + /// 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 get_duty(&self) -> Option; + + /// Return the timer number + fn get_number(&self) -> Number; +} + +/// Interface for HW configuration of timer +pub trait TimerHW { + /// Get the current source timer frequency from the HW + fn get_freq_hw(&self) -> Option<_fugit_MegahertzU32>; + + /// Configure the HW for the timer + fn configure_hw(&self, divisor: u32); + + /// Update the timer in HW + fn update_hw(&self); +} + +/// Timer struct +pub struct Timer<'a, S: TimerSpeed> { + ledc: &'a crate::pac::ledc::RegisterBlock, + clock_control_config: &'a Clocks, + number: Number, + duty: Option, + configured: bool, + clock_source: Option, +} + +impl<'a, S: TimerSpeed> TimerIFace for Timer<'a, S> +where + Timer<'a, S>: TimerHW, +{ + /// Return the frequency of the timer + fn get_freq(&self) -> Option<_fugit_MegahertzU32> { + self.get_freq_hw() + } + + /// Configure the timer + fn configure(&mut self, config: config::Config) -> Result<(), Error> { + self.duty = Some(config.duty); + self.clock_source = Some(config.clock_source); + + let src_freq: u32 = self.get_freq().unwrap().to_Hz(); + let precision = 2_u64.pow(config.duty as u32); + let frequency: u32 = config.frequency.raw(); + + let divisor = (((src_freq as u64) << 8) + ((frequency as u64 * precision) / 2)) + / (frequency as u64 * precision); + + if divisor >= 0x10_0000 || divisor == 0 { + return Err(Error::Divisor); + } + + self.configure_hw(divisor as u32); + self.update_hw(); + + self.configured = true; + + Ok(()) + } + + /// Check if the timer has been configured + fn is_configured(&self) -> bool { + self.configured + } + + /// Return the duty resolution of the timer + fn get_duty(&self) -> Option { + self.duty + } + + /// Return the timer number + fn get_number(&self) -> Number { + self.number + } +} + +impl<'a, S: TimerSpeed> Timer<'a, S> { + /// Create a new intance of a timer + pub fn new( + ledc: &'a ledc::RegisterBlock, + clock_control_config: &'a Clocks, + number: Number, + ) -> Self { + Timer { + ledc, + clock_control_config, + number, + duty: None, + configured: false, + clock_source: None, + } + } + + // TODO This function will be relevant when we add other clk sources + // Helper function that return the current frequency of the LowSpeed global + // source + // fn get_slow_clock_freq(&self) -> _fugit_MegahertzU32 { + // if self.ledc.conf.read().apb_clk_sel().bit_is_clear() { + // 8u32.MHz().into() + // } else { + // // 80u32.MHz().into() + // self.clock_control_config.apb_clock + // } + // } +} + +/// Timer HW implementation for LowSpeed timers +impl<'a> TimerHW for Timer<'a, LowSpeed> { + /// Get the current source timer frequency from the HW + fn get_freq_hw(&self) -> Option<_fugit_MegahertzU32> { + self.clock_source.map(|cs| match cs { + LSClockSource::APBClk => self.clock_control_config.apb_clock, + }) + } + + /// Configure the HW for the timer + fn configure_hw(&self, divisor: u32) { + let duty = self.duty.unwrap() as u8; + let sel_lstimer = self.clock_source.unwrap() == LSClockSource::APBClk; + match self.number { + Number::Timer0 => self.ledc.timer0_conf.modify(|_, w| unsafe { + w.tick_sel() + .bit(sel_lstimer) + .rst() + .clear_bit() + .pause() + .clear_bit() + .clk_div() + .bits(divisor) + .duty_res() + .bits(duty) + }), + Number::Timer1 => self.ledc.timer1_conf.modify(|_, w| unsafe { + w.tick_sel() + .bit(sel_lstimer) + .rst() + .clear_bit() + .pause() + .clear_bit() + .clk_div() + .bits(divisor) + .duty_res() + .bits(duty) + }), + Number::Timer2 => self.ledc.timer2_conf.modify(|_, w| unsafe { + w.tick_sel() + .bit(sel_lstimer) + .rst() + .clear_bit() + .pause() + .clear_bit() + .clk_div() + .bits(divisor) + .duty_res() + .bits(duty) + }), + Number::Timer3 => self.ledc.timer3_conf.modify(|_, w| unsafe { + w.tick_sel() + .bit(sel_lstimer) + .rst() + .clear_bit() + .pause() + .clear_bit() + .clk_div() + .bits(divisor) + .duty_res() + .bits(duty) + }), + }; + } + + /// Update the timer in HW + fn update_hw(&self) { + match self.number { + Number::Timer0 => self.ledc.timer0_conf.modify(|_, w| w.para_up().set_bit()), + Number::Timer1 => self.ledc.timer1_conf.modify(|_, w| w.para_up().set_bit()), + Number::Timer2 => self.ledc.timer2_conf.modify(|_, w| w.para_up().set_bit()), + Number::Timer3 => self.ledc.timer3_conf.modify(|_, w| w.para_up().set_bit()), + }; + } +} diff --git a/esp-hal-common/src/lib.rs b/esp-hal-common/src/lib.rs index 44631d7dc..30d4b8443 100644 --- a/esp-hal-common/src/lib.rs +++ b/esp-hal-common/src/lib.rs @@ -40,6 +40,11 @@ pub mod i2c; #[cfg_attr(target_arch = "riscv32", path = "interrupt/riscv.rs")] #[cfg_attr(target_arch = "xtensa", path = "interrupt/xtensa.rs")] pub mod interrupt; +#[cfg_attr(feature = "esp32", path = "ledc/esp32/mod.rs")] +#[cfg_attr(feature = "esp32c3", path = "ledc/others/mod.rs")] +#[cfg_attr(feature = "esp32s2", path = "ledc/others/mod.rs")] +#[cfg_attr(feature = "esp32s3", path = "ledc/others/mod.rs")] +pub mod ledc; pub mod prelude; pub mod pulse_control; pub mod rng; diff --git a/esp-hal-common/src/prelude.rs b/esp-hal-common/src/prelude.rs index 7d29d73ef..98c64b4fb 100644 --- a/esp-hal-common/src/prelude.rs +++ b/esp-hal-common/src/prelude.rs @@ -15,6 +15,8 @@ pub use embedded_hal::{ pub use fugit::{ ExtU32 as _fugit_ExtU32, ExtU64 as _fugit_ExtU64, + HertzU32 as _fugit_HertzU32, + MegahertzU32 as _fugit_MegahertzU32, RateExtU32 as _fugit_RateExtU32, RateExtU64 as _fugit_RateExtU64, }; diff --git a/esp-hal-common/src/system.rs b/esp-hal-common/src/system.rs index fdb92f473..e8bc363e6 100644 --- a/esp-hal-common/src/system.rs +++ b/esp-hal-common/src/system.rs @@ -21,6 +21,7 @@ pub enum Peripheral { #[cfg(not(feature = "esp32c3"))] I2cExt1, Rmt, + Ledc, #[cfg(feature = "esp32c3")] ApbSarAdc, } @@ -68,6 +69,10 @@ impl PeripheralClockControl { perip_clk_en0.modify(|_, w| w.rmt_clk_en().set_bit()); perip_rst_en0.modify(|_, w| w.rmt_rst().clear_bit()); } + Peripheral::Ledc => { + perip_clk_en0.modify(|_, w| w.ledc_clk_en().set_bit()); + perip_rst_en0.modify(|_, w| w.ledc_rst().clear_bit()); + } #[cfg(feature = "esp32c3")] Peripheral::ApbSarAdc => { perip_clk_en0.modify(|_, w| w.apb_saradc_clk_en().set_bit()); diff --git a/esp32-hal/Cargo.toml b/esp32-hal/Cargo.toml index 212713fc1..b49989f50 100644 --- a/esp32-hal/Cargo.toml +++ b/esp32-hal/Cargo.toml @@ -53,4 +53,4 @@ vectored = ["esp-hal-common/vectored"] [[example]] name = "hello_rgb" -required-features = ["smartled"] +required-features = ["smartled"] \ No newline at end of file diff --git a/esp32-hal/examples/ledc.rs b/esp32-hal/examples/ledc.rs new file mode 100644 index 000000000..f58635ab7 --- /dev/null +++ b/esp32-hal/examples/ledc.rs @@ -0,0 +1,67 @@ +//! Turns on LED with the option to change LED intensity depending on `duty` value. +//! +//! This assumes that a LED is connected to the pin assigned to `led`. (GPIO4) + +#![no_std] +#![no_main] + +use core::fmt::Write; +use esp32_hal::{ + gpio::IO, + Serial, + timer::TimerGroup, + RtcCntl, + clock::ClockControl, + prelude::*, + pac::Peripherals, +}; +use esp_hal_common::ledc::{ + channel::{self, ChannelIFace}, + timer::{self, TimerIFace}, + HighSpeed, LEDC, +}; +use panic_halt as _; +use xtensa_lx_rt::entry; + +#[entry] +fn main() -> ! { + let peripherals = Peripherals::take().unwrap(); + let mut system = peripherals.DPORT.split(); + let clocks = ClockControl::boot_defaults(system.clock_control).freeze(); + + let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks); + let mut wdt = timer_group0.wdt; + let mut serial0 = Serial::new(peripherals.UART0); + let mut rtc_cntl = RtcCntl::new(peripherals.RTC_CNTL); + + // Disable watchdog timer + wdt.disable(); + rtc_cntl.set_wdt_global_enable(false); + + let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); + let led = io.pins.gpio4.into_push_pull_output(); + + writeln!(serial0, "\nESP32 Started\n\n").unwrap(); + + let ledc = LEDC::new(&clocks, &mut system.peripheral_clock_control); + + let mut hstimer0 = ledc.get_timer::(timer::Number::Timer0); + + hstimer0 + .configure(timer::config::Config { + duty: timer::config::Duty::Duty5Bit, + clock_source: timer::HSClockSource::APBClk, + frequency: 24u32.kHz(), + }) + .unwrap(); + + let mut channel0 = ledc.get_channel(channel::Number::Channel0, led); + channel0 + .configure(channel::config::Config { + timer: &hstimer0, + duty_pct: 0.1, + }) + .unwrap(); + + loop {} +} diff --git a/esp32-hal/src/lib.rs b/esp32-hal/src/lib.rs index c8101b909..27c99dbc6 100644 --- a/esp32-hal/src/lib.rs +++ b/esp32-hal/src/lib.rs @@ -8,6 +8,7 @@ pub use esp_hal_common::{ gpio as gpio_types, i2c, interrupt, + ledc, macros, pac, prelude, @@ -90,4 +91,4 @@ fn gpio_intr_enable(int_enable: bool, nmi_enable: bool) -> u8 { | ((nmi_enable as u8) << 1) | (int_enable as u8) << 2 | ((nmi_enable as u8) << 3) -} +} \ No newline at end of file diff --git a/esp32c3-hal/examples/ledc.rs b/esp32c3-hal/examples/ledc.rs new file mode 100644 index 000000000..6c97f5419 --- /dev/null +++ b/esp32c3-hal/examples/ledc.rs @@ -0,0 +1,75 @@ +//! Turns on LED with the option to change LED intensity depending on `duty` +//! value. +//! +//! This assumes that a LED is connected to the pin assigned to `led`. (GPIO4) + +#![no_std] +#![no_main] + +use esp32c3_hal::{ + clock::ClockControl, + gpio::IO, + pac::Peripherals, + prelude::*, + timer::TimerGroup, + RtcCntl, +}; +use esp_hal_common::ledc::{ + channel::{self, ChannelIFace}, + timer::{self, TimerIFace}, + LSGlobalClkSource, + LowSpeed, + LEDC, +}; +use esp_println; +use panic_halt as _; +use riscv_rt::entry; + +#[entry] +fn main() -> ! { + let peripherals = Peripherals::take().unwrap(); + let mut system = peripherals.SYSTEM.split(); + let clocks = ClockControl::boot_defaults(system.clock_control).freeze(); + + let mut rtc_cntl = RtcCntl::new(peripherals.RTC_CNTL); + let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks); + let _timer0 = timer_group0.timer0; + let mut wdt0 = timer_group0.wdt; + let timer_group1 = TimerGroup::new(peripherals.TIMG1, &clocks); + let mut wdt1 = timer_group1.wdt; + + // Disable watchdog timers + rtc_cntl.set_super_wdt_enable(false); + rtc_cntl.set_wdt_global_enable(false); + wdt0.disable(); + wdt1.disable(); + + let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); + let led = io.pins.gpio4.into_push_pull_output(); + + esp_println::println!("\nESP32C3 Started\n\n"); + + let mut ledc = LEDC::new(&clocks, &mut system.peripheral_clock_control); + + ledc.set_global_slow_clock(LSGlobalClkSource::APBClk); + + let mut lstimer0 = ledc.get_timer::(timer::Number::Timer2); + + lstimer0 + .configure(timer::config::Config { + duty: timer::config::Duty::Duty5Bit, + clock_source: timer::LSClockSource::APBClk, + frequency: 24u32.kHz(), + }) + .unwrap(); + + let mut channel0 = ledc.get_channel(channel::Number::Channel0, led); + channel0 + .configure(channel::config::Config { + timer: &lstimer0, + duty_pct: 0.1, + }) + .unwrap(); + + loop {} +} diff --git a/esp32c3-hal/src/lib.rs b/esp32c3-hal/src/lib.rs index 8824d7ad6..679cd2484 100644 --- a/esp32c3-hal/src/lib.rs +++ b/esp32c3-hal/src/lib.rs @@ -9,6 +9,7 @@ pub use esp_hal_common::{ gpio as gpio_types, i2c, interrupt, + ledc, macros, pac, prelude, diff --git a/esp32s2-hal/examples/ledc.rs b/esp32s2-hal/examples/ledc.rs new file mode 100644 index 000000000..fbd451d3f --- /dev/null +++ b/esp32s2-hal/examples/ledc.rs @@ -0,0 +1,73 @@ +//! Turns on LED with the option to change LED intensity depending on `duty` +//! value. +//! +//! This assumes that a LED is connected to the pin assigned to `led`. (GPIO4) + +#![no_std] +#![no_main] + +use esp32s2_hal::{ + clock::ClockControl, + gpio::IO, + pac::Peripherals, + prelude::*, + timer::TimerGroup, + RtcCntl, + Serial, +}; +use esp_hal_common::ledc::{ + channel::{self, ChannelIFace}, + timer::{self, TimerIFace}, + LSGlobalClkSource, + LowSpeed, + LEDC, +}; +use esp_println; +use panic_halt as _; +use xtensa_lx_rt::entry; + +#[entry] +fn main() -> ! { + let peripherals = Peripherals::take().unwrap(); + let mut system = peripherals.SYSTEM.split(); + let clocks = ClockControl::boot_defaults(system.clock_control).freeze(); + + let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks); + let _timer0 = timer_group0.timer0; + let mut wdt = timer_group0.wdt; + let _serial0 = Serial::new(peripherals.UART0); + let mut rtc_cntl = RtcCntl::new(peripherals.RTC_CNTL); + + // Disable watchdog timer + wdt.disable(); + rtc_cntl.set_wdt_global_enable(false); + + let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); + let led = io.pins.gpio4.into_push_pull_output(); + + esp_println::println!("\nESP32S2 Started\n\n"); + + let mut ledc = LEDC::new(&clocks, &mut system.peripheral_clock_control); + + ledc.set_global_slow_clock(LSGlobalClkSource::APBClk); + + let mut lstimer0 = ledc.get_timer::(timer::Number::Timer2); + + lstimer0 + .configure(timer::config::Config { + duty: timer::config::Duty::Duty5Bit, + clock_source: timer::LSClockSource::APBClk, + frequency: 24u32.kHz(), + }) + .unwrap(); + + let mut channel0 = ledc.get_channel(channel::Number::Channel0, led); + channel0 + .configure(channel::config::Config { + timer: &lstimer0, + duty_pct: 0.1, + }) + .unwrap(); + + loop {} +} diff --git a/esp32s3-hal/examples/ledc.rs b/esp32s3-hal/examples/ledc.rs new file mode 100644 index 000000000..a650d5eef --- /dev/null +++ b/esp32s3-hal/examples/ledc.rs @@ -0,0 +1,73 @@ +//! Turns on LED with the option to change LED intensity depending on `duty` +//! value. +//! +//! This assumes that a LED is connected to the pin assigned to `led`. (GPIO4) + +#![no_std] +#![no_main] + +use esp32s3_hal::{ + clock::ClockControl, + gpio::IO, + pac::Peripherals, + prelude::*, + timer::TimerGroup, + RtcCntl, + Serial, +}; +use esp_hal_common::ledc::{ + channel::{self, ChannelIFace}, + timer::{self, TimerIFace}, + LSGlobalClkSource, + LowSpeed, + LEDC, +}; +use esp_println; +use panic_halt as _; +use xtensa_lx_rt::entry; + +#[entry] +fn main() -> ! { + let peripherals = Peripherals::take().unwrap(); + let mut system = peripherals.SYSTEM.split(); + let clocks = ClockControl::boot_defaults(system.clock_control).freeze(); + + let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks); + let _timer0 = timer_group0.timer0; + let mut wdt = timer_group0.wdt; + let mut _serial0 = Serial::new(peripherals.UART0); + let mut rtc_cntl = RtcCntl::new(peripherals.RTC_CNTL); + + // Disable watchdog timer + wdt.disable(); + rtc_cntl.set_wdt_global_enable(false); + + let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); + let led = io.pins.gpio4.into_push_pull_output(); + + esp_println::println!("\nESP32S3 Started\n\n"); + + let mut ledc = LEDC::new(&clocks, &mut system.peripheral_clock_control); + + ledc.set_global_slow_clock(LSGlobalClkSource::APBClk); + + let mut lstimer0 = ledc.get_timer::(timer::Number::Timer2); + + lstimer0 + .configure(timer::config::Config { + duty: timer::config::Duty::Duty5Bit, + clock_source: timer::LSClockSource::APBClk, + frequency: 24u32.kHz(), + }) + .unwrap(); + + let mut channel0 = ledc.get_channel(channel::Number::Channel0, led); + channel0 + .configure(channel::config::Config { + timer: &lstimer0, + duty_pct: 0.1, + }) + .unwrap(); + + loop {} +}