mirror of
https://github.com/esp-rs/esp-idf-hal.git
synced 2025-12-29 20:55:39 +00:00
* push first draft * fix source clock * fix clippy lints * rename `source_clock` to `clock_source` * implement sync manager * implement `RxChannel` * finish implementing TxChannel * make code compile with and without `alloc` * implement encoder and remove non_exhaustive * push new changes * push now hopefully safe impl * fix ci * use correct esp_idf_version in simple_encoder example * cleanup example * ... * fix ub * minor code improvements * implement async support * fix use-after-free bug and add a helper function * redesign `TxChannelDriver` (again) * fix minor mistakes and add `TxQueue` * rewrite `RxChannelDriver` and do some minor adjustments * adress the review (partially) * implement more things * make the async code mostly safe wrappers * return token in when pushing on queue * push initial new impl * update code * update code * remove todo * update changelog * adjust duty cycle to store an `f32` * fix clippy * fix clippy * fix clippy
246 lines
7.9 KiB
Rust
246 lines
7.9 KiB
Rust
//! This example is based on the led strip example found in the esp-idf repository:
|
|
//! https://github.com/espressif/esp-idf/blob/master/examples/peripherals/rmt/led_strip_simple_encoder/main/led_strip_example_main.c
|
|
//!
|
|
//! It uses the RMT peripheral to control an addressable LED strip (WS2812).
|
|
//!
|
|
//! This example demonstrates how to create a custom RMT encoder using the `SimpleEncoder` and how to interact with the
|
|
//! RMT API in general.
|
|
//!
|
|
//! SPDX-License-Identifier: Unlicense OR CC0-1.0
|
|
|
|
#![allow(unknown_lints)]
|
|
#![allow(unexpected_cfgs)]
|
|
|
|
#[cfg(any(
|
|
feature = "rmt-legacy",
|
|
not(esp_idf_version_at_least_5_3_0),
|
|
not(esp_idf_soc_rmt_supported),
|
|
))]
|
|
fn main() -> anyhow::Result<()> {
|
|
println!("This example requires feature `rmt-legacy` disabled, using ESP-IDF >= v5.1.2, or is not supported on this MCU");
|
|
|
|
loop {
|
|
std::thread::sleep(std::time::Duration::from_millis(1000));
|
|
}
|
|
}
|
|
|
|
#[cfg(all(
|
|
esp_idf_soc_rmt_supported,
|
|
esp_idf_version_at_least_5_3_0,
|
|
not(feature = "rmt-legacy")
|
|
))]
|
|
mod example {
|
|
use esp_idf_hal::peripherals::Peripherals;
|
|
use esp_idf_hal::rmt::config::{MemoryAccess, TxChannelConfig};
|
|
use esp_idf_hal::rmt::encoder::simple_encoder::{
|
|
EncoderCallback, NotEnoughSpace, SimpleEncoder, SymbolBuffer,
|
|
};
|
|
use esp_idf_hal::rmt::TxChannelDriver;
|
|
use esp_idf_hal::rmt::{PinState, PulseTicks, Symbol};
|
|
use esp_idf_hal::units::Hertz;
|
|
|
|
use core::f64::consts::PI;
|
|
use core::time::Duration;
|
|
|
|
const RMT_LED_STRIP_RESOLUTION_HZ: Hertz = Hertz(10_000_000); // 10MHz resolution, 1 tick = 0.1us
|
|
const NUMBER_OF_LEDS: usize = 1;
|
|
const ANGLE_INC_FRAME: f64 = 0.02;
|
|
const ANGLE_INC_LED: f64 = 0.3;
|
|
const FRAME_DURATION: Duration = Duration::from_millis(20);
|
|
const BRIGHTNESS: f64 = 0.5; // 0.0 = dark, 1.0 = full brightness
|
|
|
|
// From the WS2812 datasheet:
|
|
const T0H: Duration = Duration::from_nanos(350); // 0.35us
|
|
const T0L: Duration = Duration::from_nanos(800); // 0.8us
|
|
const T1H: Duration = Duration::from_nanos(700); // 0.7us
|
|
const T1L: Duration = Duration::from_nanos(600); // 0.6us
|
|
const TRESET: Duration = Duration::from_micros(281); // >50us
|
|
|
|
// From the WS2812b datasheet:
|
|
// const T0H: Duration = Duration::from_nanos(300); // 0.22us ~ 0.38us
|
|
// const T0L: Duration = Duration::from_nanos(790); // 0.58us ~ 1.0us
|
|
// const T1H: Duration = Duration::from_nanos(790); // 0.58us ~ 1.0us
|
|
// const T1L: Duration = Duration::from_nanos(790); // 0.58us ~ 1.0us
|
|
// const TRESET: Duration = Duration::from_micros(281); // >280us
|
|
|
|
#[derive(Debug, Clone, Copy, Default)]
|
|
#[repr(C)]
|
|
pub struct Color {
|
|
pub red: u8,
|
|
pub green: u8,
|
|
pub blue: u8,
|
|
}
|
|
|
|
impl Color {
|
|
#[must_use]
|
|
pub fn apply_brightness(self, brightness: f64) -> Self {
|
|
let factor = brightness.clamp(0.0, 1.0);
|
|
|
|
Self {
|
|
red: (self.red as f64 * factor).round() as u8,
|
|
green: (self.green as f64 * factor).round() as u8,
|
|
blue: (self.blue as f64 * factor).round() as u8,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn byte_to_symbols(byte: u8) -> impl IntoIterator<Item = Symbol> {
|
|
let ws2812_zero = Symbol::new_with(
|
|
RMT_LED_STRIP_RESOLUTION_HZ,
|
|
PinState::High,
|
|
T0H,
|
|
PinState::Low,
|
|
T0L,
|
|
)
|
|
.unwrap();
|
|
let ws2812_one = Symbol::new_with(
|
|
RMT_LED_STRIP_RESOLUTION_HZ,
|
|
PinState::High,
|
|
T1H,
|
|
PinState::Low,
|
|
T1L,
|
|
)
|
|
.unwrap();
|
|
|
|
(0..8)
|
|
.map(|i| 0x80 >> i)
|
|
.map(move |bitmask| (byte & bitmask) != 0)
|
|
.map(move |is_one| if is_one { ws2812_one } else { ws2812_zero })
|
|
}
|
|
|
|
impl Color {
|
|
#[must_use]
|
|
pub fn to_vec(self) -> Vec<Symbol> {
|
|
let mut result = Vec::with_capacity(24);
|
|
for byte in [self.green, self.red, self.blue] {
|
|
result.extend(byte_to_symbols(byte));
|
|
}
|
|
|
|
result
|
|
}
|
|
}
|
|
|
|
pub struct LedEncoder {
|
|
input_position: usize,
|
|
}
|
|
|
|
impl LedEncoder {
|
|
pub fn new() -> Self {
|
|
Self { input_position: 0 }
|
|
}
|
|
}
|
|
|
|
impl EncoderCallback for LedEncoder {
|
|
type Item = Color;
|
|
|
|
fn encode(
|
|
&mut self,
|
|
input_data: &[Self::Item],
|
|
buffer: &mut SymbolBuffer<'_>,
|
|
) -> Result<(), NotEnoughSpace> {
|
|
let ws2812_reset = Symbol::new_half_split(
|
|
RMT_LED_STRIP_RESOLUTION_HZ,
|
|
PinState::Low,
|
|
PinState::Low,
|
|
TRESET,
|
|
)
|
|
.unwrap();
|
|
|
|
if buffer.position() == 0 {
|
|
self.input_position = 0;
|
|
}
|
|
|
|
for &next_color in &input_data[self.input_position..] {
|
|
let mut symbols = vec![ws2812_reset];
|
|
symbols.extend(next_color.apply_brightness(BRIGHTNESS).to_vec());
|
|
|
|
buffer.write_all(&symbols)?;
|
|
self.input_position += 1;
|
|
}
|
|
|
|
// finished encoding all input data
|
|
|
|
// Add a delay between this signal and the next one:
|
|
|
|
let max_duration = PulseTicks::max().duration(RMT_LED_STRIP_RESOLUTION_HZ) * 2; // times two, because we use half-split symbols
|
|
|
|
let vec = Symbol::new_half_split(
|
|
RMT_LED_STRIP_RESOLUTION_HZ,
|
|
PinState::Low,
|
|
PinState::Low,
|
|
max_duration,
|
|
)
|
|
.unwrap()
|
|
.repeat_for(RMT_LED_STRIP_RESOLUTION_HZ, FRAME_DURATION)
|
|
.collect::<Vec<Symbol>>();
|
|
|
|
buffer.write_all(&vec)?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub fn run() -> anyhow::Result<()> {
|
|
println!("Create RMT TX channel");
|
|
|
|
let peripherals = Peripherals::take()?;
|
|
|
|
let mut channel = TxChannelDriver::new(
|
|
peripherals.pins.gpio21, // set the pin of the led strip here
|
|
&TxChannelConfig {
|
|
clock_source: Default::default(),
|
|
memory_access: MemoryAccess::Indirect {
|
|
memory_block_symbols: 64,
|
|
}, // increasing might reduce flickering
|
|
resolution: RMT_LED_STRIP_RESOLUTION_HZ,
|
|
transaction_queue_depth: 4, // The number of transactions that can be pending in the background
|
|
..Default::default()
|
|
},
|
|
)?;
|
|
|
|
println!("Start LED rainbow chase");
|
|
let mut offset = 0.0;
|
|
|
|
let iterator = core::iter::from_fn(|| {
|
|
let mut signal = [Color::default(); NUMBER_OF_LEDS];
|
|
for (i, color) in signal.iter_mut().enumerate() {
|
|
// Build RGB pixels. Each color is an offset sine, which gives a
|
|
// hue-like effect.
|
|
let angle = offset + (i as f64 * ANGLE_INC_LED);
|
|
let color_offset = (PI * 2.0) / 3.0;
|
|
|
|
*color = Color {
|
|
red: ((angle + color_offset * 1.0).sin() * 127.0 + 128.0) as u8,
|
|
green: ((angle + color_offset * 0.0).sin() * 127.0 + 128.0) as u8,
|
|
blue: ((angle + color_offset * 2.0).sin() * 117.0 + 128.0) as u8,
|
|
};
|
|
}
|
|
|
|
// Increase offset to shift pattern
|
|
offset += ANGLE_INC_FRAME;
|
|
if offset > 2.0 * PI {
|
|
offset -= 2.0 * PI;
|
|
}
|
|
|
|
Some(signal)
|
|
});
|
|
|
|
// This will use 10 encoders to encode the data returned by the iterator:
|
|
let mut encoders = [(); 10]
|
|
.map(|_| SimpleEncoder::with_config(LedEncoder::new(), &Default::default()).unwrap());
|
|
|
|
channel.send_iter(encoders.each_mut(), iterator, &Default::default())?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[cfg(all(
|
|
esp_idf_soc_rmt_supported,
|
|
esp_idf_version_at_least_5_3_0,
|
|
not(feature = "rmt-legacy")
|
|
))]
|
|
fn main() -> anyhow::Result<()> {
|
|
example::run()
|
|
}
|