mirror of
https://github.com/esp-rs/esp-idf-hal.git
synced 2025-09-27 12:21:02 +00:00
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 <maciej.blomberg@mikoton.com>
This commit is contained in:
parent
471dfc435f
commit
8fc7e6d35d
22
.cargo/config.toml
Normal file
22
.cargo/config.toml
Normal file
@ -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"]
|
4
.github/configs/sdkconfig.defaults
vendored
4
.github/configs/sdkconfig.defaults
vendored
@ -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
|
||||
|
7
.github/workflows/ci.yml
vendored
7
.github/workflows/ci.yml
vendored
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
```
|
||||
|
3
build.rs
3
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")]
|
||||
|
30
examples/ledc-simple.rs
Normal file
30
examples/ledc-simple.rs
Normal file
@ -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 {}
|
||||
}
|
76
examples/ledc-threads.rs
Normal file
76
examples/ledc-threads.rs
Normal file
@ -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<Duty = u32, Error = EspError>,
|
||||
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));
|
||||
}
|
||||
}
|
535
src/ledc.rs
Normal file
535
src/ledc.rs
Normal file
@ -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<bool> = 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<T: HwTimer> {
|
||||
instance: T,
|
||||
speed_mode: ledc_mode_t,
|
||||
max_duty: Duty,
|
||||
}
|
||||
|
||||
impl<T: HwTimer> Timer<T> {
|
||||
/// Creates a new LED Control timer abstraction
|
||||
pub fn new(instance: T, config: &config::TimerConfig) -> Result<Self, EspError> {
|
||||
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<T, EspError> {
|
||||
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<C: HwChannel, H: HwTimer, T: Borrow<Timer<H>>, P: OutputPin> {
|
||||
instance: C,
|
||||
_hw_timer: PhantomData<H>,
|
||||
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<C: HwChannel, H: HwTimer, T: Borrow<Timer<H>>, P: OutputPin> Channel<C, H, T, P> {
|
||||
/// Creates a new LED Control output channel abstraction
|
||||
pub fn new(instance: C, timer: T, pin: P) -> Result<Self, EspError> {
|
||||
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<C: HwChannel, H: HwTimer, T: Borrow<Timer<H>>, P: OutputPin> PwmPin for Channel<C, H, T, P> {
|
||||
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<Self::Duty, Self::Error> {
|
||||
Ok(self.get_duty())
|
||||
}
|
||||
|
||||
fn get_max_duty(&self) -> Result<Self::Duty, Self::Error> {
|
||||
Ok(self.get_max_duty())
|
||||
}
|
||||
|
||||
fn set_duty(&mut self, duty: Duty) -> Result<(), Self::Error> {
|
||||
self.set_duty(duty)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: HwChannel, H: HwTimer, T: Borrow<Timer<H>>, P: OutputPin> embedded_hal_0_2::PwmPin
|
||||
for Channel<C, H, T, P>
|
||||
{
|
||||
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<ledc_timer_t>,
|
||||
}
|
||||
|
||||
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<ledc_channel_t>,
|
||||
}
|
||||
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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(),
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user