From 8fc7e6d35d02f9158d3e4985cabcab5f428cd71b Mon Sep 17 00:00:00 2001 From: Christian Meusel Date: Sat, 5 Feb 2022 17:57:46 +0100 Subject: [PATCH] Add basic driver for LEDC (#43) * Add basic LEDC driver This driver allows to configure timers and channels but does not provide advanced features like fading * Added missing impl_channels. * Use non-raw types for LEDC hw "handle" markers This makes Timer and Channel Sync again. * Add missing config and deps for dev building examples * Add simple example for LEDC driver * Channel changed to hold Timer as Borrow<>, to allow Arc etc. * Provide unstable options from config.toml This allows to build and run examples of this crate with cargo-espflash 1.2.0 which does not support passing unstable options. * Provide ESP_IDF_SDKCONFIG_DEFAULTS from config.toml This requires only to specify the target for a build explicitly. * Use main task stack size from rust-esp32-std-demo * Add multi-threaded example for LEDC * Clean up ledc example and formatting * Add note on running examples from this repository * Fix build for ULP HAL This LEDC interface is not available there. * Build examples in CI too * Use core types for no_std build of ledc * Init channel config and hpoint with Default This makes the initialization compatible with ESP IDF 4.3 and 4.4 where the new field 'flag' has been added. As the default for hpoint was previously zero, omitting it allows to use ..Default::default() in the struct initialization in both cases. * Install ldproxy in CI for building examples It gets installed just before building examples because this takes its time and should not delay other builds. Co-authored-by: yunta --- .cargo/config.toml | 22 ++ .github/configs/sdkconfig.defaults | 4 + .github/workflows/ci.yml | 7 + Cargo.toml | 6 + README.md | 7 + build.rs | 3 +- examples/ledc-simple.rs | 30 ++ examples/ledc-threads.rs | 76 ++++ src/ledc.rs | 535 +++++++++++++++++++++++++++++ src/lib.rs | 1 + src/peripherals.rs | 6 + 11 files changed, 696 insertions(+), 1 deletion(-) create mode 100644 .cargo/config.toml create mode 100644 examples/ledc-simple.rs create mode 100644 examples/ledc-threads.rs create mode 100644 src/ledc.rs diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 000000000..f6ec1ef65 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,22 @@ +[target.xtensa-esp32-espidf] +linker = "ldproxy" + +[target.xtensa-esp32s2-espidf] +linker = "ldproxy" + +[target.xtensa-esp32s3-espidf] +linker = "ldproxy" + +[target.riscv32imc-esp-espidf] +linker = "ldproxy" + +# Future - necessary for the experimental "native build" of esp-idf-sys with ESP32C3 +# See also https://github.com/ivmarkov/embuild/issues/16 +rustflags = ["-C", "default-linker-libraries"] + +[env] +ESP_IDF_SDKCONFIG_DEFAULTS = ".github/configs/sdkconfig.defaults" + +[unstable] +build-std = ["std", "panic_abort"] +build-std-features = ["panic_immediate_abort"] diff --git a/.github/configs/sdkconfig.defaults b/.github/configs/sdkconfig.defaults index 93047f124..c95a6f766 100644 --- a/.github/configs/sdkconfig.defaults +++ b/.github/configs/sdkconfig.defaults @@ -1,3 +1,7 @@ # Workaround for https://github.com/espressif/esp-idf/issues/7631 CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=n CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=n + +# Some examples (ledc-simple) require a larger than the default stack size for +# the main thread. +CONFIG_ESP_MAIN_TASK_STACK_SIZE=7000 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ae83dad71..8b112bc3f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,3 +34,10 @@ jobs: run: cargo build --features riscv-ulp-hal --no-default-features --target riscv32imc-unknown-none-elf -Zbuild-std=core,panic_abort -Zbuild-std-features=panic_immediate_abort - name: Build | Compile Native ESP-IDF V4.4 no_std run: export ESP_IDF_VERSION=release/v4.4; export ESP_IDF_SDKCONFIG_DEFAULTS=$(pwd)/.github/configs/sdkconfig.defaults; cargo build --features esp-idf-sys/native --no-default-features --target riscv32imc-esp-espidf -Zbuild-std=std,panic_abort -Zbuild-std-features=panic_immediate_abort + - name: Setup | ldproxy + uses: actions-rs/install@v0.1 + with: + crate: ldproxy + version: latest + - name: Build | Examples + run: export ESP_IDF_SDKCONFIG_DEFAULTS=$(pwd)/.github/configs/sdkconfig.defaults; cargo build --examples --target riscv32imc-esp-espidf -Zbuild-std=std,panic_abort -Zbuild-std-features=panic_immediate_abort diff --git a/Cargo.toml b/Cargo.toml index d17f7e695..0544918a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,3 +34,9 @@ embassy = { version = "0.1", git = "https://github.com/embassy-rs/embassy", opti [build-dependencies] embuild = "0.28" anyhow = "1" + +[dev-dependencies] +anyhow = "1" +esp-idf-svc = "0.36.2" +esp-idf-sys = { version = "0.30", features = ["pio", "binstart"] } +log = "0.4" diff --git a/README.md b/README.md index ab08f1cb0..5847b74e7 100644 --- a/README.md +++ b/README.md @@ -26,3 +26,10 @@ Please refer to the table below to determine the pins which are not recommended | **ESP32-S3** | 26 - 32, 33 - 37\* | _\* When using Octal Flash and/or Octal PSRAM_ + +## Examples + +The examples could be built and flashed conveniently with [`cargo-espflash`](https://github.com/esp-rs/espflash/). To run `ledc-simple` on an ESP32-C3: +``` +$ cargo espflash --release --target riscv32imc-esp-espidf --example ledc-simple --monitor /dev/ttyUSB0 +``` diff --git a/build.rs b/build.rs index 360bac2df..bc865fd1f 100644 --- a/build.rs +++ b/build.rs @@ -1,6 +1,7 @@ #[cfg(not(feature = "riscv-ulp-hal"))] fn main() -> anyhow::Result<()> { - embuild::build::CfgArgs::output_propagated("ESP_IDF") + embuild::build::CfgArgs::output_propagated("ESP_IDF")?; + embuild::build::LinkArgs::output_propagated("ESP_IDF") } #[cfg(feature = "riscv-ulp-hal")] diff --git a/examples/ledc-simple.rs b/examples/ledc-simple.rs new file mode 100644 index 000000000..0aecdc4b2 --- /dev/null +++ b/examples/ledc-simple.rs @@ -0,0 +1,30 @@ +use embedded_hal::{delay::blocking::DelayUs, pwm::blocking::PwmPin}; +use esp_idf_hal::delay::Ets; +use esp_idf_hal::ledc::{config::TimerConfig, Channel, Timer}; +use esp_idf_hal::peripherals::Peripherals; +use esp_idf_hal::prelude::*; +use log::*; + +fn main() -> anyhow::Result<()> { + esp_idf_sys::link_patches(); + esp_idf_svc::log::EspLogger::initialize_default(); + + info!("Configuring output channel"); + + let peripherals = Peripherals::take().unwrap(); + let config = TimerConfig::default().frequency(25.kHz().into()); + let timer = Timer::new(peripherals.ledc.timer0, &config)?; + let mut channel = Channel::new(peripherals.ledc.channel0, &timer, peripherals.pins.gpio1)?; + + info!("Starting duty-cycle loop"); + + let max_duty = channel.get_max_duty()?; + for numerator in [0, 1, 2, 3, 4, 5].iter().cycle() { + info!("Duty {}/5", numerator); + channel.set_duty(max_duty * numerator / 5)?; + Ets.delay_ms(2000)?; + } + + #[allow(clippy::empty_loop)] + loop {} +} diff --git a/examples/ledc-threads.rs b/examples/ledc-threads.rs new file mode 100644 index 000000000..195e0d99b --- /dev/null +++ b/examples/ledc-threads.rs @@ -0,0 +1,76 @@ +use embedded_hal::pwm::blocking::PwmPin; +use esp_idf_hal::ledc::{config::TimerConfig, Channel, Timer}; +use esp_idf_hal::peripherals::Peripherals; +use esp_idf_hal::prelude::*; +use esp_idf_sys::EspError; +use log::*; +use std::{sync::Arc, time::Duration}; + +const CYCLES: usize = 3; + +fn cycle_duty( + mut pwm: impl PwmPin, + times: usize, + log_prefix: &str, + sleep: Duration, +) -> anyhow::Result<()> { + let max_duty = pwm.get_max_duty()?; + + for cycle in 0..times { + info!("{} cycle: {}", log_prefix, cycle); + + for numerator in [0, 1, 2, 3, 4, 5].iter() { + info!("{} duty: {}/5", log_prefix, numerator); + pwm.set_duty(max_duty * numerator / 5)?; + std::thread::sleep(sleep); + } + } + + Ok(()) +} + +fn main() -> anyhow::Result<()> { + esp_idf_sys::link_patches(); + esp_idf_svc::log::EspLogger::initialize_default(); + + info!("Setting up PWM output channels"); + + let mut peripherals = Peripherals::take().unwrap(); + let config = TimerConfig::default().frequency(25.kHz().into()); + let timer = Arc::new(Timer::new(peripherals.ledc.timer0, &config)?); + let timer0 = timer.clone(); + let timer1 = timer.clone(); + let channel0 = Channel::new(peripherals.ledc.channel0, timer0, peripherals.pins.gpio1)?; + let channel1 = Channel::new(peripherals.ledc.channel1, timer1, peripherals.pins.gpio2)?; + + info!("Spawning PWM threads"); + + let thread0 = std::thread::Builder::new() + .stack_size(7000) + .spawn(move || cycle_duty(channel0, CYCLES, "PWM 0", Duration::from_millis(1000)))?; + let thread1 = std::thread::Builder::new() + .stack_size(7000) + .spawn(move || cycle_duty(channel1, CYCLES, "PWM 1", Duration::from_millis(1750)))?; + + info!("Waiting for PWM threads"); + + thread0.join().unwrap()?; + thread1.join().unwrap()?; + + info!("Joined PWM threads"); + + if let Ok(timer) = Arc::try_unwrap(timer) { + info!("Unwrapped timer"); + if let Ok(hw_timer) = timer.release() { + info!("Recovered HW timer"); + peripherals.ledc.timer0 = hw_timer; + } + } + + info!("Done"); + + loop { + // Don't let the idle task starve and trigger warnings from the watchdog. + std::thread::sleep(Duration::from_millis(100)); + } +} diff --git a/src/ledc.rs b/src/ledc.rs new file mode 100644 index 000000000..fea3e4851 --- /dev/null +++ b/src/ledc.rs @@ -0,0 +1,535 @@ +#![cfg(not(feature = "riscv-ulp-hal"))] + +//! LED Control peripheral (which also crates PWM signals for other purposes) +//! +//! Interface to the [LED Control (LEDC) +//! peripheral](https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/ledc.html) +//! +//! This is an initial implementation supporting the generation of PWM signals +//! but no chrome and spoilers like fading. +//! +//! # Examples +//! +//! Create a 25 kHz PWM signal with 75 % duty cycle on GPIO 1 +//! ``` +//! use embedded_hal::pwm::blocking::PwmPin; +//! use esp_idf_hal::ledc::{config::TimerConfig, Channel, Timer}; +//! use esp_idf_hal::peripherals::Peripherals; +//! use esp_idf_hal::prelude::*; +//! +//! let peripherals = Peripherals::take().unwrap(); +//! let config = TimerConfig::default().frequency(25.kHz().into()); +//! let timer = Timer::new(peripherals.ledc.timer0, &config)?; +//! let channel = Channel::new(peripherals.ledc.channel0, &timer, peripherals.pins.gpio1)?; +//! +//! let max_duty = channel.get_max_duty()?; +//! channel.set_duty(max_duty * 3 / 4); +//! ``` +//! +//! See the `examples/` folder of this repository for more. + +use core::{borrow::Borrow, marker::PhantomData}; + +use crate::gpio::OutputPin; +use crate::mutex::Mutex; +use embedded_hal::pwm::blocking::PwmPin; +use esp_idf_sys::*; + +pub use chip::*; + +type Duty = u32; + +const IDLE_LEVEL: u32 = 0; + +static FADE_FUNC_INSTALLED: Mutex = Mutex::new(false); + +/// Types for configuring the LED Control peripheral +pub mod config { + use super::*; + use crate::units::*; + + pub use chip::Resolution; + + pub struct TimerConfig { + pub frequency: Hertz, + pub resolution: Resolution, + pub speed_mode: ledc_mode_t, + } + + impl TimerConfig { + #[must_use] + pub fn frequency(mut self, f: Hertz) -> Self { + self.frequency = f; + self + } + + #[must_use] + pub fn resolution(mut self, r: Resolution) -> Self { + self.resolution = r; + self + } + + #[must_use] + pub fn speed_mode(mut self, mode: ledc_mode_t) -> Self { + self.speed_mode = mode; + self + } + } + + impl Default for TimerConfig { + fn default() -> Self { + TimerConfig { + frequency: 1000.Hz(), + resolution: Resolution::Bits8, + speed_mode: ledc_mode_t_LEDC_LOW_SPEED_MODE, + } + } + } +} + +/// LED Control timer abstraction +pub struct Timer { + instance: T, + speed_mode: ledc_mode_t, + max_duty: Duty, +} + +impl Timer { + /// Creates a new LED Control timer abstraction + pub fn new(instance: T, config: &config::TimerConfig) -> Result { + let timer_config = ledc_timer_config_t { + speed_mode: config.speed_mode, + timer_num: T::timer(), + __bindgen_anon_1: ledc_timer_config_t__bindgen_ty_1 { + duty_resolution: config.resolution.timer_bits(), + }, + freq_hz: config.frequency.into(), + clk_cfg: ledc_clk_cfg_t_LEDC_AUTO_CLK, + }; + + // SAFETY: We own the instance and therefor are safe to configure it. + esp!(unsafe { ledc_timer_config(&timer_config) })?; + + Ok(Timer { + instance, + speed_mode: config.speed_mode, + max_duty: config.resolution.max_duty(), + }) + } + + /// Pauses the timer. Operation can be resumed with + /// [`resume()`](Timer::resume()). + pub fn pause(&mut self) -> Result<(), EspError> { + esp!(unsafe { ledc_timer_pause(self.speed_mode, T::timer()) })?; + Ok(()) + } + + /// Stops the timer and releases its hardware resource + pub fn release(mut self) -> Result { + self.reset()?; + Ok(self.instance) + } + + fn reset(&mut self) -> Result<(), EspError> { + esp!(unsafe { ledc_timer_rst(self.speed_mode, T::timer()) })?; + Ok(()) + } + + /// Resumes the operation of a previously paused timer + pub fn resume(&mut self) -> Result<(), EspError> { + esp!(unsafe { ledc_timer_resume(self.speed_mode, T::timer()) })?; + Ok(()) + } +} + +/// LED Control output channel abstraction +pub struct Channel>, P: OutputPin> { + instance: C, + _hw_timer: PhantomData, + timer: T, + pin: P, + duty: Duty, +} + +// TODO: Stop channel when the instance gets dropped. It seems that we can't +// have both at the same time: a method for releasing its hardware resources +// and implementing Drop. +impl>, P: OutputPin> Channel { + /// Creates a new LED Control output channel abstraction + pub fn new(instance: C, timer: T, pin: P) -> Result { + let duty = 0; + let channel_config = ledc_channel_config_t { + speed_mode: timer.borrow().speed_mode, + channel: C::channel(), + timer_sel: H::timer(), + intr_type: ledc_intr_type_t_LEDC_INTR_DISABLE, + gpio_num: pin.pin(), + duty: duty as u32, + ..Default::default() + }; + + let mut installed = FADE_FUNC_INSTALLED.lock(); + if !*installed { + // It looks like ledc_channel_config requires the face function to + // be installed. I don't see why this is nescessary yet but hey, + // let the Wookie win for now. + // + // TODO: Check whether it's worth to track its install status and + // remove it if no longer needed. + esp!(unsafe { ledc_fade_func_install(0) })?; + *installed = true; + } + drop(installed); + + // SAFETY: As long as we have borrowed the timer, we are safe to use + // it. + esp!(unsafe { ledc_channel_config(&channel_config) })?; + + Ok(Channel { + instance, + _hw_timer: PhantomData, + timer, + pin, + duty, + }) + } + + /// Stops the output channel and releases its hardware resource and GPIO + /// pin + pub fn release(mut self) -> Result<(C, P), EspError> { + self.stop()?; + Ok((self.instance, self.pin)) + } + + fn get_duty(&self) -> Duty { + self.duty + } + + fn get_max_duty(&self) -> Duty { + self.timer.borrow().max_duty + } + + fn disable(&mut self) -> Result<(), EspError> { + self.update_duty(0)?; + Ok(()) + } + + fn enable(&mut self) -> Result<(), EspError> { + self.update_duty(self.duty)?; + Ok(()) + } + + fn set_duty(&mut self, duty: Duty) -> Result<(), EspError> { + // Clamp the actual duty cycle to the current maximum as done by other + // Pwm/PwmPin implementations. + // + // TODO: Why does calling self.get_max_duty() result in the compiler + // error 'expected `u32`, found enum `Result`' when our method returns + // Duty? + let clamped = duty.min(self.timer.borrow().max_duty); + self.duty = clamped; + self.update_duty(clamped)?; + Ok(()) + } + + fn stop(&mut self) -> Result<(), EspError> { + esp!(unsafe { ledc_stop(self.timer.borrow().speed_mode, C::channel(), IDLE_LEVEL) })?; + Ok(()) + } + + fn update_duty(&mut self, duty: Duty) -> Result<(), EspError> { + esp!(unsafe { + ledc_set_duty_and_update( + self.timer.borrow().speed_mode, + C::channel(), + duty as u32, + Default::default(), + ) + })?; + Ok(()) + } +} + +impl>, P: OutputPin> PwmPin for Channel { + type Duty = Duty; + type Error = EspError; + + fn disable(&mut self) -> Result<(), Self::Error> { + self.disable() + } + + fn enable(&mut self) -> Result<(), Self::Error> { + self.enable() + } + + fn get_duty(&self) -> Result { + Ok(self.get_duty()) + } + + fn get_max_duty(&self) -> Result { + Ok(self.get_max_duty()) + } + + fn set_duty(&mut self, duty: Duty) -> Result<(), Self::Error> { + self.set_duty(duty) + } +} + +impl>, P: OutputPin> embedded_hal_0_2::PwmPin + for Channel +{ + type Duty = Duty; + + fn disable(&mut self) { + if let Err(e) = self.disable() { + panic!("disabling PWM failed: {}", e); + } + } + + fn enable(&mut self) { + if let Err(e) = self.enable() { + panic!("enabling PWM failed: {}", e); + } + } + + fn get_duty(&self) -> Self::Duty { + self.get_duty() + } + + fn get_max_duty(&self) -> Self::Duty { + self.get_max_duty() + } + + fn set_duty(&mut self, duty: Duty) { + if let Err(e) = self.set_duty(duty) { + panic!("updating duty failed: {}", e); + } + } +} + +mod chip { + use core::marker::PhantomData; + use esp_idf_sys::*; + + pub enum Resolution { + Bits1, + Bits2, + Bits3, + Bits4, + Bits5, + Bits6, + Bits7, + Bits8, + Bits9, + Bits10, + Bits11, + Bits12, + Bits13, + Bits14, + #[cfg(esp32)] + Bits15, + #[cfg(esp32)] + Bits16, + #[cfg(esp32)] + Bits17, + #[cfg(esp32)] + Bits18, + #[cfg(esp32)] + Bits19, + #[cfg(esp32)] + Bits20, + } + + impl Resolution { + pub const fn bits(&self) -> usize { + match self { + Resolution::Bits1 => 1, + Resolution::Bits2 => 2, + Resolution::Bits3 => 3, + Resolution::Bits4 => 4, + Resolution::Bits5 => 5, + Resolution::Bits6 => 6, + Resolution::Bits7 => 7, + Resolution::Bits8 => 8, + Resolution::Bits9 => 9, + Resolution::Bits10 => 10, + Resolution::Bits11 => 11, + Resolution::Bits12 => 12, + Resolution::Bits13 => 13, + Resolution::Bits14 => 14, + #[cfg(esp32)] + Resolution::Bits15 => 15, + #[cfg(esp32)] + Resolution::Bits16 => 16, + #[cfg(esp32)] + Resolution::Bits17 => 17, + #[cfg(esp32)] + Resolution::Bits18 => 18, + #[cfg(esp32)] + Resolution::Bits19 => 19, + #[cfg(esp32)] + Resolution::Bits20 => 20, + } + } + + pub const fn max_duty(&self) -> u32 { + (1 << self.bits()) - 1 + } + + pub(crate) const fn timer_bits(&self) -> ledc_timer_bit_t { + match self { + Resolution::Bits1 => ledc_timer_bit_t_LEDC_TIMER_1_BIT, + Resolution::Bits2 => ledc_timer_bit_t_LEDC_TIMER_2_BIT, + Resolution::Bits3 => ledc_timer_bit_t_LEDC_TIMER_3_BIT, + Resolution::Bits4 => ledc_timer_bit_t_LEDC_TIMER_4_BIT, + Resolution::Bits5 => ledc_timer_bit_t_LEDC_TIMER_5_BIT, + Resolution::Bits6 => ledc_timer_bit_t_LEDC_TIMER_6_BIT, + Resolution::Bits7 => ledc_timer_bit_t_LEDC_TIMER_7_BIT, + Resolution::Bits8 => ledc_timer_bit_t_LEDC_TIMER_8_BIT, + Resolution::Bits9 => ledc_timer_bit_t_LEDC_TIMER_9_BIT, + Resolution::Bits10 => ledc_timer_bit_t_LEDC_TIMER_10_BIT, + Resolution::Bits11 => ledc_timer_bit_t_LEDC_TIMER_11_BIT, + Resolution::Bits12 => ledc_timer_bit_t_LEDC_TIMER_12_BIT, + Resolution::Bits13 => ledc_timer_bit_t_LEDC_TIMER_13_BIT, + Resolution::Bits14 => ledc_timer_bit_t_LEDC_TIMER_14_BIT, + #[cfg(esp32)] + Resolution::Bits15 => ledc_timer_bit_t_LEDC_TIMER_15_BIT, + #[cfg(esp32)] + Resolution::Bits16 => ledc_timer_bit_t_LEDC_TIMER_16_BIT, + #[cfg(esp32)] + Resolution::Bits17 => ledc_timer_bit_t_LEDC_TIMER_17_BIT, + #[cfg(esp32)] + Resolution::Bits18 => ledc_timer_bit_t_LEDC_TIMER_18_BIT, + #[cfg(esp32)] + Resolution::Bits19 => ledc_timer_bit_t_LEDC_TIMER_19_BIT, + #[cfg(esp32)] + Resolution::Bits20 => ledc_timer_bit_t_LEDC_TIMER_20_BIT, + } + } + } + + /// LED Control peripheral timer + pub trait HwTimer { + fn timer() -> ledc_timer_t; + } + + /// LED Control peripheral output channel + pub trait HwChannel { + fn channel() -> ledc_channel_t; + } + + macro_rules! impl_timer { + ($instance:ident: $timer:expr) => { + pub struct $instance { + _marker: PhantomData, + } + + impl $instance { + /// # Safety + /// + /// It is safe to instantiate this timer exactly one time. + pub unsafe fn new() -> Self { + $instance { + _marker: PhantomData, + } + } + } + + impl HwTimer for $instance { + fn timer() -> ledc_timer_t { + $timer + } + } + }; + } + + impl_timer!(TIMER0: ledc_timer_t_LEDC_TIMER_0); + impl_timer!(TIMER1: ledc_timer_t_LEDC_TIMER_1); + impl_timer!(TIMER2: ledc_timer_t_LEDC_TIMER_2); + impl_timer!(TIMER3: ledc_timer_t_LEDC_TIMER_3); + + macro_rules! impl_channel { + ($instance:ident: $channel:expr) => { + pub struct $instance { + _marker: PhantomData, + } + + impl $instance { + /// # Safety + /// + /// It is safe to instantiate this output channel exactly one + /// time. + pub unsafe fn new() -> Self { + $instance { + _marker: PhantomData, + } + } + } + + impl HwChannel for $instance { + fn channel() -> ledc_channel_t { + $channel + } + } + }; + } + + impl_channel!(CHANNEL0: ledc_channel_t_LEDC_CHANNEL_0); + impl_channel!(CHANNEL1: ledc_channel_t_LEDC_CHANNEL_1); + impl_channel!(CHANNEL2: ledc_channel_t_LEDC_CHANNEL_2); + impl_channel!(CHANNEL3: ledc_channel_t_LEDC_CHANNEL_3); + impl_channel!(CHANNEL4: ledc_channel_t_LEDC_CHANNEL_4); + impl_channel!(CHANNEL5: ledc_channel_t_LEDC_CHANNEL_5); + #[cfg(any(esp32, esp32s2, esp32s3, esp8684))] + impl_channel!(CHANNEL6: ledc_channel_t_LEDC_CHANNEL_6); + #[cfg(any(esp32, esp32s2, esp32s3, esp8684))] + impl_channel!(CHANNEL7: ledc_channel_t_LEDC_CHANNEL_7); + + /// The LED Control device peripheral + pub struct Peripheral { + pub timer0: TIMER0, + pub timer1: TIMER1, + pub timer2: TIMER2, + pub timer3: TIMER3, + pub channel0: CHANNEL0, + pub channel1: CHANNEL1, + pub channel2: CHANNEL2, + pub channel3: CHANNEL3, + pub channel4: CHANNEL4, + pub channel5: CHANNEL5, + #[cfg(any(esp32, esp32s2, esp32s3, esp8684))] + pub channel6: CHANNEL6, + #[cfg(any(esp32, esp32s2, esp32s3, esp8684))] + pub channel7: CHANNEL7, + } + + impl Peripheral { + /// Creates a new instance of the LEDC peripheral. Typically one wants + /// to use the instance [`ledc`](crate::peripherals::Peripherals::ledc) from + /// the device peripherals obtained via + /// [`peripherals::Peripherals::take()`](crate::peripherals::Peripherals::take()). + /// + /// # Safety + /// + /// It is safe to instantiate the LEDC peripheral exactly one time. + /// Care has to be taken that this has not already been done elsewhere. + pub unsafe fn new() -> Self { + Self { + timer0: TIMER0::new(), + timer1: TIMER1::new(), + timer2: TIMER2::new(), + timer3: TIMER3::new(), + channel0: CHANNEL0::new(), + channel1: CHANNEL1::new(), + channel2: CHANNEL2::new(), + channel3: CHANNEL3::new(), + channel4: CHANNEL4::new(), + channel5: CHANNEL5::new(), + #[cfg(any(esp32, esp32s2, esp32s3, esp8684))] + channel6: CHANNEL6::new(), + #[cfg(any(esp32, esp32s2, esp32s3, esp8684))] + channel7: CHANNEL7::new(), + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 6b4fc136a..e7436dbf3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,6 +34,7 @@ pub mod hall; pub mod i2c; #[cfg(all(feature = "experimental", not(feature = "riscv-ulp-hal")))] pub mod interrupt; +pub mod ledc; #[cfg(not(feature = "riscv-ulp-hal"))] pub mod mutex; pub mod peripherals; diff --git a/src/peripherals.rs b/src/peripherals.rs index da90994ab..2387d3d5b 100644 --- a/src/peripherals.rs +++ b/src/peripherals.rs @@ -13,6 +13,8 @@ use crate::hall; #[cfg(not(feature = "riscv-ulp-hal"))] use crate::i2c; #[cfg(not(feature = "riscv-ulp-hal"))] +use crate::ledc; +#[cfg(not(feature = "riscv-ulp-hal"))] use crate::serial; #[cfg(not(feature = "riscv-ulp-hal"))] use crate::spi; @@ -43,6 +45,8 @@ pub struct Peripherals { pub hall_sensor: hall::HallSensor, #[cfg(not(feature = "riscv-ulp-hal"))] pub can: can::CAN, + #[cfg(not(feature = "riscv-ulp-hal"))] + pub ledc: ledc::Peripheral, #[cfg(all(any(esp32, esp32s2, esp32s3), not(feature = "riscv-ulp-hal")))] pub ulp: ulp::ULP, } @@ -89,6 +93,8 @@ impl Peripherals { hall_sensor: hall::HallSensor::new(), #[cfg(not(feature = "riscv-ulp-hal"))] can: can::CAN::new(), + #[cfg(not(feature = "riscv-ulp-hal"))] + ledc: ledc::Peripheral::new(), #[cfg(all(any(esp32, esp32s2, esp32s3), not(feature = "riscv-ulp-hal")))] ulp: ulp::ULP::new(), }