From a55c9d77ec438570df5546a582163c918d302e1c Mon Sep 17 00:00:00 2001 From: Robert Wiewel Date: Sun, 24 Apr 2022 19:32:50 +0200 Subject: [PATCH] Add RMT output channel support for all current ESP32 variants - Add RMT output channel support for ESP32, ESP32-S2, ESP32-S3, ESP32-C3 - Add add RMT adapter for `smart-leds` crate - Add example `hello_rgb` for ESP32-S2, ESP32-S3 and ESP32-C3 that either drives one LED at the pin where a LED is located on the official devkits - Add example `hello_rgb` for ESP32 that is driving a 12-element RGB ring. --- esp-hal-common/Cargo.toml | 15 +- esp-hal-common/src/gpio/esp32s2.rs | 8 + esp-hal-common/src/gpio/esp32s3.rs | 8 + esp-hal-common/src/lib.rs | 3 + esp-hal-common/src/pulse_control.rs | 1045 +++++++++++++++++ esp-hal-common/src/utils/mod.rs | 12 + .../src/utils/smart_leds_adapter.rs | 202 ++++ esp32-hal/Cargo.toml | 1 + esp32-hal/examples/hello_rgb.rs | 88 ++ esp32-hal/src/lib.rs | 3 + esp32c3-hal/Cargo.toml | 1 + esp32c3-hal/examples/hello_rgb.rs | 89 ++ esp32c3-hal/src/lib.rs | 3 + esp32s2-hal/Cargo.toml | 1 + esp32s2-hal/examples/hello_rgb.rs | 79 ++ esp32s2-hal/src/lib.rs | 3 + esp32s3-hal/Cargo.toml | 1 + esp32s3-hal/examples/hello_rgb.rs | 88 ++ esp32s3-hal/src/lib.rs | 3 + 19 files changed, 1649 insertions(+), 4 deletions(-) create mode 100644 esp-hal-common/src/pulse_control.rs create mode 100644 esp-hal-common/src/utils/mod.rs create mode 100644 esp-hal-common/src/utils/smart_leds_adapter.rs create mode 100644 esp32-hal/examples/hello_rgb.rs create mode 100644 esp32c3-hal/examples/hello_rgb.rs create mode 100644 esp32s2-hal/examples/hello_rgb.rs create mode 100644 esp32s3-hal/examples/hello_rgb.rs diff --git a/esp-hal-common/Cargo.toml b/esp-hal-common/Cargo.toml index ebbb7ac36..ccefe18b4 100644 --- a/esp-hal-common/Cargo.toml +++ b/esp-hal-common/Cargo.toml @@ -29,6 +29,10 @@ xtensa-lx-rt = { version = "0.11", optional = true } # Part of `ufmt` containing only `uWrite` trait ufmt-write = { version = "0.1", optional = true } +# Smart-LED (e.g., WS2812/SK68XX) support +smart-leds-trait = { version = "0.2.1", optional = true } + + # IMPORTANT: # Each supported device MUST have its PAC included below along with a # corresponding feature. We rename the PAC packages because we cannot @@ -39,10 +43,10 @@ esp32s2_pac = { package = "esp32s2", git = "https://github.com/esp-rs/esp-pacs.g esp32s3_pac = { package = "esp32s3", git = "https://github.com/esp-rs/esp-pacs.git", branch = "with_source", optional = true } [features] -esp32 = [ "esp32_pac/rt", "xtensa", "dual_core", "xtensa-lx-rt/esp32", "xtensa-lx/esp32"] -esp32c3 = ["esp32c3_pac/rt", "risc_v", "single_core"] -esp32s2 = ["esp32s2_pac/rt", "xtensa", "single_core", "xtensa-lx-rt/esp32s2", "xtensa-lx/esp32s2"] -esp32s3 = ["esp32s3_pac/rt", "xtensa", "dual_core", "xtensa-lx-rt/esp32s3", "xtensa-lx/esp32s3"] +esp32 = [ "esp32_pac/rt", "xtensa", "dual_core", "xtensa-lx-rt/esp32", "xtensa-lx/esp32", "smartled"] +esp32c3 = ["esp32c3_pac/rt", "risc_v", "single_core", "smartled"] +esp32s2 = ["esp32s2_pac/rt", "xtensa", "single_core", "xtensa-lx-rt/esp32s2", "xtensa-lx/esp32s2", "smartled"] +esp32s3 = ["esp32s3_pac/rt", "xtensa", "dual_core", "xtensa-lx-rt/esp32s3", "xtensa-lx/esp32s3", "smartled"] # Architecture (should not be enabled directly, but instead by a PAC's feature) risc_v = ["riscv", "riscv-atomic-emulation-trap"] @@ -54,3 +58,6 @@ dual_core = [] # To support `ufmt` ufmt = ["ufmt-write"] + +# To use the external `smart_led` crate +smartled = ["smart-leds-trait"] \ No newline at end of file diff --git a/esp-hal-common/src/gpio/esp32s2.rs b/esp-hal-common/src/gpio/esp32s2.rs index e364962db..d14589c47 100644 --- a/esp-hal-common/src/gpio/esp32s2.rs +++ b/esp-hal-common/src/gpio/esp32s2.rs @@ -37,6 +37,10 @@ pub enum InputSignal { SPI3_D = 74, SPI3_HD = 75, SPI3_CS0 = 76, + RMT_SIG_IN0 = 83, + RMT_SIG_IN1 = 84, + RMT_SIG_IN2 = 85, + RMT_SIG_IN3 = 86, I2CEXT1_SCL = 95, I2CEXT1_SDA = 96, FSPICLK = 108, @@ -98,6 +102,10 @@ pub enum OutputSignal { SPI3_CS0 = 76, SPI3_CS1 = 77, SPI3_CS2 = 78, + RMT_SIG_OUT0 = 87, + RMT_SIG_OUT1 = 88, + RMT_SIG_OUT2 = 89, + RMT_SIG_OUT3 = 90, I2CEXT1_SCL = 95, I2CEXT1_SDA = 96, GPIO_SD0 = 100, diff --git a/esp-hal-common/src/gpio/esp32s3.rs b/esp-hal-common/src/gpio/esp32s3.rs index 126973f6f..f7b448744 100644 --- a/esp-hal-common/src/gpio/esp32s3.rs +++ b/esp-hal-common/src/gpio/esp32s3.rs @@ -50,6 +50,10 @@ pub enum InputSignal { SPI3_HD = 69, SPI3_WP = 70, SPI3_CS0 = 71, + RMT_SIG_IN0 = 81, + RMT_SIG_IN1 = 82, + RMT_SIG_IN2 = 83, + RMT_SIG_IN3 = 84, I2CEXT0_SCL = 89, I2CEXT0_SDA = 90, I2CEXT1_SCL = 91, @@ -139,6 +143,10 @@ pub enum OutputSignal { SPI3_WP = 70, SPI3_CS0 = 71, SPI3_CS1 = 72, + RMT_SIG_OUT0 = 81, + RMT_SIG_OUT1 = 82, + RMT_SIG_OUT2 = 83, + RMT_SIG_OUT3 = 84, I2CEXT0_SCL = 89, I2CEXT0_SDA = 90, I2CEXT1_SCL = 91, diff --git a/esp-hal-common/src/lib.rs b/esp-hal-common/src/lib.rs index c35a8397c..3084be0c7 100644 --- a/esp-hal-common/src/lib.rs +++ b/esp-hal-common/src/lib.rs @@ -34,6 +34,7 @@ pub mod i2c; #[cfg_attr(feature = "xtensa", path = "interrupt/xtensa.rs")] pub mod interrupt; pub mod prelude; +pub mod pulse_control; pub mod rng; #[cfg(not(feature = "esp32c3"))] pub mod rtc_cntl; @@ -42,11 +43,13 @@ pub mod spi; pub mod timer; #[cfg(any(feature = "esp32c3", feature = "esp32s3"))] pub mod usb_serial_jtag; +pub mod utils; pub use delay::Delay; pub use gpio::*; pub use interrupt::*; pub use procmacros::ram; +pub use pulse_control::PulseControl; pub use rng::Rng; #[cfg(not(feature = "esp32c3"))] pub use rtc_cntl::RtcCntl; diff --git a/esp-hal-common/src/pulse_control.rs b/esp-hal-common/src/pulse_control.rs new file mode 100644 index 000000000..e5e02800d --- /dev/null +++ b/esp-hal-common/src/pulse_control.rs @@ -0,0 +1,1045 @@ +//! # Remote Control Peripheral (RMT) +//! +//! ### Summary +//! The ESP32 variants include a remote control peripheral (RMT) that +//! is designed to handle infrared remote control signals. For that +//! purpose, it can convert bitstreams of data (from the RAM) into +//! pulse codes and even modulate those codes into a carrier wave. +//! +//! It can also convert received pulse codes (again, with carrier +//! wave support) into data bits. +//! +//! A secondary use case for this peripheral is to drive RGB(W) LEDs +//! that bear an internal IC and use a pulse code protocol. +//! +//! ### Channels +//! The RMT peripheral has the following channels available +//! on individual chips: +//! +//! * The **ESP32** has 8 channels, each of them can be either receiver or +//! transmitter +//! * The **ESP32-C3** has 4 channels, `Channel0` and `Channel1` hardcoded for +//! transmitting signals and `Channel2` and `Channel3` hardcoded for receiving +//! signals. +//! * The **ESP32-S2** has 4 channels, each of them can be either receiver or +//! transmitter. +//! * The **ESP32-S3** has 8 channels, `Channel0`-`Channel3` hardcdoded for +//! transmitting signals and `Channel4`-`Channel7` hardcoded for receiving +//! signals. +//! +//! ### Implementation State +//! * FIFO mode is not supported (there appear to be some issues with FIFO mode +//! in some variants and for consistency all variants therefore we use +//! NON-FIFO mode everywhere) +//! * Non-blocking mode is currently not supported! +//! * Input channels are currently not supported! +//! +//! ### Example (for ESP32-C3) +//! ``` +//! let mut peripherals = pac::Peripherals::take().unwrap(); +//! +//! // Configure RMT peripheral globally +//! let pulse = PulseControl::new( +//! peripherals.RMT, +//! &mut peripherals.SYSTEM, +//! ClockSource::APB, +//! 0, // Integer part of the RMT-wide clock divider +//! 0, // Numerator part of the RMT-wide clock divider +//! 0, // Denominator part of the RMT-wide clock divider +//! ) +//! .unwrap(); +//! +//! // Get reference to channel +//! let mut rmt_channel0 = pulse.channel0; +//! +//! // Set up channel +//! rmt_channel0 +//! .set_idle_output_level(false) +//! .set_carrier_modulation(false) +//! .set_channel_divider(1) +//! .set_idle_output(true); +//! +//! // Assign GPIO pin where pulses should be sent to +//! rmt_channel0.assign_pin(io.pins.gpio8); +//! +//! // Create pulse sequence +//! let mut seq = [PulseCode { +//! level1: true, +//! length1: 1, +//! level2: false, +//! length2: 9, +//! }; 288]; +//! +//! // Send sequence +//! rmt_channel0 +//! .send_pulse_sequence(RepeatMode::SingleShot, &seq) +//! .unwrap(); +//! ``` + +#![deny(missing_docs)] + +use core::slice::Iter; + +use crate::{ + gpio::{types::OutputSignal, OutputPin}, + pac::RMT, +}; + +/// Errors that can occur when the peripheral is configured +#[derive(Debug)] +pub enum SetupError { + /// The global configuration for the RMT peripheral is invalid + /// (e.g. the fractional parameters are outOfBound) + InvalidGlobalConfig, + /// A pin was already assigned to the channel, at this point in + /// time, only one assigned pin per channel is supported + PinAlreadyAssigned, +} + +/// Errors that can occur during a transmission attempt +#[derive(Debug)] +pub enum TransmissionError { + /// Generic Transmission Error + Failure(bool, bool, bool, bool), + /// The maximum number of transmissions (`=(2^10)-1`) was exceeded + RepetitionOverflow, + /// The `RepeatNtimes` and `Forever` modesl are only feasible if the + /// sequence fits into the RAM in one go. If the sequence has > 48 + /// elements, the `RepeatNtimes` and `Forever` modes cannot be used. + IncompatibleRepeatMode, +} + +/// Specifies the mode with which pulses are sent out in transmitter channels +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum RepeatMode { + /// Send sequence once + SingleShot, + /// Send sequence N times (`N < (2^10)`) + #[cfg(not(feature = "esp32"))] + RepeatNtimes(u16), + /// Repeat sequence until stopped by additional function call + Forever, +} + +/// Specify the clock source for the RMT peripheral +#[cfg(any(feature = "esp32c3", feature = "esp32s3"))] +#[derive(Debug, Copy, Clone)] +pub enum ClockSource { + /// Application-level clock + APB = 1, + /// 20 MHz internal oscillator + RTC20M = 2, + /// External clock source + XTAL = 3, +} + +/// Specify the clock source for the RMT peripheral on the ESP32 and ESP32-S3 +/// variants +#[cfg(any(feature = "esp32s2", feature = "esp32"))] +#[derive(Debug, Copy, Clone)] +pub enum ClockSource { + /// Reference Tick (usually configured to 1 us) + RefTick = 0, + /// Application-level clock + APB = 1, +} + +// Specifies how many entries we can store in the RAM section that is allocated +// to the RMT channel +#[cfg(any(feature = "esp32s2", feature = "esp32"))] +const CHANNEL_RAM_SIZE: u8 = 64; +#[cfg(any(feature = "esp32c3", feature = "esp32s3"))] +const CHANNEL_RAM_SIZE: u8 = 48; + +// Specifies where the RMT RAM section starts for the particular ESP32 variant +#[cfg(feature = "esp32s2")] +const RMT_RAM_START: usize = 0x3f416400; +#[cfg(feature = "esp32c3")] +const RMT_RAM_START: usize = 0x60016400; +#[cfg(feature = "esp32")] +const RMT_RAM_START: usize = 0x3ff56800; +#[cfg(feature = "esp32s3")] +const RMT_RAM_START: usize = 0x60016800; + +/// Object representing the state of one pulse code per ESP32-C3 TRM +/// +/// Allows for the assignment of two levels and their lenghts +#[derive(Clone, Copy, Debug)] +pub struct PulseCode { + /// Logical output level in the first pulse code interval + pub level1: bool, + /// Length of the first pulse code interval (in clock cycles) + pub length1: u16, + /// Logical output level in the second pulse code interval + pub level2: bool, + /// Length of the second pulse code interval (in clock cycles) + pub length2: u16, +} + +/// Convert a pulse code structure into a u32 value that can be written +/// into the data registers +impl From for u32 { + #[inline(always)] + fn from(p: PulseCode) -> u32 { + // The Pulse Code format in the RAM appears to be + // little-endian + + // The length1 value resides in bits [14:0] + let mut entry: u32 = p.length1 as u32; + + // If level1 is high, set bit 15, otherwise clear it + if p.level1 { + entry |= 1 << 15; + } else { + entry &= !(1 << 15); + } + + // If level2 is high, set bit 31, otherwise clear it + if p.level2 { + entry |= 1 << 31; + } else { + entry &= !(1 << 31); + } + + // The length2 value resides in bits [30:16] + entry |= (p.length2 as u32) << 16; + + entry + } +} + +#[cfg(feature = "esp32")] +type System = crate::pac::DPORT; +#[cfg(not(feature = "esp32"))] +type System = crate::pac::SYSTEM; + +/// Functionality that every OutputChannel must support +pub trait OutputChannel { + /// Set the logical level that the connected pin is pulled to + /// while the channel is idle + fn set_idle_output_level(&mut self, level: bool) -> &mut Self; + + /// Enable/Disable the output while the channel is idle + fn set_idle_output(&mut self, state: bool) -> &mut Self; + + /// Set channel clock divider value + fn set_channel_divider(&mut self, divider: u8) -> &mut Self; + + /// Enable/Disable carrier modulation + fn set_carrier_modulation(&mut self, state: bool) -> &mut Self; + + /// Set the clock source (for the ESP32-S2 abd ESP32 this can be done on a + /// channel level) + #[cfg(any(feature = "esp32s2", feature = "esp32"))] + fn set_clock_source(&mut self, source: ClockSource) -> &mut Self; + + /// Assign a pin that should be driven by this channel + /// + /// (Note that we only take a reference here, so the ownership remains with + /// the calling entity. The configured pin thus can be re-configured + /// independently.) + fn assign_pin>( + &mut self, + pin: RmtPin, + ) -> &mut Self; + + /// Send a pulse sequence in a blocking fashion + fn send_pulse_sequence( + &mut self, + repeat_mode: RepeatMode, + sequence: &[PulseCode; N], + ) -> Result<(), TransmissionError>; + + /// Send a raw pulse sequence in a blocking fashion + /// + /// In this function we expect the `sequence` elements to be already + /// in the correct u32 format that is understood by the RMT. + /// Please refer to the reference manual or use the variant which + /// accepts `PulseCode` objects instead. + fn send_pulse_sequence_raw( + &mut self, + repeat_mode: RepeatMode, + sequence: &[u32; N], + ) -> Result<(), TransmissionError>; + + /// Stop any ongoing (repetitive) transmission + /// + /// This function needs to be called to stop sending when + /// previously a sequence was sent with `RepeatMode::Forever`. + fn stop_transmission(&self); +} + +macro_rules! channel_instance { + ($num:literal, $cxi:ident, $output_signal:path + ) => { + /// RX/TX Input/Output Channel + pub struct $cxi { + mem_offset: usize, + } + impl $cxi { + /// Create a new channel instance + pub fn new() -> Self { + let mut channel = $cxi { mem_offset: 0 }; + + cfg_if::cfg_if! { + if #[cfg(any(feature = "esp32c3", feature = "esp32s3"))] { + // Apply default configuration + unsafe { &*RMT::ptr() }.ch_tx_conf0[$num].modify(|_, w| unsafe { + // Configure memory block size + w.mem_size() + .bits(1) + // Set config bit + .conf_update() + .set_bit() + // Enable wrap mode (this is enabled globally for + // the ESP32 and ESP32-S2) + .mem_tx_wrap_en() + .set_bit() + }); + } + else { + conf0!($num).modify(|_, w| unsafe { + // Configure memory block size + w.mem_size() + .bits(1) + }); + conf1!($num).modify(|_, w| + // Configure memory block size + w.mem_owner() + .clear_bit() + ); + + } + }; + + #[cfg(feature = "esp32")] + conf0!($num).modify(|_, w| + // Enable clock + w.clk_en() + .set_bit() + // Disable forced power down of the peripheral (just to be sure) + .mem_pd() + .clear_bit() + ); + + channel.set_carrier_modulation(false); + channel.set_idle_output_level(false); + channel.set_idle_output(false); + channel.set_channel_divider(1); + + channel + } + + /// Write a sequence of pulse codes into the RMT fifo buffer + #[inline(always)] + fn write_sequence( + &mut self, + seq_iter: &mut Iter, + max_inserted_elements: u8, + ){ + for _ in 0..max_inserted_elements { + match seq_iter.next() { + None => { + break; + } + Some(pulse) => self.load_fifo(*pulse), + } + } + } + + #[inline(always)] + fn load_fifo(&mut self, value: u32) { + let base_ptr: usize = RMT_RAM_START + ($num * CHANNEL_RAM_SIZE as usize * 4); + let ram_ptr = (base_ptr + self.mem_offset) as *mut u32; + unsafe { + ram_ptr.write_volatile(value); + } + + self.mem_offset += 4; + if self.mem_offset >= CHANNEL_RAM_SIZE as usize * 4 { + self.mem_offset = 0; + } + } + + #[inline(always)] + fn reset_fifo(&mut self) { + self.mem_offset = 0; + } + } + }; +} + +macro_rules! output_channel { + ($num:literal, $cxi:ident, $output_signal:path + ) => { + impl OutputChannel for $cxi { + /// Set the logical level that the connected pin is pulled to + /// while the channel is idle + #[inline(always)] + fn set_idle_output_level(&mut self, level: bool) -> &mut Self { + cfg_if::cfg_if! { + if #[cfg(any(feature = "esp32c3", feature = "esp32s3"))] { + unsafe { &*RMT::ptr() } + .ch_tx_conf0[$num] + .modify(|_, w| w.idle_out_lv().bit(level)); + } + else { + conf1!($num) + .modify(|_, w| w.idle_out_lv().bit(level)); + } + }; + self + } + + /// Enable/Disable the output while the channel is idle + #[inline(always)] + fn set_idle_output(&mut self, state: bool) -> &mut Self { + cfg_if::cfg_if! { + if #[cfg(any(feature = "esp32c3", feature = "esp32s3"))] { + unsafe { &*RMT::ptr() } + .ch_tx_conf0[$num] + .modify(|_, w| w.idle_out_en().bit(state)); + } + else { + conf1!($num) + .modify(|_, w| w.idle_out_en().bit(state)); + } + }; + self + } + + /// Set channel clock divider value + #[inline(always)] + fn set_channel_divider(&mut self, divider: u8) -> &mut Self { + cfg_if::cfg_if! { + if #[cfg(any(feature = "esp32c3", feature = "esp32s3"))] { + unsafe { &*RMT::ptr() } + .ch_tx_conf0[$num] + .modify(|_, w| unsafe { w.div_cnt().bits(divider) }); + } + else { + conf0!($num) + .modify(|_, w| unsafe { w.div_cnt().bits(divider) }); + } + }; + self + } + + /// Enable/Disable carrier modulation + #[inline(always)] + fn set_carrier_modulation(&mut self, state: bool) -> &mut Self { + cfg_if::cfg_if! { + if #[cfg(any(feature = "esp32c3", feature = "esp32s3"))] { + unsafe { &*RMT::ptr() } + .ch_tx_conf0[$num] + .modify(|_, w| w.carrier_en().bit(state)); + } + else { + conf0!($num) + .modify(|_, w| w.carrier_en().bit(state)); + } + }; + self + } + + /// Set the clock source (for the ESP32-S2 and ESP32 this can be done on a + /// channel level) + #[cfg(any(feature = "esp32s2", feature = "esp32"))] + #[inline(always)] + fn set_clock_source(&mut self, source: ClockSource) -> &mut Self { + let bit_value = match source { + ClockSource::RefTick => false, + ClockSource::APB => true, + }; + + conf1!($num) + .modify(|_, w| w.ref_always_on().bit(bit_value)); + self + } + + /// Assign a pin that should be driven by this channel + fn assign_pin>( + &mut self, + mut pin: RmtPin, + ) -> &mut Self { + // Configure Pin as output anc connect to signal + pin.set_to_push_pull_output() + .connect_peripheral_to_output($output_signal); + + self + } + + /// Send a pulse sequence in a blocking fashion + fn send_pulse_sequence( + &mut self, + repeat_mode: RepeatMode, + sequence: &[PulseCode; N], + ) -> Result<(), TransmissionError> { + // Create an internal object with an u32 representation of the pulses + // While this is a memory overhead, we need this for performance reasons (doing the + // conversion while already sending and replacing pulse codes in the fifo is too + // slow) + let precomputed_sequence = sequence.map(|x| u32::from(x)); + + self.send_pulse_sequence_raw(repeat_mode, &precomputed_sequence) + } + + /// Send a raw pulse sequence in a blocking fashion + /// + /// In this function we expect the `sequence` elements to be already + /// in the correct u32 format that is understood by the RMT. + /// Please refer to the reference manual or use the variant which + /// accepts `PulseCode` objects instead. + /// + /// We expect that the end marker is already part of the provided + /// sequence and to be provided in all modes! + fn send_pulse_sequence_raw( + &mut self, + repeat_mode: RepeatMode, + sequence: &[u32; N], + ) -> Result<(), TransmissionError> { + // Check for any configuration error states + match repeat_mode { + #[cfg(not(feature = "esp32"))] + RepeatMode::RepeatNtimes(val) => { + if val >= 1024 { + return Err(TransmissionError::RepetitionOverflow); + } + if sequence.len() > CHANNEL_RAM_SIZE as usize { + return Err(TransmissionError::IncompatibleRepeatMode); + } + } + RepeatMode::Forever => { + if sequence.len() > CHANNEL_RAM_SIZE as usize { + return Err(TransmissionError::IncompatibleRepeatMode); + } + } + _ => (), + }; + + // Depending on the variant, other registers have to be used here + cfg_if::cfg_if! { + if #[cfg(any(feature = "esp32", feature = "esp32s2"))] { + let conf_reg = & conf1!($num); + } else { + let conf_reg = & unsafe{ &*RMT::ptr() }.ch_tx_conf0[$num]; + } + } + + // The ESP32 does not support loop/count modes, as such we have to + // only configure a subset of registers + cfg_if::cfg_if! { + if #[cfg(feature = "esp32")] { + // Configure counting mode and repetitions + unsafe { &*RMT::ptr() }.ch_tx_lim[$num].modify(|_, w| unsafe { + // Set the interrupt threshold for sent pulse codes to + // half the size of the RAM in case we use wrap mode + w.tx_lim() + .bits(CHANNEL_RAM_SIZE as u16 /2) + }); + } else { + // Extract repetition value + let mut reps = 0; + if let RepeatMode::RepeatNtimes(val) = repeat_mode { + reps = val; + } + + // Configure counting mode and repetitions + unsafe { &*RMT::ptr() }.ch_tx_lim[$num].modify(|_, w| unsafe { + // Set number of repetitions + w.tx_loop_num() + .bits(reps) + // Enable loop counting + .tx_loop_cnt_en() + .bit(reps != 0) + // Reset any pre-existing counting value + .loop_count_reset() + .set_bit() + // Set the interrupt threshold for sent pulse codes to 24 + // (= half the size of the RAM) in case we use wrap mode + .tx_lim() + .bits(CHANNEL_RAM_SIZE as u16/2) + }); + } + } + + #[cfg(any(feature = "esp32c3", feature = "esp32s3"))] + conf_reg.modify(|_, w| { + // Set config update bit + w.conf_update().set_bit() + }); + + // Setup configuration + conf_reg.modify(|_, w| { + // Set configure continuous + // (also reset FIFO buffer pointers) + w.tx_conti_mode() + .bit(repeat_mode != RepeatMode::SingleShot) + .mem_rd_rst() + .set_bit() + .apb_mem_rst() + .set_bit() + }); + + self.reset_fifo(); + let mut sequence_iter = sequence.iter(); + + // We have to differentiate here if we can fit the whole sequence + // in the RAM in one go or if we have to use the wrap mode to split + // the sequence into chuncks. + if sequence.len() >= CHANNEL_RAM_SIZE as usize { + // Write the first 48 entries + self.write_sequence(&mut sequence_iter, CHANNEL_RAM_SIZE); + } else { + // Write whole sequence to FIFO RAM + self.write_sequence(&mut sequence_iter, CHANNEL_RAM_SIZE - 1); + } + + // Clear the relevant interrupts + // + // (since this is a write-through register, we can do this + // safely for multiple separate channel instances without + // having concurrency issues) + // Depending on the variant, other registers have to be used here + cfg_if::cfg_if! { + if #[cfg(feature = "esp32")] { + unsafe { &*RMT::ptr() }.int_clr.write(|w| unsafe { + // The ESP32 variant does not have the loop functionality + w.ch_tx_end_int_clr($num) + .set_bit() + .ch_err_int_clr($num) + .set_bit() + .ch_tx_thr_event_int_clr($num) + .set_bit() + }); + } else if #[cfg(feature = "esp32s2")] { + unsafe { &*RMT::ptr() }.int_clr.write(|w| unsafe { + w.ch_tx_end_int_clr($num) + .set_bit() + .ch_tx_loop_int_clr($num) + .set_bit() + .ch_err_int_clr($num) + .set_bit() + .ch_tx_thr_event_int_clr($num) + .set_bit() + }); + } else { + unsafe { &*RMT::ptr() }.int_clr.write(|w| unsafe { + w.ch_tx_end_int_clr($num) + .set_bit() + .ch_tx_loop_int_clr($num) + .set_bit() + .ch_tx_err_int_clr($num) + .set_bit() + .ch_tx_thr_event_int_clr($num) + .set_bit() + }); + } + } + + + // Depending on the variant, other registers have to be used here + cfg_if::cfg_if! { + if #[cfg(any(feature = "esp32", feature = "esp32s2"))] { + conf1!($num).modify(|_, w| w.tx_start().set_bit()); + } else { + unsafe{ &*RMT::ptr() }.ch_tx_conf0[$num].modify(|_, w| w.tx_start().set_bit()); + } + } + + // If we're in forever mode, we return right away, otherwise we wait + // for completion + if repeat_mode != RepeatMode::Forever { + // Wait for interrupt being raised, either completion or error + loop { + let interrupts = unsafe { &*RMT::ptr() }.int_raw.read(); + + match ( + unsafe { interrupts.ch_tx_end_int_raw($num).bit() }, + // The ESP32 variant does not support the loop functionality + #[cfg(not(feature = "esp32"))] + unsafe {interrupts.ch_tx_loop_int_raw($num).bit()}, + #[cfg(feature = "esp32")] + false, + // The C3/S3 have a slightly different interrupt naming scheme + #[cfg(any(feature = "esp32", feature= "esp32s2"))] + unsafe { interrupts.ch_err_int_raw($num).bit() }, + #[cfg(any(feature = "esp32c3", feature= "esp32s3"))] + unsafe { interrupts.ch_tx_err_int_raw($num).bit() }, + unsafe { interrupts.ch_tx_thr_event_int_raw($num).bit() }, + ) { + // SingleShot completed and no error -> success + (true, false, false, _) => break, + // Sequence completed and no error -> success + (false, true, false, _) => { + // Stop transmitting (only necessary in sequence case) + self.stop_transmission(); + break; + } + // Refill the buffer + (false, false, false, true) => { + self.write_sequence(&mut sequence_iter, CHANNEL_RAM_SIZE / 2); + + // Clear the threshold interrupt (write-through) + unsafe { &*RMT::ptr() }.int_clr.write(|w| unsafe { + w.ch_tx_thr_event_int_clr($num).set_bit() + }); + } + // Neither completed nor error -> continue busy waiting + (false, false, false, false) => (), + // Anything else constitutes an error state + _ => { + return Err(TransmissionError::Failure( + unsafe { interrupts.ch_tx_end_int_raw($num).bit() }, + // The ESP32 variant does not support the loop functionality + #[cfg(not(feature = "esp32"))] + unsafe {interrupts.ch_tx_loop_int_raw($num).bit()}, + #[cfg(feature = "esp32")] + false, + // The C3/S3 have a slightly different interrupt naming scheme + #[cfg(any(feature = "esp32", feature= "esp32s2"))] + unsafe { interrupts.ch_err_int_raw($num).bit() }, + #[cfg(any(feature = "esp32c3", feature= "esp32s3"))] + unsafe { interrupts.ch_tx_err_int_raw($num).bit() }, + unsafe { interrupts.ch_tx_thr_event_int_raw($num).bit() }, + )) + } + } + } + } + + Ok(()) + } + + /// Stop any ongoing (repetitive) transmission + /// + /// This function needs to be called to stop sending when + /// previously a sequence was sent with `RepeatMode::Forever`. + fn stop_transmission(&self) { + cfg_if::cfg_if! { + if #[cfg(any(feature = "esp32c3", feature = "esp32s3"))] { + unsafe { &*RMT::ptr() } + .ch_tx_conf0[$num] + .modify(|_, w| w.tx_stop().set_bit()); + } + else if #[cfg(feature = "esp32s2")] { + conf1!($num) + .modify(|_, w| w.tx_stop().set_bit()); + } + // The ESP32 variant does not have any way to stop a + // transmission once it has been started! + }; + } + } + }; +} + +#[cfg(feature = "esp32")] +macro_rules! conf0 { + ($channel: literal) => { + match $channel { + 0 => &unsafe { &*RMT::ptr() }.ch0conf0, + 1 => &unsafe { &*RMT::ptr() }.ch1conf0, + 2 => &unsafe { &*RMT::ptr() }.ch2conf0, + 3 => &unsafe { &*RMT::ptr() }.ch3conf0, + 4 => &unsafe { &*RMT::ptr() }.ch4conf0, + 5 => &unsafe { &*RMT::ptr() }.ch5conf0, + 6 => &unsafe { &*RMT::ptr() }.ch6conf0, + 7 => &unsafe { &*RMT::ptr() }.ch7conf0, + _ => panic!("Attempted access to non-existing channel!"), + } + }; +} + +#[cfg(feature = "esp32")] +macro_rules! conf1 { + ($channel: literal) => { + match $channel { + 0 => &unsafe { &*RMT::ptr() }.ch0conf1, + 1 => &unsafe { &*RMT::ptr() }.ch1conf1, + 2 => &unsafe { &*RMT::ptr() }.ch2conf1, + 3 => &unsafe { &*RMT::ptr() }.ch3conf1, + 4 => &unsafe { &*RMT::ptr() }.ch4conf1, + 5 => &unsafe { &*RMT::ptr() }.ch5conf1, + 6 => &unsafe { &*RMT::ptr() }.ch6conf1, + 7 => &unsafe { &*RMT::ptr() }.ch7conf1, + _ => panic!("Attempted access to non-existing channel!"), + } + }; +} + +#[cfg(feature = "esp32s2")] +macro_rules! conf0 { + ($channel: literal) => { + match $channel { + 0 => &unsafe { &*RMT::ptr() }.ch0conf0, + 1 => &unsafe { &*RMT::ptr() }.ch1conf0, + 2 => &unsafe { &*RMT::ptr() }.ch2conf0, + 3 => &unsafe { &*RMT::ptr() }.ch3conf0, + _ => panic!("Attempted access to non-existing channel!"), + } + }; +} + +#[cfg(feature = "esp32s2")] +macro_rules! conf1 { + ($channel: literal) => { + match $channel { + 0 => &unsafe { &*RMT::ptr() }.ch0conf1, + 1 => &unsafe { &*RMT::ptr() }.ch1conf1, + 2 => &unsafe { &*RMT::ptr() }.ch2conf1, + 3 => &unsafe { &*RMT::ptr() }.ch3conf1, + _ => panic!("Attempted access to non-existing channel!"), + } + }; +} + +macro_rules! rmt { + ( + $global_conf_reg:ident, + $( + ($num:literal, $cxi:ident, $obj_name:ident, $output_signal:path), + )+ + ) + => { + /// RMT peripheral (RMT) + pub struct PulseControl { + /// The underlying register block + reg: RMT, + $( + /// RMT channel $cxi + pub $obj_name: $cxi, + )+ + } + + impl PulseControl { + /// Create a new pulse controller instance + #[cfg(any(feature = "esp32c3", feature = "esp32s3"))] + pub fn new( + instance: RMT, + system: &mut System, + clk_source: ClockSource, + div_abs: u8, + div_frac_a: u8, + div_frac_b: u8, + ) -> Result { + + let pc = PulseControl { + reg: instance, + $( + $obj_name: $cxi::new(), + )+ + }; + + pc.enable_peripheral(system); + pc.config_global(clk_source, div_abs, div_frac_a, div_frac_b)?; + + Ok(pc) + } + + /// Create a new pulse controller instance + #[cfg(any(feature = "esp32", feature = "esp32s2"))] + pub fn new( + instance: RMT, + system: &mut System, + ) -> Result { + + let pc = PulseControl { + reg: instance, + $( + $obj_name: $cxi::new(), + )+ + }; + + pc.enable_peripheral(system); + pc.config_global()?; + + Ok(pc) + } + + /// Return the raw interface to the underlying RMT peripheral + pub fn free(self) -> RMT { + self.reg + } + + // Enable the RMT peripherals clock in the system peripheral + fn enable_peripheral(&self, system: &mut System) { + cfg_if::cfg_if! { + if #[cfg(feature = "esp32")] { + system + .perip_clk_en + .modify(|_, w| w.rmt_clk_en().set_bit()); + system + .perip_rst_en + .modify(|_, w| w.rmt_rst().clear_bit()); + } else { + system + .perip_clk_en0 + .modify(|_, w| w.rmt_clk_en().set_bit()); + system + .perip_rst_en0 + .modify(|_, w| w.rmt_rst().clear_bit()); + } + } + } + + /// Assign the global (peripheral-wide) configuration. This + /// is mostly the divider setup and the clock source selection + /// + /// The dividing factor for the source + /// clock is calculated as follows: + /// + /// divider = absolute_part + 1 + (fractional_part_a / fractional_part_b) + #[cfg(any(feature = "esp32c3", feature = "esp32s3"))] + fn config_global( + &self, + clk_source: ClockSource, + div_abs: u8, + div_frac_a: u8, + div_frac_b: u8, + ) -> Result<(), SetupError> { + // Before assigning, confirm that the fractional parameters for + // the divider are within bounds + if div_frac_a > 64 || div_frac_b > 64 { + return Err(SetupError::InvalidGlobalConfig); + } + + // TODO: Confirm that the selected clock source is enabled in the + // system / rtc_cntl peripheral? Particularly relevant for clock sources + // other than APB_CLK, this needs to be revisited once #24 and $44 have been + // addressed! + + // Configure peripheral + self.reg.sys_conf.modify(|_, w| unsafe { + // Enable clock + w.clk_en() + .set_bit() + // Force Clock on + .mem_clk_force_on() + .set_bit() + // Enable Source clock + .sclk_active() + .set_bit() + // Disable forced power down of the peripheral (just to be sure) + .mem_force_pd() + .clear_bit() + // Disable FIFO mode + .apb_fifo_mask() + .set_bit() + // Select clock source + .sclk_sel() + .bits(clk_source as u8) + // Set absolute part of divider + .sclk_div_num() + .bits(div_abs) + // Set fractional parts of divider to 0 + .sclk_div_a() + .bits(div_frac_a) + .sclk_div_b() + .bits(div_frac_b) + }); + + // Disable all interrupts + self.reg.int_ena.write(|w| unsafe { w.bits(0) }); + + // Clear all interrupts + self.reg.int_clr.write(|w| unsafe { w.bits(0) }); + + Ok(()) + } + + /// Assign the global (peripheral-wide) configuration. + #[cfg(any(feature = "esp32s2", feature = "esp32"))] + fn config_global(&self) -> Result<(), SetupError> { + // TODO: Confirm that the selected clock source is enabled in the + // system / rtc_cntl peripheral? Particularly relevant for clock sources + // other than APB_CLK, this needs to be revisited once #24 and $44 have been + // addressed! + + cfg_if::cfg_if! { + if #[cfg(feature = "esp32")] { + // Configure peripheral + self.reg.apb_conf.modify(|_, w| + // Disable FIFO mode + w.apb_fifo_mask() + .set_bit() + // Enable wrap mode (globally for the ESP32 and ESP32-S2 variants) + .mem_tx_wrap_en() + .set_bit() + ); + } + else { + // Configure peripheral + self.reg.apb_conf.modify(|_, w| + // Enable clock + w.clk_en() + .set_bit() + // Force Clock on + .mem_clk_force_on() + .set_bit() + // Disable forced power down of the peripheral (just to be sure) + .mem_force_pd() + .clear_bit() + // Disable FIFO mode + .apb_fifo_mask() + .set_bit() + // Enable wrap mode (globally for the ESP32 and ESP32-S2 variants) + .mem_tx_wrap_en() + .set_bit() + ); + } + }; + + // Disable all interrupts + self.reg.int_ena.write(|w| unsafe { w.bits(0) }); + + // Clear all interrupts + self.reg.int_clr.write(|w| unsafe { w.bits(0) }); + + Ok(()) + } + } + $( + channel_instance!($num, $cxi, $output_signal); + output_channel!($num, $cxi, $output_signal); + )+ + }; +} + +#[cfg(feature = "esp32c3")] +rmt!( + sys_conf, + (0, Channel0, channel0, OutputSignal::RMT_SIG_0), + (1, Channel1, channel1, OutputSignal::RMT_SIG_1), +); + +#[cfg(feature = "esp32s2")] +rmt!( + apb_conf, + (0, Channel0, channel0, OutputSignal::RMT_SIG_OUT0), + (1, Channel1, channel1, OutputSignal::RMT_SIG_OUT1), + (2, Channel2, channel2, OutputSignal::RMT_SIG_OUT2), + (3, Channel3, channel3, OutputSignal::RMT_SIG_OUT3), +); + +#[cfg(feature = "esp32")] +rmt!( + apb_conf, + (0, Channel0, channel0, OutputSignal::RMT_SIG_0), + (1, Channel1, channel1, OutputSignal::RMT_SIG_1), + (2, Channel2, channel2, OutputSignal::RMT_SIG_2), + (3, Channel3, channel3, OutputSignal::RMT_SIG_3), + (4, Channel4, channel4, OutputSignal::RMT_SIG_4), + (5, Channel5, channel5, OutputSignal::RMT_SIG_5), + (6, Channel6, channel6, OutputSignal::RMT_SIG_6), + (7, Channel7, channel7, OutputSignal::RMT_SIG_7), +); + +#[cfg(feature = "esp32s3")] +rmt!( + sys_conf, + (0, Channel0, channel0, OutputSignal::RMT_SIG_OUT0), + (1, Channel1, channel1, OutputSignal::RMT_SIG_OUT1), + (2, Channel2, channel2, OutputSignal::RMT_SIG_OUT2), + (3, Channel3, channel3, OutputSignal::RMT_SIG_OUT3), +); diff --git a/esp-hal-common/src/utils/mod.rs b/esp-hal-common/src/utils/mod.rs new file mode 100644 index 000000000..433869239 --- /dev/null +++ b/esp-hal-common/src/utils/mod.rs @@ -0,0 +1,12 @@ +//! Helper Utils + +// Only provide adapter when feature is enabled! +#[cfg(feature = "smartled")] +pub mod smart_leds_adapter; +#[cfg(feature = "smartled")] +pub use smart_leds_adapter::SmartLedsAdapter; + +// Re-export the macro that due to the macro_export configuration was already exported +// in the root module (i.e., `esp-hal-common`) +#[cfg(feature = "smartled")] +pub use crate::smartLedAdapter; diff --git a/esp-hal-common/src/utils/smart_leds_adapter.rs b/esp-hal-common/src/utils/smart_leds_adapter.rs new file mode 100644 index 000000000..408ef8a9d --- /dev/null +++ b/esp-hal-common/src/utils/smart_leds_adapter.rs @@ -0,0 +1,202 @@ +//! # Smart-LEDs RMT Adapter +//! +//! This adapter allows for the use of an RMT output channel to easily interact +//! with RGB LEDs and use the convenience functions of the external +//! [`smart-leds`](https://crates.io/crates/smart-leds) crate. +//! +//! _This is a simple implementation where every LED is adressed in an +//! individual RMT operation. This is working perfectly fine in blocking mode, +//! but in case this is used in combination with interrupts that might disturb +//! the sequential sending, an alternative implementation (addressing the LEDs +//! in a sequence in a single RMT send operation) might be required!_ +#![deny(missing_docs)] + +use core::{marker::PhantomData, slice::IterMut}; + +use smart_leds_trait::{SmartLedsWrite, RGB8}; + +#[cfg(any(feature = "esp32", feature = "esp32s2"))] +use crate::pulse_control::ClockSource; +use crate::{ + gpio::{types::OutputSignal, OutputPin}, + pulse_control::{OutputChannel, PulseCode, RepeatMode, TransmissionError}, +}; + +// Specifies what clock frequency we're using for the RMT peripheral (if +// properly configured) +// +// TODO: Factor in clock configuration, this needs to be revisited once #24 and +// #44 have been addressed. +#[cfg(feature = "esp32c3")] +const SOURCE_CLK_FREQ: u32 = 40_000_000; +#[cfg(feature = "esp32s2")] +const SOURCE_CLK_FREQ: u32 = 40_000_000; +#[cfg(feature = "esp32")] +const SOURCE_CLK_FREQ: u32 = 40_000_000; +#[cfg(feature = "esp32s3")] +const SOURCE_CLK_FREQ: u32 = 40_000_000; + +const SK68XX_CODE_PERIOD: u32 = 1200; +const SK68XX_T0H_NS: u32 = 320; +const SK68XX_T0L_NS: u32 = SK68XX_CODE_PERIOD - SK68XX_T0H_NS; +const SK68XX_T1H_NS: u32 = 640; +const SK68XX_T1L_NS: u32 = SK68XX_CODE_PERIOD - SK68XX_T1H_NS; + +const SK68XX_T0H_CYCLES: u16 = ((SK68XX_T0H_NS * (SOURCE_CLK_FREQ / 1_000_000)) / 500) as u16; +const SK68XX_T0L_CYCLES: u16 = ((SK68XX_T0L_NS * (SOURCE_CLK_FREQ / 1_000_000)) / 500) as u16; +const SK68XX_T1H_CYCLES: u16 = ((SK68XX_T1H_NS * (SOURCE_CLK_FREQ / 1_000_000)) / 500) as u16; +const SK68XX_T1L_CYCLES: u16 = ((SK68XX_T1L_NS * (SOURCE_CLK_FREQ / 1_000_000)) / 500) as u16; + +/// All types of errors that can happen during the conversion and transmission +/// of LED commands +#[derive(Debug)] +pub enum LedAdapterError { + /// Raised in the event that the provided data container is not large enough + BufferSizeExceeded, + /// Raised if something goes wrong in the transmission, + TransmissionError(TransmissionError), +} + +/// Macro to generate adapters with an arbitrary buffer size fitting for a +/// specific number of `$buffer_size` LEDs to be addressed. Attempting to use +/// more LEDs that the buffer is configured for will result in an +/// `LedAdapterError:BufferSizeExceeded` error. +#[macro_export] +macro_rules! smartLedAdapter { + ($buffer_size: literal ) => { + // The size we're assigning here is calculated as following + // ( + // Nr. of LEDs + // * channels (r,g,b -> 3) + // * pulses per channel 8) + // ) + 1 additional pulse for the end delimiter + SmartLedsAdapter::<_, _, { $buffer_size * 24 + 1 }> + }; +} + +/// Adapter taking an RMT channel and a specific pin and providing RGB LED +/// interaction functionality using the `smart-leds` crate +pub struct SmartLedsAdapter { + channel: CHANNEL, + rmt_buffer: [u32; BUFFER_SIZE], + _pin: PhantomData, +} + +impl SmartLedsAdapter +where + CHANNEL: OutputChannel, + PIN: OutputPin, +{ + /// Create a new adapter object that drives the pin using the RMT channel. + pub fn new(mut channel: CHANNEL, pin: PIN) -> SmartLedsAdapter { + #[cfg(any(feature = "esp32c3", feature = "esp32s3"))] + channel + .set_idle_output_level(false) + .set_carrier_modulation(false) + .set_channel_divider(1) + .set_idle_output(true); + + #[cfg(any(feature = "esp32", feature = "esp32s2"))] + channel + .set_idle_output_level(false) + .set_carrier_modulation(false) + .set_channel_divider(1) + .set_idle_output(true) + .set_clock_source(ClockSource::APB); + + channel.assign_pin(pin); + Self { + channel, + rmt_buffer: [0; BUFFER_SIZE], + _pin: PhantomData, + } + } + + fn convert_rgb_to_pulse( + value: RGB8, + mut_iter: &mut IterMut, + ) -> Result<(), LedAdapterError> { + SmartLedsAdapter::::convert_rgb_channel_to_pulses( + value.g, mut_iter, + )?; + SmartLedsAdapter::::convert_rgb_channel_to_pulses( + value.r, mut_iter, + )?; + SmartLedsAdapter::::convert_rgb_channel_to_pulses( + value.b, mut_iter, + )?; + + Ok(()) + } + + fn convert_rgb_channel_to_pulses( + channel_value: u8, + mut_iter: &mut IterMut, + ) -> Result<(), LedAdapterError> { + for position in [128, 64, 32, 16, 8, 4, 2, 1] { + *mut_iter.next().ok_or(LedAdapterError::BufferSizeExceeded)? = + match channel_value & position { + 0 => PulseCode { + level1: true, + length1: SK68XX_T0H_CYCLES, + level2: false, + length2: SK68XX_T0L_CYCLES, + } + .into(), + _ => PulseCode { + level1: true, + length1: SK68XX_T1H_CYCLES, + level2: false, + length2: SK68XX_T1L_CYCLES, + } + .into(), + } + } + + Ok(()) + } +} + +impl SmartLedsWrite + for SmartLedsAdapter +where + CHANNEL: OutputChannel, + PIN: OutputPin, +{ + type Error = LedAdapterError; + type Color = RGB8; + + /// Convert all RGB8 items of the iterator to the RMT format and + /// add them to internal buffer. Then start a singular RMT operation + /// based on that buffer. + fn write(&mut self, iterator: T) -> Result<(), Self::Error> + where + T: Iterator, + I: Into, + { + // We always start from the beginning of the buffer + let mut seq_iter = self.rmt_buffer.iter_mut(); + + // Add all converted iterator items to the buffer. + // This will result in an `BufferSizeExceeded` error in case + // the iterator provides more elements than the buffer can take. + for item in iterator { + SmartLedsAdapter::::convert_rgb_to_pulse( + item.into(), + &mut seq_iter, + )?; + } + + // Finally, add an end element. + *seq_iter.next().ok_or(LedAdapterError::BufferSizeExceeded)? = 0; + + // Perform the actual RMT operation. We use the u32 values here right away. + match self + .channel + .send_pulse_sequence_raw(RepeatMode::SingleShot, &self.rmt_buffer) + { + Ok(_) => Ok(()), + Err(x) => Err(LedAdapterError::TransmissionError(x)), + } + } +} diff --git a/esp32-hal/Cargo.toml b/esp32-hal/Cargo.toml index c381f1ddd..784a17824 100644 --- a/esp32-hal/Cargo.toml +++ b/esp32-hal/Cargo.toml @@ -39,6 +39,7 @@ features = ["esp32"] embedded-graphics = "0.7" panic-halt = "0.2" ssd1306 = "0.7" +smart-leds = "0.3" [features] default = ["rt"] diff --git a/esp32-hal/examples/hello_rgb.rs b/esp32-hal/examples/hello_rgb.rs new file mode 100644 index 000000000..87de849c0 --- /dev/null +++ b/esp32-hal/examples/hello_rgb.rs @@ -0,0 +1,88 @@ +//! RGB LED Demo +//! +//! This example drives an 12-element RGB ring that is connected to GPIO33 +//! +//! The LEDs in the ring are transitioning though the HSV color spectrum for +//! - Saturation: 255 +//! - Hue: 0 - 255 +//! - Value: 255 +//! +//! For the 12-element RGB ring to work, building the release version is going +//! to be required. + +#![no_std] +#![no_main] + +use esp32_hal::{ + pac, + prelude::*, + utils::{smartLedAdapter, SmartLedsAdapter}, + Delay, + PulseControl, + RtcCntl, + Timer, + IO, +}; +#[allow(unused_imports)] +use panic_halt as _; +use smart_leds::{ + brightness, + gamma, + hsv::{hsv2rgb, Hsv}, + SmartLedsWrite, +}; +use xtensa_lx_rt::entry; + +#[entry] +fn main() -> ! { + let mut peripherals = pac::Peripherals::take().unwrap(); + + let mut rtc_cntl = RtcCntl::new(peripherals.RTC_CNTL); + let mut timer0 = Timer::new(peripherals.TIMG0); + let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); + + // Disable MWDT and RWDT (Watchdog) flash boot protection + timer0.disable(); + rtc_cntl.set_wdt_global_enable(false); + + // Configure RMT peripheral globally + let pulse = PulseControl::new(peripherals.RMT, &mut peripherals.DPORT).unwrap(); + + // We use one of the RMT channels to instantiate a `SmartLedsAdapter` which can + // be used directly with all `smart_led` implementations + // -> We need to use the macro `smartLedAdapter!` with the number of addressed + // LEDs here to initialize the internal LED pulse buffer to the correct + // size! + let mut led = ::new(pulse.channel0, io.pins.gpio33); + + // Initialize the Delay peripheral, and use it to toggle the LED state in a + // loop. + let mut delay = Delay::new(); + + let mut color = Hsv { + hue: 0, + sat: 255, + val: 255, + }; + let mut data; + + loop { + // Iterate over the rainbow! + for hue in 0..=255 { + color.hue = hue; + // Convert from the HSV color space (where we can easily transition from one + // color to the other) to the RGB color space that we can then send to the LED + let rgb_color = hsv2rgb(color); + + // Assign new color to all 12 LEDs + data = [rgb_color; 12]; + + // When sending to the LED, we do a gamma correction first (see smart_leds + // documentation for details) and then limit the brightness to 10 out of 255 so + // that the output it's not too bright. + led.write(brightness(gamma(data.iter().cloned()), 10)) + .unwrap(); + delay.delay_ms(20u8); + } + } +} diff --git a/esp32-hal/src/lib.rs b/esp32-hal/src/lib.rs index be83daedf..0eab8916a 100644 --- a/esp32-hal/src/lib.rs +++ b/esp32-hal/src/lib.rs @@ -6,10 +6,13 @@ pub use esp_hal_common::{ interrupt, pac, prelude, + pulse_control, ram, spi, + utils, Cpu, Delay, + PulseControl, Rng, RtcCntl, Serial, diff --git a/esp32c3-hal/Cargo.toml b/esp32c3-hal/Cargo.toml index 00f82a629..c3d72950b 100644 --- a/esp32c3-hal/Cargo.toml +++ b/esp32c3-hal/Cargo.toml @@ -40,6 +40,7 @@ features = ["esp32c3"] embedded-graphics = "0.7" panic-halt = "0.2" ssd1306 = "0.7" +smart-leds = "0.3" [features] default = ["rt"] diff --git a/esp32c3-hal/examples/hello_rgb.rs b/esp32c3-hal/examples/hello_rgb.rs new file mode 100644 index 000000000..5df4b8eb9 --- /dev/null +++ b/esp32c3-hal/examples/hello_rgb.rs @@ -0,0 +1,89 @@ +//! //! RGB LED Demo +//! +//! This example drives an SK68XX RGB LED that is connected to the GPIO8 pin. +//! A RGB LED is connected to that pin on the ESP32-C3-DevKitM-1 and +//! ESP32-C3-DevKitC-02 boards. +//! +//! The demo will leverage the [`smart_leds`](https://crates.io/crates/smart-leds) +//! crate functionality to circle through the HSV hue color space (with +//! saturation and value both at 255). Additionally, we apply a gamma correction +//! and limit the brightness to 10 (out of 255). +#![no_std] +#![no_main] + +use esp32c3_hal::{ + pac, + prelude::*, + pulse_control::ClockSource, + utils::{smartLedAdapter, SmartLedsAdapter}, + Delay, + PulseControl, + RtcCntl, + Timer, + IO, +}; +#[allow(unused_imports)] +use panic_halt; +use riscv_rt::entry; +use smart_leds::{ + brightness, + gamma, + hsv::{hsv2rgb, Hsv}, + SmartLedsWrite, +}; + +#[entry] +fn main() -> ! { + let mut peripherals = pac::Peripherals::take().unwrap(); + + let mut rtc_cntl = RtcCntl::new(peripherals.RTC_CNTL); + let mut timer0 = Timer::new(peripherals.TIMG0); + let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); + + // Disable watchdogs + rtc_cntl.set_super_wdt_enable(false); + rtc_cntl.set_wdt_enable(false); + timer0.disable(); + + // Configure RMT peripheral globally + let pulse = PulseControl::new( + peripherals.RMT, + &mut peripherals.SYSTEM, + ClockSource::APB, + 0, + 0, + 0, + ) + .unwrap(); + + // We use one of the RMT channels to instantiate a `SmartLedsAdapter` which can + // be used directly with all `smart_led` implementations + let mut led = ::new(pulse.channel0, io.pins.gpio8); + + // Initialize the Delay peripheral, and use it to toggle the LED state in a + // loop. + let mut delay = Delay::new(peripherals.SYSTIMER); + + let mut color = Hsv { + hue: 0, + sat: 255, + val: 255, + }; + let mut data; + + loop { + // Iterate over the rainbow! + for hue in 0..=255 { + color.hue = hue; + // Convert from the HSV color space (where we can easily transition from one + // color to the other) to the RGB color space that we can then send to the LED + data = [hsv2rgb(color)]; + // When sending to the LED, we do a gamma correction first (see smart_leds + // documentation for details) and then limit the brightness to 10 out of 255 so + // that the output it's not too bright. + led.write(brightness(gamma(data.iter().cloned()), 10)) + .unwrap(); + delay.delay_ms(20u8); + } + } +} diff --git a/esp32c3-hal/src/lib.rs b/esp32c3-hal/src/lib.rs index 723946c17..4c770df9e 100644 --- a/esp32c3-hal/src/lib.rs +++ b/esp32c3-hal/src/lib.rs @@ -8,10 +8,13 @@ pub use esp_hal_common::{ interrupt, pac, prelude, + pulse_control, ram, spi, + utils, Cpu, Delay, + PulseControl, Rng, Serial, Timer, diff --git a/esp32s2-hal/Cargo.toml b/esp32s2-hal/Cargo.toml index 1bde0f704..74989681f 100644 --- a/esp32s2-hal/Cargo.toml +++ b/esp32s2-hal/Cargo.toml @@ -39,6 +39,7 @@ features = ["esp32s2"] embedded-graphics = "0.7" panic-halt = "0.2" ssd1306 = "0.7" +smart-leds = "0.3" [features] default = ["rt"] diff --git a/esp32s2-hal/examples/hello_rgb.rs b/esp32s2-hal/examples/hello_rgb.rs new file mode 100644 index 000000000..4aca40082 --- /dev/null +++ b/esp32s2-hal/examples/hello_rgb.rs @@ -0,0 +1,79 @@ +//! RGB LED Demo +//! +//! This example drives an SK68XX RGB LED that is connected to GPIO18. +//! A RGB LED is connected to that pin on the official DevKits. +//! +//! The demo will leverage the [`smart_leds`](https://crates.io/crates/smart-leds) +//! crate functionality to circle through the HSV hue color space (with +//! saturation and value both at 255). Additionally, we apply a gamma correction +//! and limit the brightness to 10 (out of 255). + +#![no_std] +#![no_main] + +use esp32s2_hal::{ + pac, + prelude::*, + utils::{smartLedAdapter, SmartLedsAdapter}, + Delay, + PulseControl, + RtcCntl, + Timer, + IO, +}; +#[allow(unused_imports)] +use panic_halt as _; +use smart_leds::{ + brightness, + gamma, + hsv::{hsv2rgb, Hsv}, + SmartLedsWrite, +}; +use xtensa_lx_rt::entry; + +#[entry] +fn main() -> ! { + let mut peripherals = pac::Peripherals::take().unwrap(); + + let mut rtc_cntl = RtcCntl::new(peripherals.RTC_CNTL); + let mut timer0 = Timer::new(peripherals.TIMG0); + let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); + + // Disable MWDT and RWDT (Watchdog) flash boot protection + timer0.disable(); + rtc_cntl.set_wdt_global_enable(false); + + // Configure RMT peripheral globally + let pulse = PulseControl::new(peripherals.RMT, &mut peripherals.SYSTEM).unwrap(); + + // We use one of the RMT channels to instantiate a `SmartLedsAdapter` which can + // be used directly with all `smart_led` implementations + let mut led = ::new(pulse.channel0, io.pins.gpio18); + + // Initialize the Delay peripheral, and use it to toggle the LED state in a + // loop. + let mut delay = Delay::new(); + + let mut color = Hsv { + hue: 0, + sat: 255, + val: 255, + }; + let mut data; + + loop { + // Iterate over the rainbow! + for hue in 0..=255 { + color.hue = hue; + // Convert from the HSV color space (where we can easily transition from one + // color to the other) to the RGB color space that we can then send to the LED + data = [hsv2rgb(color)]; + // When sending to the LED, we do a gamma correction first (see smart_leds + // documentation for details) and then limit the brightness to 10 out of 255 so + // that the output it's not too bright. + led.write(brightness(gamma(data.iter().cloned()), 10)) + .unwrap(); + delay.delay_ms(20u8); + } + } +} diff --git a/esp32s2-hal/src/lib.rs b/esp32s2-hal/src/lib.rs index 8e2abf39c..676425a84 100644 --- a/esp32s2-hal/src/lib.rs +++ b/esp32s2-hal/src/lib.rs @@ -6,10 +6,13 @@ pub use esp_hal_common::{ interrupt, pac, prelude, + pulse_control, ram, spi, + utils, Cpu, Delay, + PulseControl, Rng, RtcCntl, Serial, diff --git a/esp32s3-hal/Cargo.toml b/esp32s3-hal/Cargo.toml index 8f5edaec1..b71fd17d8 100644 --- a/esp32s3-hal/Cargo.toml +++ b/esp32s3-hal/Cargo.toml @@ -39,6 +39,7 @@ features = ["esp32s3"] embedded-graphics = "0.7" panic-halt = "0.2" ssd1306 = "0.7" +smart-leds = "0.3" [features] default = ["rt"] diff --git a/esp32s3-hal/examples/hello_rgb.rs b/esp32s3-hal/examples/hello_rgb.rs new file mode 100644 index 000000000..014afa7e6 --- /dev/null +++ b/esp32s3-hal/examples/hello_rgb.rs @@ -0,0 +1,88 @@ +//! RGB LED Demo +//! +//! This example drives an SK68XX RGB LED that is connected to the GPI48 pin. +//! A RGB LED is connected to that pin on the official DevKits. +//! +//! The demo will leverage the [`smart_leds`](https://crates.io/crates/smart-leds) +//! crate functionality to circle through the HSV hue color space (with +//! saturation and value both at 255). Additionally, we apply a gamma correction +//! and limit the brightness to 10 (out of 255). + +#![no_std] +#![no_main] + +use esp32s3_hal::{ + pac, + prelude::*, + pulse_control::ClockSource, + utils::{smartLedAdapter, SmartLedsAdapter}, + Delay, + PulseControl, + RtcCntl, + Timer, + IO, +}; +#[allow(unused_imports)] +use panic_halt as _; +use smart_leds::{ + brightness, + gamma, + hsv::{hsv2rgb, Hsv}, + SmartLedsWrite, +}; +use xtensa_lx_rt::entry; + +#[entry] +fn main() -> ! { + let mut peripherals = pac::Peripherals::take().unwrap(); + + let mut rtc_cntl = RtcCntl::new(peripherals.RTC_CNTL); + let mut timer0 = Timer::new(peripherals.TIMG0); + let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); + + // Disable MWDT and RWDT (Watchdog) flash boot protection + timer0.disable(); + rtc_cntl.set_wdt_global_enable(false); + + // Configure RMT peripheral globally + let pulse = PulseControl::new( + peripherals.RMT, + &mut peripherals.SYSTEM, + ClockSource::APB, + 0, + 0, + 0, + ) + .unwrap(); + + // We use one of the RMT channels to instantiate a `SmartLedsAdapter` which can + // be used directly with all `smart_led` implementations + let mut led = ::new(pulse.channel0, io.pins.gpio48); + + // Initialize the Delay peripheral, and use it to toggle the LED state in a + // loop. + let mut delay = Delay::new(); + + let mut color = Hsv { + hue: 0, + sat: 255, + val: 255, + }; + let mut data; + + loop { + // Iterate over the rainbow! + for hue in 0..=255 { + color.hue = hue; + // Convert from the HSV color space (where we can easily transition from one + // color to the other) to the RGB color space that we can then send to the LED + data = [hsv2rgb(color)]; + // When sending to the LED, we do a gamma correction first (see smart_leds + // documentation for details) and then limit the brightness to 10 out of 255 so + // that the output it's not too bright. + led.write(brightness(gamma(data.iter().cloned()), 10)) + .unwrap(); + delay.delay_ms(20u8); + } + } +} diff --git a/esp32s3-hal/src/lib.rs b/esp32s3-hal/src/lib.rs index ffd723798..139cf2828 100644 --- a/esp32s3-hal/src/lib.rs +++ b/esp32s3-hal/src/lib.rs @@ -6,11 +6,14 @@ pub use esp_hal_common::{ interrupt, pac, prelude, + pulse_control, ram, spi, + utils, usb_serial_jtag, Cpu, Delay, + PulseControl, Rng, RtcCntl, Serial,