#![doc = document_features::document_features!()]
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")]
#![deny(missing_docs)]
#![no_std]
use core::{fmt::Debug, marker::PhantomData, slice::IterMut};
use esp_hal::{
Async, Blocking,
clock::Clocks,
gpio::{Level, interconnect::PeripheralOutput},
rmt::{Channel, Error as RmtError, PulseCode, Tx, TxChannelConfig, TxChannelCreator},
};
use rgb::Grb;
use smart_leds_trait::{SmartLedsWrite, SmartLedsWriteAsync};
const RMT_RAM_ONE_LED: usize = 3 * 8;
const RMT_RAM_ONE_RBGW_LED: usize = 4 * 8;
const SK68XX_CODE_PERIOD: u32 = 1250; const SK68XX_T0H_NS: u32 = 400; const SK68XX_T0L_NS: u32 = SK68XX_CODE_PERIOD - SK68XX_T0H_NS;
const SK68XX_T1H_NS: u32 = 850; const SK68XX_T1L_NS: u32 = SK68XX_CODE_PERIOD - SK68XX_T1H_NS;
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum LedAdapterError {
BufferSizeExceeded,
TransmissionError(RmtError),
}
impl From<RmtError> for LedAdapterError {
fn from(e: RmtError) -> Self {
LedAdapterError::TransmissionError(e)
}
}
fn led_pulses_for_clock(src_clock: u32) -> (PulseCode, PulseCode) {
(
PulseCode::new(
Level::High,
((SK68XX_T0H_NS * src_clock) / 1000) as u16,
Level::Low,
((SK68XX_T0L_NS * src_clock) / 1000) as u16,
),
PulseCode::new(
Level::High,
((SK68XX_T1H_NS * src_clock) / 1000) as u16,
Level::Low,
((SK68XX_T1L_NS * src_clock) / 1000) as u16,
),
)
}
fn led_config() -> TxChannelConfig {
TxChannelConfig::default()
.with_clk_divider(1)
.with_idle_output_level(Level::Low)
.with_carrier_modulation(false)
.with_idle_output(true)
}
fn convert_to_pulses(
value: &[u8],
mut_iter: &mut IterMut<PulseCode>,
pulses: (PulseCode, PulseCode),
) -> Result<(), LedAdapterError> {
for v in value {
convert_rgb_channel_to_pulses(*v, mut_iter, pulses)?;
}
Ok(())
}
fn convert_rgb_channel_to_pulses(
channel_value: u8,
mut_iter: &mut IterMut<PulseCode>,
pulses: (PulseCode, PulseCode),
) -> 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(())
}
pub const fn buffer_size(num_leds: usize) -> usize {
num_leds * RMT_RAM_ONE_LED + 1
}
pub const fn buffer_size_rgbw(num_leds: usize) -> usize {
num_leds * RMT_RAM_ONE_RBGW_LED + 1
}
#[macro_export]
macro_rules! smart_led_buffer {
( $num_leds: expr ) => {
[::esp_hal::rmt::PulseCode::end_marker(); $crate::buffer_size($num_leds)]
};
( $num_leds: expr; RGBW ) => {
[::esp_hal::rmt::PulseCode::end_marker(); $crate::buffer_size_rgbw($num_leds)]
};
}
#[macro_export]
#[deprecated]
macro_rules! smartLedBuffer {
( $num_leds: expr ) => {
smart_led_buffer!($num_leds);
};
}
pub struct SmartLedsAdapter<'ch, const BUFFER_SIZE: usize, Color = Grb<u8>> {
channel: Option<Channel<'ch, Blocking, Tx>>,
rmt_buffer: &'ch mut [PulseCode; BUFFER_SIZE],
pulses: (PulseCode, PulseCode),
color: PhantomData<Color>,
}
impl<'ch, const BUFFER_SIZE: usize> SmartLedsAdapter<'ch, BUFFER_SIZE, Grb<u8>> {
pub fn new<C, O>(channel: C, pin: O, rmt_buffer: &'ch mut [PulseCode; BUFFER_SIZE]) -> Self
where
O: PeripheralOutput<'ch>,
C: TxChannelCreator<'ch, Blocking>,
{
Self::new_with_color(channel, pin, rmt_buffer)
}
}
impl<'ch, const BUFFER_SIZE: usize, Color> SmartLedsAdapter<'ch, BUFFER_SIZE, Color>
where
Color: rgb::ComponentSlice<u8>,
{
pub fn new_with_color<C, O>(
channel: C,
pin: O,
rmt_buffer: &'ch mut [PulseCode; BUFFER_SIZE],
) -> SmartLedsAdapter<'ch, BUFFER_SIZE, Color>
where
O: PeripheralOutput<'ch>,
C: TxChannelCreator<'ch, Blocking>,
{
let channel = channel.configure_tx(pin, led_config()).unwrap();
let src_clock = Clocks::get().apb_clock.as_mhz();
Self {
channel: Some(channel),
rmt_buffer,
pulses: led_pulses_for_clock(src_clock),
color: PhantomData,
}
}
}
impl<'ch, const BUFFER_SIZE: usize, Color> SmartLedsWrite
for SmartLedsAdapter<'ch, BUFFER_SIZE, Color>
where
Color: rgb::ComponentSlice<u8>,
{
type Error = LedAdapterError;
type Color = Color;
fn write<T, I>(&mut self, iterator: T) -> Result<(), Self::Error>
where
T: IntoIterator<Item = I>,
I: Into<Self::Color>,
{
let mut seq_iter = self.rmt_buffer.iter_mut();
for item in iterator {
convert_to_pulses(item.into().as_slice(), &mut seq_iter, self.pulses)?;
}
*seq_iter.next().ok_or(LedAdapterError::BufferSizeExceeded)? = PulseCode::end_marker();
let channel = self.channel.take().unwrap();
match channel.transmit(self.rmt_buffer)?.wait() {
Ok(chan) => {
self.channel = Some(chan);
Ok(())
}
Err((e, chan)) => {
self.channel = Some(chan);
Err(LedAdapterError::TransmissionError(e))
}
}
}
}
pub const fn buffer_size_async(num_leds: usize) -> usize {
num_leds * (RMT_RAM_ONE_LED + 1)
}
pub const fn buffer_size_async_rgbw(num_leds: usize) -> usize {
num_leds * (RMT_RAM_ONE_RBGW_LED + 1)
}
pub struct SmartLedsAdapterAsync<'ch, const BUFFER_SIZE: usize, Color = Grb<u8>> {
channel: Channel<'ch, Async, Tx>,
rmt_buffer: &'ch mut [PulseCode; BUFFER_SIZE],
pulses: (PulseCode, PulseCode),
color: PhantomData<Color>,
}
impl<'ch, const BUFFER_SIZE: usize> SmartLedsAdapterAsync<'ch, BUFFER_SIZE, Grb<u8>> {
pub fn new<C, O>(channel: C, pin: O, rmt_buffer: &'ch mut [PulseCode; BUFFER_SIZE]) -> Self
where
O: PeripheralOutput<'ch>,
C: TxChannelCreator<'ch, Async>,
{
Self::new_with_color(channel, pin, rmt_buffer)
}
}
impl<'ch, const BUFFER_SIZE: usize, Color> SmartLedsAdapterAsync<'ch, BUFFER_SIZE, Color>
where
Color: rgb::ComponentSlice<u8>,
{
pub fn new_with_color<C, O>(
channel: C,
pin: O,
rmt_buffer: &'ch mut [PulseCode; BUFFER_SIZE],
) -> SmartLedsAdapterAsync<'ch, BUFFER_SIZE, Color>
where
O: PeripheralOutput<'ch>,
C: TxChannelCreator<'ch, Async>,
{
let channel = channel.configure_tx(pin, led_config()).unwrap();
let src_clock = Clocks::get().apb_clock.as_mhz();
Self {
channel,
rmt_buffer,
pulses: led_pulses_for_clock(src_clock),
color: PhantomData,
}
}
fn prepare_rmt_buffer<I: Into<Color>>(
&mut self,
iterator: impl IntoIterator<Item = I>,
) -> Result<(), LedAdapterError> {
let mut seq_iter = self.rmt_buffer.iter_mut();
for item in iterator {
Self::convert_to_pulses(item.into().as_slice(), &mut seq_iter, self.pulses)?;
}
Ok(())
}
fn convert_to_pulses(
value: &[u8],
mut_iter: &mut IterMut<PulseCode>,
pulses: (PulseCode, PulseCode),
) -> Result<(), LedAdapterError> {
for v in value {
convert_rgb_channel_to_pulses(*v, mut_iter, pulses)?;
}
*mut_iter.next().ok_or(LedAdapterError::BufferSizeExceeded)? = PulseCode::end_marker();
Ok(())
}
}
impl<'ch, const BUFFER_SIZE: usize, Color> SmartLedsWriteAsync
for SmartLedsAdapterAsync<'ch, BUFFER_SIZE, Color>
where
Color: rgb::ComponentSlice<u8>,
{
type Error = LedAdapterError;
type Color = Color;
async fn write<T, I>(&mut self, iterator: T) -> Result<(), Self::Error>
where
T: IntoIterator<Item = I>,
I: Into<Self::Color>,
{
self.prepare_rmt_buffer(iterator)?;
for chunk in self.rmt_buffer.chunks(RMT_RAM_ONE_LED + 1) {
self.channel
.transmit(chunk)
.await
.map_err(LedAdapterError::TransmissionError)?;
}
Ok(())
}
}