Skip to content

Commit

Permalink
add support for non-blocking smart LED driver
Browse files Browse the repository at this point in the history
  • Loading branch information
robamu committed Oct 12, 2024
1 parent 58014bf commit 374e8a2
Show file tree
Hide file tree
Showing 2 changed files with 177 additions and 64 deletions.
2 changes: 1 addition & 1 deletion esp-hal-smartled/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "esp-hal-smartled"
version = "0.13.1"
version = "0.14.0"
edition = "2021"
rust-version = "1.76.0"
description = "RMT peripheral adapter for smart LEDs"
Expand Down
239 changes: 176 additions & 63 deletions esp-hal-smartled/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ use esp_hal::{
};
use smart_leds_trait::{SmartLedsWrite, RGB8};

// Required RMT RAM to drive one LED.
// number of channels (r,g,b -> 3) * pulses per channel 8)
const RMT_RAM_ONE_LED: usize = 3 * 8;

const SK68XX_CODE_PERIOD: u32 = 1200;
const SK68XX_T0H_NS: u32 = 320;
const SK68XX_T0L_NS: u32 = SK68XX_CODE_PERIOD - SK68XX_T0H_NS;
Expand All @@ -51,24 +55,89 @@ pub enum LedAdapterError {
TransmissionError(RmtError),
}

fn led_pulses_for_clock(src_clock: u32) -> (u32, u32) {
(
u32::from(PulseCode {
level1: true,
length1: ((SK68XX_T0H_NS * src_clock) / 1000) as u16,
level2: false,
length2: ((SK68XX_T0L_NS * src_clock) / 1000) as u16,
}),
u32::from(PulseCode {
level1: true,
length1: ((SK68XX_T1H_NS * src_clock) / 1000) as u16,
level2: false,
length2: ((SK68XX_T1L_NS * src_clock) / 1000) as u16,
}),
)
}

fn led_config() -> TxChannelConfig {
TxChannelConfig {
clk_divider: 1,
idle_output_level: false,
carrier_modulation: false,
idle_output: true,

..TxChannelConfig::default()
}
}

fn convert_rgb_to_pulses(
value: RGB8,
mut_iter: &mut IterMut<u32>,
pulses: (u32, u32),
) -> Result<(), LedAdapterError> {
convert_rgb_channel_to_pulses(value.g, mut_iter, pulses)?;
convert_rgb_channel_to_pulses(value.r, mut_iter, pulses)?;
convert_rgb_channel_to_pulses(value.b, mut_iter, pulses)?;
Ok(())
}

fn convert_rgb_channel_to_pulses(
channel_value: u8,
mut_iter: &mut IterMut<u32>,
pulses: (u32, u32),
) -> 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 => pulses.0,
_ => pulses.1,
}
}

Ok(())
}

/// Macro to allocate a buffer sized for a specific number of 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! smart_led_buffer {
( $num_leds: ident ) => {
[0u32; buffer_size($num_leds)]
};
}

/// Deprecated alias for [smart_led_buffer] macro.
#[macro_export]
#[deprecated]
macro_rules! smartLedBuffer {
( $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
[0u32; $buffer_size * 24 + 1]
( $num_leds: ident ) => {
smart_led_buffer!($num_leds);
};
}

/// Function to calculate the required RMT buffer size for a given number of LEDs when using
/// the blocking API.
pub const fn buffer_size(num_leds: usize) -> usize {
// 1 additional pulse for the end delimiter
num_leds * RMT_RAM_ONE_LED + 1
}

/// Adapter taking an RMT channel and a specific pin and providing RGB LED
/// interaction functionality using the `smart-leds` crate
pub struct SmartLedsAdapter<TX, const BUFFER_SIZE: usize>
Expand All @@ -94,67 +163,16 @@ where
O: OutputPin + 'd,
C: TxChannelCreator<'d, TX, O>,
{
let config = TxChannelConfig {
clk_divider: 1,
idle_output_level: false,
carrier_modulation: false,
idle_output: true,

..TxChannelConfig::default()
};

let channel = channel.configure(pin, config).unwrap();
let channel = channel.configure(pin, led_config()).unwrap();

// Assume the RMT peripheral is set up to use the APB clock
let clocks = Clocks::get();
let src_clock = clocks.apb_clock.to_MHz();
let src_clock = Clocks::get().apb_clock.to_MHz();

Self {
channel: Some(channel),
rmt_buffer,
pulses: (
u32::from(PulseCode {
level1: true,
length1: ((SK68XX_T0H_NS * src_clock) / 1000) as u16,
level2: false,
length2: ((SK68XX_T0L_NS * src_clock) / 1000) as u16,
}),
u32::from(PulseCode {
level1: true,
length1: ((SK68XX_T1H_NS * src_clock) / 1000) as u16,
level2: false,
length2: ((SK68XX_T1L_NS * src_clock) / 1000) as u16,
}),
),
}
}

fn convert_rgb_to_pulse(
value: RGB8,
mut_iter: &mut IterMut<u32>,
pulses: (u32, u32),
) -> Result<(), LedAdapterError> {
Self::convert_rgb_channel_to_pulses(value.g, mut_iter, pulses)?;
Self::convert_rgb_channel_to_pulses(value.r, mut_iter, pulses)?;
Self::convert_rgb_channel_to_pulses(value.b, mut_iter, pulses)?;

Ok(())
}

fn convert_rgb_channel_to_pulses(
channel_value: u8,
mut_iter: &mut IterMut<u32>,
pulses: (u32, u32),
) -> 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 => pulses.0,
_ => pulses.1,
}
pulses: led_pulses_for_clock(src_clock),
}

Ok(())
}
}

Expand All @@ -180,7 +198,7 @@ where
// This will result in an `BufferSizeExceeded` error in case
// the iterator provides more elements than the buffer can take.
for item in iterator {
Self::convert_rgb_to_pulse(item.into(), &mut seq_iter, self.pulses)?;
convert_rgb_to_pulses(item.into(), &mut seq_iter, self.pulses)?;
}

// Finally, add an end element.
Expand All @@ -200,3 +218,98 @@ where
}
}
}

/// Support for asynchronous and non-blocking use of the RMT peripheral to drive smart LEDs.
pub mod asynch {
use super::*;
use esp_hal::{
clock::Clocks,
gpio::PeripheralOutput,
peripheral::Peripheral,
rmt::{asynch::TxChannelAsync, TxChannelCreatorAsync},
};

/// Function to calculate the required RMT buffer size for a given number of LEDs when using
/// the asynchronous API.
pub const fn buffer_size(num_leds: usize) -> usize {
// 1 byte end delimiter for each transfer.
num_leds * (RMT_RAM_ONE_LED + 1)
}

/// Adapter taking an RMT channel and a specific pin and providing RGB LED
/// interaction functionality.
pub struct SmartLedAdapterAsync<Tx, const BUFFER_SIZE: usize> {
channel: Tx,
rmt_buffer: [u32; BUFFER_SIZE],
pulses: (u32, u32),
}

impl<'d, Tx: TxChannelAsync, const BUFFER_SIZE: usize> SmartLedAdapterAsync<Tx, BUFFER_SIZE> {
/// Create a new adapter object that drives the pin using the RMT channel.
pub fn new<C, O>(
channel: C,
pin: impl Peripheral<P = O> + 'd,
rmt_buffer: [u32; BUFFER_SIZE],
) -> SmartLedAdapterAsync<Tx, BUFFER_SIZE>
where
O: PeripheralOutput + 'd,
C: TxChannelCreatorAsync<'d, Tx, O>,
{
let channel = channel.configure(pin, led_config()).unwrap();

// Assume the RMT peripheral is set up to use the APB clock
let src_clock = Clocks::get().apb_clock.to_MHz();

Self {
channel,
rmt_buffer,
pulses: led_pulses_for_clock(src_clock),
}
}

/// Convert all RGB8 items of the iterator to the RMT format and
/// add them to internal buffer, then start perform all asynchronous operations based on
/// that buffer.
pub async fn write(
&mut self,
led: impl IntoIterator<Item = RGB8>,
) -> Result<(), LedAdapterError> {
self.prepare_rmt_buffer(led)?;
for chunk in self.rmt_buffer.chunks(RMT_RAM_ONE_LED + 1) {
self.channel
.transmit(chunk)
.await
.map_err(LedAdapterError::TransmissionError)?;
}
Ok(())
}

fn prepare_rmt_buffer(
&mut self,
iterator: impl IntoIterator<Item = RGB8>,
) -> Result<(), LedAdapterError> {
// 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 {
Self::convert_rgb_to_pulse(item, &mut seq_iter, self.pulses)?;
}
Ok(())
}

/// Converts a RGB value to the correspodnign pulse value.
fn convert_rgb_to_pulse(
value: RGB8,
mut_iter: &mut IterMut<u32>,
pulses: (u32, u32),
) -> Result<(), LedAdapterError> {
convert_rgb_to_pulses(value, mut_iter, pulses)?;
*mut_iter.next().ok_or(LedAdapterError::BufferSizeExceeded)? = 0;

Ok(())
}
}
}

0 comments on commit 374e8a2

Please sign in to comment.