#![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};
pub use color_order::ColorOrder;
use esp_hal::{
Async, Blocking, DriverMode,
gpio::{Level, interconnect::PeripheralOutput},
rmt::{
Channel, ConfigError as RmtConfigError, Error as RmtError, PulseCode, Tx, TxChannelConfig,
TxChannelCreator,
},
time::Rate,
};
use num_traits::Unsigned;
use smart_leds_trait::{CctWhite, RGB, RGBCCT, RGBW, SmartLedsWrite, SmartLedsWriteAsync, White};
#[derive(Clone, Copy)]
pub struct Timing {
pub time_0_low: u16,
pub time_0_high: u16,
pub time_1_low: u16,
pub time_1_high: u16,
pub reset_us: u16,
}
impl Timing {
#[must_use]
pub const fn with_reset_us(mut self, reset_us: u16) -> Self {
self.reset_us = reset_us;
self
}
}
const SK68XX_CODE_PERIOD: u16 = 1200;
const SK68XX_TIME_0_HIGH: u16 = 320;
const SK68XX_TIME_1_HIGH: u16 = 640;
pub const SK68XX_TIMING: Timing = Timing {
time_0_high: SK68XX_TIME_0_HIGH,
time_0_low: SK68XX_CODE_PERIOD - SK68XX_TIME_0_HIGH,
time_1_high: SK68XX_TIME_1_HIGH,
time_1_low: SK68XX_CODE_PERIOD - SK68XX_TIME_1_HIGH,
reset_us: 300,
};
pub const WS2812B_TIMING: Timing = Timing {
time_0_high: 400,
time_0_low: 800,
time_1_high: 850,
time_1_low: 450,
reset_us: 300,
};
pub const WS2812_TIMING: Timing = Timing {
time_0_high: 350,
time_0_low: 700,
time_1_high: 800,
time_1_low: 600,
reset_us: 80,
};
pub const WS2811_LOW_SPEED_TIMING: Timing = Timing {
time_0_high: 500,
time_0_low: 2000,
time_1_high: 1200,
time_1_low: 1300,
reset_us: 300,
};
pub const WS2811_TIMING: Timing = Timing {
time_0_high: WS2811_LOW_SPEED_TIMING.time_0_high / 2,
time_0_low: WS2811_LOW_SPEED_TIMING.time_0_low / 2,
time_1_high: WS2811_LOW_SPEED_TIMING.time_1_high / 2,
time_1_low: WS2811_LOW_SPEED_TIMING.time_1_low / 2,
reset_us: 300,
};
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum AdapterError {
BufferSizeExceeded,
TransmissionError(RmtError),
BufferNotReady,
}
impl From<RmtError> for AdapterError {
fn from(value: RmtError) -> Self {
Self::TransmissionError(value)
}
}
pub trait Color {
const CHANNELS: u8;
type ChannelType: Unsigned + Into<usize>;
}
impl<T> Color for RGB<T>
where
T: Unsigned + Into<usize>,
{
const CHANNELS: u8 = 3;
type ChannelType = T;
}
impl<T> Color for RGBW<T>
where
T: Unsigned + Into<usize>,
{
const CHANNELS: u8 = 4;
type ChannelType = T;
}
impl<T> Color for RGBCCT<T>
where
T: Unsigned + Into<usize>,
{
const CHANNELS: u8 = 5;
type ChannelType = T;
}
impl<T> Color for White<T>
where
T: Unsigned + Into<usize>,
{
const CHANNELS: u8 = 1;
type ChannelType = T;
}
impl<T> Color for CctWhite<T>
where
T: Unsigned + Into<usize>,
{
const CHANNELS: u8 = 2;
type ChannelType = T;
}
pub const fn buffer_size<C: Color>(led_count: usize) -> usize {
led_count * (size_of::<C::ChannelType>() * 8) * C::CHANNELS as usize + 2
}
pub mod color_order {
use num_traits::Unsigned;
use smart_leds_trait::{RGB, RGBW, White};
use crate::Color;
pub trait ColorOrder<C: Color> {
fn get_channel_data(color: &C, channel: u8) -> C::ChannelType;
}
macro_rules! color_order_rgb {
($name:ident => $first:ident, $second:ident, $third:ident) => {
#[doc = concat!("[`ColorOrder`] ", stringify!($name), ".")]
pub enum $name {}
impl<T> ColorOrder<RGB<T>> for $name
where
T: Copy + Unsigned + Into<usize>,
{
fn get_channel_data(color: &RGB<T>, channel: u8) -> T {
match channel {
0 => color.$first,
1 => color.$second,
2 => color.$third,
_ => unreachable!(),
}
}
}
};
}
color_order_rgb!(Rgb => r, g, b);
color_order_rgb!(Rbg => r, b, g);
color_order_rgb!(Grb => g, r, b);
color_order_rgb!(Gbr => g, b, r);
color_order_rgb!(Brg => b, r, g);
color_order_rgb!(Bgr => b, g, r);
pub enum Rgbw {}
impl<T> ColorOrder<RGBW<T>> for Rgbw
where
T: Copy + Unsigned + Into<usize>,
{
fn get_channel_data(color: &RGBW<T>, channel: u8) -> T {
match channel {
0 => color.r,
1 => color.g,
2 => color.b,
3 => color.a.0,
_ => unreachable!(),
}
}
}
pub enum Grbw {}
impl<T> ColorOrder<RGBW<T>> for Grbw
where
T: Copy + num_traits::sign::Unsigned + Into<usize>,
{
fn get_channel_data(color: &RGBW<T>, channel: u8) -> T {
match channel {
0 => color.g,
1 => color.r,
2 => color.b,
3 => color.a.0,
_ => unreachable!(),
}
}
}
pub enum SingleChannel {}
impl<T> ColorOrder<White<T>> for SingleChannel
where
T: Copy + Unsigned + Into<usize>,
{
fn get_channel_data(color: &White<T>, _channel: u8) -> T {
color.0
}
}
}
pub struct RmtSmartLeds<'d, const BUFFER_SIZE: usize, Mode, C, Order>
where
Mode: DriverMode,
C: Color,
Order: ColorOrder<C>,
{
channel: Option<Channel<'d, Mode, Tx>>,
rmt_buffer: [PulseCode; BUFFER_SIZE],
buffer_valid: bool,
zero_pulse: PulseCode,
one_pulse: PulseCode,
reset_pulse: PulseCode,
rmt_freq: Rate,
_order: PhantomData<Order>,
_color: PhantomData<C>,
}
fn zero_pulse(t: &Timing, src_clock_mhz: u32) -> Option<PulseCode> {
PulseCode::try_new(
Level::High,
(t.time_0_high as u32 * src_clock_mhz) / 1000,
Level::Low,
(t.time_0_low as u32 * src_clock_mhz) / 1000,
)
}
fn one_pulse(t: &Timing, src_clock_mhz: u32) -> Option<PulseCode> {
PulseCode::try_new(
Level::High,
(t.time_1_high as u32 * src_clock_mhz) / 1000,
Level::Low,
(t.time_1_low as u32 * src_clock_mhz) / 1000,
)
}
fn reset_pulse(t: &Timing, src_clock_mhz: u32) -> Option<PulseCode> {
let reset_half = (t.reset_us / 2) as u32;
PulseCode::try_new(
Level::Low,
reset_half * src_clock_mhz,
Level::Low,
reset_half * src_clock_mhz,
)
}
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("could not calculate valid pulses for the provided timing")]
Timing,
#[error("{_0:?}")]
RmtConfig(#[from] RmtConfigError),
}
impl<'d, const BUFFER_SIZE: usize, Mode, C, Order> RmtSmartLeds<'d, BUFFER_SIZE, Mode, C, Order>
where
Mode: DriverMode,
C: Color,
Order: ColorOrder<C>,
{
pub fn new<Ch, P>(timing: Timing, channel: Ch, pin: P, rmt_freq: Rate) -> Result<Self, Error>
where
Ch: TxChannelCreator<'d, Mode>,
P: PeripheralOutput<'d>,
{
Self::new_with_memsize(timing, channel, pin, 1, rmt_freq)
}
pub fn new_with_memsize<Ch, P>(
timing: Timing,
channel: Ch,
pin: P,
memsize: u8,
rmt_freq: Rate,
) -> Result<Self, Error>
where
Ch: TxChannelCreator<'d, Mode>,
P: PeripheralOutput<'d>,
{
let config = TxChannelConfig::default()
.with_clk_divider(1)
.with_idle_output_level(Level::Low)
.with_memsize(memsize)
.with_carrier_modulation(false)
.with_idle_output(true);
let channel = channel.configure_tx(&config)?.with_pin(pin);
let (zero_pulse, one_pulse, reset_pulse) =
Self::get_timings_for(&timing, rmt_freq).ok_or(Error::Timing)?;
Ok(Self {
channel: Some(channel),
rmt_buffer: [PulseCode::end_marker(); BUFFER_SIZE],
buffer_valid: false,
zero_pulse,
one_pulse,
reset_pulse,
rmt_freq,
_order: PhantomData,
_color: PhantomData,
})
}
pub fn get_timings_for(
t: &Timing,
rmt_freq: Rate,
) -> Option<(PulseCode, PulseCode, PulseCode)> {
let src_clock = rmt_freq.as_mhz();
Some((
zero_pulse(t, src_clock)?,
one_pulse(t, src_clock)?,
reset_pulse(t, src_clock)?,
))
}
pub fn set_timing(&mut self, t: Timing) -> Result<(), Error> {
let (zero_pulse, one_pulse, reset_pulse) =
Self::get_timings_for(&t, self.rmt_freq).ok_or(Error::Timing)?;
self.zero_pulse = zero_pulse;
self.one_pulse = one_pulse;
self.reset_pulse = reset_pulse;
self.buffer_valid = false;
Ok(())
}
fn create_rmt_data(
&mut self,
iterator: impl IntoIterator<Item = impl Into<C>>,
) -> Result<(), AdapterError> {
self.buffer_valid = false;
let mut seq_iter = self.rmt_buffer.iter_mut();
for item in iterator {
convert_colors_to_pulse::<_, Order>(
&item.into(),
&mut seq_iter,
self.zero_pulse,
self.one_pulse,
)?;
}
*seq_iter.next().ok_or(AdapterError::BufferSizeExceeded)? = self.reset_pulse;
*seq_iter.next().ok_or(AdapterError::BufferSizeExceeded)? = PulseCode::end_marker();
self.buffer_valid = true;
Ok(())
}
#[allow(unused)]
pub(crate) fn write_pixel_data(
&mut self,
index: usize,
color: impl Into<C>,
) -> Result<(), AdapterError> {
let buffer_start_index = index * C::CHANNELS as usize * (size_of::<C::ChannelType>() * 8);
let mut buffer_iter = self
.rmt_buffer
.get_mut(buffer_start_index..)
.ok_or(AdapterError::BufferSizeExceeded)?
.iter_mut();
convert_colors_to_pulse::<_, Order>(
&color.into(),
&mut buffer_iter,
self.zero_pulse,
self.one_pulse,
)
}
}
impl<'d, const BUFFER_SIZE: usize, C, Order> RmtSmartLeds<'d, BUFFER_SIZE, Blocking, C, Order>
where
C: Color,
Order: ColorOrder<C>,
{
pub fn flush(&mut self) -> Result<(), AdapterError> {
if !self.buffer_valid {
return Err(AdapterError::BufferNotReady);
}
let channel = self.channel.take().unwrap();
match channel
.transmit(&self.rmt_buffer)
.map_err(|(e, _)| e)?
.wait()
{
Ok(chan) => {
self.channel = Some(chan);
Ok(())
}
Err((e, chan)) => {
self.channel = Some(chan);
Err(AdapterError::TransmissionError(e))
}
}
}
}
impl<'d, const BUFFER_SIZE: usize, C, Order> SmartLedsWrite
for RmtSmartLeds<'d, BUFFER_SIZE, Blocking, C, Order>
where
C: Color,
Order: ColorOrder<C>,
{
type Error = AdapterError;
type Color = C;
fn write<T, I>(&mut self, iterator: T) -> Result<(), Self::Error>
where
T: IntoIterator<Item = I>,
I: Into<Self::Color>,
{
self.create_rmt_data(iterator)?;
self.flush()
}
}
impl<'d, const BUFFER_SIZE: usize, C, Order> SmartLedsWriteAsync
for RmtSmartLeds<'d, BUFFER_SIZE, Async, C, Order>
where
C: Color,
Order: ColorOrder<C>,
{
type Error = AdapterError;
type Color = C;
fn write<T, I>(&mut self, iterator: T) -> impl Future<Output = Result<(), Self::Error>>
where
T: IntoIterator<Item = I>,
I: Into<Self::Color>,
{
let res = self.create_rmt_data(iterator);
async move {
res?;
self.channel
.as_mut()
.unwrap()
.transmit(&self.rmt_buffer)
.await?;
Ok(())
}
}
}
fn convert_colors_to_pulse<'a, C, Order>(
value: &C,
mut_iter: &mut impl Iterator<Item = &'a mut PulseCode>,
zero_pulse: PulseCode,
one_pulse: PulseCode,
) -> Result<(), AdapterError>
where
C: Color,
Order: ColorOrder<C>,
{
for channel in 0..C::CHANNELS {
convert_channel_to_pulses(
Order::get_channel_data(value, channel),
mut_iter,
zero_pulse,
one_pulse,
)?;
}
Ok(())
}
fn convert_channel_to_pulses<'a, N>(
channel_value: N,
mut_iter: &mut impl Iterator<Item = &'a mut PulseCode>,
zero_pulse: PulseCode,
one_pulse: PulseCode,
) -> Result<(), AdapterError>
where
N: Unsigned + Into<usize>,
{
let channel_value: usize = channel_value.into();
for index in (0..size_of::<N>() * 8).rev() {
let position = 1 << index;
*mut_iter.next().ok_or(AdapterError::BufferSizeExceeded)? = match channel_value & position {
0 => zero_pulse,
_ => one_pulse,
}
}
Ok(())
}