#[doc(inline)]
pub use smart_leds::RGB8;
pub mod colors {
pub use smart_leds::colors::*;
}
#[doc(inline)]
pub use embedded_graphics::pixelcolor::Rgb888;
pub trait ToRgb8 {
#[must_use]
fn to_rgb8(self) -> RGB8;
}
impl ToRgb8 for RGB8 {
#[inline(always)]
fn to_rgb8(self) -> RGB8 {
self
}
}
impl ToRgb8 for Rgb888 {
#[inline(always)]
fn to_rgb8(self) -> RGB8 {
use embedded_graphics::prelude::RgbColor;
RGB8::new(self.r(), self.g(), self.b())
}
}
pub trait ToRgb888 {
#[must_use]
fn to_rgb888(self) -> Rgb888;
}
impl ToRgb888 for RGB8 {
#[inline(always)]
fn to_rgb888(self) -> Rgb888 {
Rgb888::new(self.r, self.g, self.b)
}
}
impl ToRgb888 for Rgb888 {
#[inline(always)]
fn to_rgb888(self) -> Rgb888 {
self
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Gamma {
Linear,
Srgb,
SmartLeds,
}
impl Default for Gamma {
fn default() -> Self {
Self::Srgb
}
}
#[doc(hidden)]
pub const GAMMA_DEFAULT: Gamma = Gamma::Srgb;
#[doc(hidden)]
pub const MAX_FRAMES_DEFAULT: usize = 16;
pub(crate) const GAMMA_SRGB_TABLE: [u8; 256] = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2,
3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 11, 11,
11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 22, 22, 23,
23, 24, 25, 25, 26, 26, 27, 28, 28, 29, 30, 30, 31, 32, 33, 33, 34, 35, 35, 36, 37, 38, 39, 39,
40, 41, 42, 43, 43, 44, 45, 46, 47, 48, 49, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 73, 74, 75, 76, 77, 78, 79, 81, 82, 83, 84, 85, 87, 88,
89, 90, 91, 93, 94, 95, 97, 98, 99, 100, 102, 103, 105, 106, 107, 109, 110, 111, 113, 114, 116,
117, 119, 120, 121, 123, 124, 126, 127, 129, 130, 132, 133, 135, 137, 138, 140, 141, 143, 145,
146, 148, 149, 151, 153, 154, 156, 158, 159, 161, 163, 165, 166, 168, 170, 172, 173, 175, 177,
179, 181, 182, 184, 186, 188, 190, 192, 194, 196, 197, 199, 201, 203, 205, 207, 209, 211, 213,
215, 217, 219, 221, 223, 225, 227, 229, 231, 234, 236, 238, 240, 242, 244, 246, 248, 251, 253,
255,
];
pub(crate) const GAMMA_SMARTLEDS_TABLE: [u8; 256] = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5,
5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14,
14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25, 25, 26, 27,
27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36, 37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46,
47, 48, 49, 50, 50, 51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68, 69, 70, 72,
73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89, 90, 92, 93, 95, 96, 98, 99, 101, 102, 104,
105, 107, 109, 110, 112, 114, 115, 117, 119, 120, 122, 124, 126, 127, 129, 131, 133, 135, 137,
138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 167, 169, 171, 173, 175,
177, 180, 182, 184, 186, 189, 191, 193, 196, 198, 200, 203, 205, 208, 210, 213, 215, 218, 220,
223, 225, 228, 231, 233, 236, 239, 241, 244, 247, 249, 252, 255,
];
const LINEAR_TABLE: [u8; 256] = {
let mut t = [0u8; 256];
let mut index = 0usize;
while index < 256 {
t[index] = index as u8;
index += 1;
}
t
};
#[doc(hidden)]
#[must_use]
pub const fn generate_combo_table(gamma: Gamma, max_brightness: u8) -> [u8; 256] {
let gamma_table = match gamma {
Gamma::Linear => &LINEAR_TABLE,
Gamma::Srgb => &GAMMA_SRGB_TABLE,
Gamma::SmartLeds => &GAMMA_SMARTLEDS_TABLE,
};
let mut result = [0u8; 256];
let mut index = 0usize;
while index < 256 {
let corrected = gamma_table[index];
result[index] = ((corrected as u16 * max_brightness as u16) / 255) as u8;
index += 1;
}
result
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Current {
Milliamps(u32),
Unlimited,
}
impl Default for Current {
fn default() -> Self {
Self::Milliamps(250)
}
}
impl Current {
#[doc(hidden)]
#[must_use]
pub const fn max_brightness(self, worst_case_ma: u32) -> u8 {
assert!(worst_case_ma > 0, "worst_case_ma must be positive");
match self {
Self::Milliamps(ma) => {
let scale = (ma as u64 * 255) / worst_case_ma as u64;
if scale > 255 { 255 } else { scale as u8 }
}
Self::Unlimited => 255,
}
}
}
use core::ops::{Deref, DerefMut};
#[derive(Clone, Copy, Debug)]
pub struct Frame1d<const N: usize>(pub [RGB8; N]);
impl<const N: usize> Frame1d<N> {
pub const LEN: usize = N;
#[must_use]
pub const fn new() -> Self {
Self([RGB8::new(0, 0, 0); N])
}
#[must_use]
pub const fn filled(color: RGB8) -> Self {
Self([color; N])
}
}
impl<const N: usize> Deref for Frame1d<N> {
type Target = [RGB8; N];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<const N: usize> DerefMut for Frame1d<N> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<const N: usize> From<[RGB8; N]> for Frame1d<N> {
fn from(array: [RGB8; N]) -> Self {
Self(array)
}
}
impl<const N: usize> From<Frame1d<N>> for [RGB8; N] {
fn from(frame: Frame1d<N>) -> Self {
frame.0
}
}
impl<const N: usize> Default for Frame1d<N> {
fn default() -> Self {
Self::new()
}
}
use core::borrow::Borrow;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::signal::Signal;
use embassy_time::Duration;
use heapless::Vec;
pub trait LedStrip<const N: usize> {
const LEN: usize = N;
const MAX_FRAMES: usize;
const MAX_BRIGHTNESS: u8;
fn write_frame(&self, frame: Frame1d<N>);
fn animate<I>(&self, frames: I)
where
I: IntoIterator,
I::Item: Borrow<(Frame1d<N>, embassy_time::Duration)>;
}
#[doc(hidden)]
pub type LedStripCommandSignal<const N: usize, const MAX_FRAMES: usize> =
Signal<CriticalSectionRawMutex, Command<N, MAX_FRAMES>>;
#[doc(hidden)]
#[derive(Clone)]
pub enum Command<const N: usize, const MAX_FRAMES: usize> {
DisplayStatic(Frame1d<N>),
Animate(Vec<(Frame1d<N>, Duration), MAX_FRAMES>),
}
#[doc(hidden)]
pub fn __write_frame<const N: usize, const MAX_FRAMES: usize>(
command_signal: &'static LedStripCommandSignal<N, MAX_FRAMES>,
frame: Frame1d<N>,
) {
command_signal.signal(Command::DisplayStatic(frame));
}
#[doc(hidden)]
pub fn __animate<const N: usize, const MAX_FRAMES: usize, I>(
command_signal: &'static LedStripCommandSignal<N, MAX_FRAMES>,
frames: I,
) where
I: IntoIterator,
I::Item: Borrow<(Frame1d<N>, Duration)>,
{
assert!(MAX_FRAMES > 0, "animation disabled (MAX_FRAMES = 0)");
let mut sequence: Vec<(Frame1d<N>, Duration), MAX_FRAMES> = Vec::new();
for item in frames {
let (frame, duration) = *item.borrow();
assert!(
duration.as_micros() > 0,
"animation frame duration must be positive"
);
sequence
.push((frame, duration))
.expect("animation sequence fits within MAX_FRAMES");
}
assert!(
!sequence.is_empty(),
"animation requires at least one frame"
);
command_signal.signal(Command::Animate(sequence));
}
#[doc(hidden)]
pub struct LedStripStatic<const N: usize, const MAX_FRAMES: usize> {
command_signal: LedStripCommandSignal<N, MAX_FRAMES>,
}
impl<const N: usize, const MAX_FRAMES: usize> LedStripStatic<N, MAX_FRAMES> {
#[must_use]
#[doc(hidden)]
pub const fn new_static() -> Self {
Self {
command_signal: Signal::new(),
}
}
#[doc(hidden)]
pub fn command_signal(&'static self) -> &'static LedStripCommandSignal<N, MAX_FRAMES> {
&self.command_signal
}
}
#[doc(hidden)]
pub fn apply_correction<const N: usize>(frame: &mut Frame1d<N>, combo_table: &[u8; 256]) {
frame.iter_mut().for_each(|pixel| {
pixel.r = combo_table[pixel.r as usize];
pixel.g = combo_table[pixel.g as usize];
pixel.b = combo_table[pixel.b as usize];
});
}