#![cfg_attr(not(test), no_std)]
pub mod grid;
use rgb::RGB8;
pub fn rgb_to_grb(rgb: RGB8) -> u32 {
((rgb.g as u32) << 16) | ((rgb.r as u32) << 8) | rgb.b as u32
}
pub fn color_to_bits(color: u32) -> [bool; 24] {
let mut bits = [false; 24];
for i in (0..24).rev() {
bits[23 - i] = (color >> i) & 1 != 0;
}
bits
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SpiEncodeError {
BufferTooSmall,
}
impl core::fmt::Display for SpiEncodeError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::BufferTooSmall => write!(f, "SPI output buffer too small"),
}
}
}
pub const fn spi_data_len(num_leds: usize) -> usize {
num_leds * 12
}
pub const SPI_RESET_BYTES_2MHZ: usize = 80;
const SPI_PATTERNS: [u8; 4] = [0x88, 0x8E, 0xE8, 0xEE];
pub fn prerender_spi(colors: &[RGB8], buf: &mut [u8]) -> Result<(), SpiEncodeError> {
let required = spi_data_len(colors.len());
if buf.len() < required {
return Err(SpiEncodeError::BufferTooSmall);
}
let mut pos = 0;
for &rgb in colors {
let grb = rgb_to_grb(rgb);
for shift in (0..24).step_by(2).rev() {
let pair = ((grb >> shift) & 0b11) as usize;
buf[pos] = SPI_PATTERNS[pair];
pos += 1;
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rgb_to_grb_red() {
let red = RGB8::new(255, 0, 0);
assert_eq!(rgb_to_grb(red), 0x00FF00);
}
#[test]
fn test_rgb_to_grb_green() {
let green = RGB8::new(0, 255, 0);
assert_eq!(rgb_to_grb(green), 0xFF0000);
}
#[test]
fn test_rgb_to_grb_blue() {
let blue = RGB8::new(0, 0, 255);
assert_eq!(rgb_to_grb(blue), 0x0000FF);
}
#[test]
fn test_rgb_to_grb_white() {
let white = RGB8::new(255, 255, 255);
assert_eq!(rgb_to_grb(white), 0xFFFFFF);
}
#[test]
fn test_rgb_to_grb_black() {
let black = RGB8::new(0, 0, 0);
assert_eq!(rgb_to_grb(black), 0x000000);
}
#[test]
fn test_rgb_to_grb_mixed() {
let color = RGB8::new(0x12, 0x34, 0x56);
assert_eq!(rgb_to_grb(color), 0x341256);
}
#[test]
fn test_color_to_bits_all_ones() {
let bits = color_to_bits(0xFFFFFF);
assert!(bits.iter().all(|&b| b));
}
#[test]
fn test_color_to_bits_all_zeros() {
let bits = color_to_bits(0x000000);
assert!(bits.iter().all(|&b| !b));
}
#[test]
fn test_color_to_bits_alternating() {
let bits = color_to_bits(0xAAAAAA);
for i in 0..24 {
assert_eq!(bits[i], i % 2 == 0, "bit {} should be {}", i, i % 2 == 0);
}
}
#[test]
fn test_color_to_bits_msb_first() {
let bits = color_to_bits(0x800000);
assert!(bits[0], "MSB should be set");
assert!(bits[1..].iter().all(|&b| !b), "all other bits should be 0");
}
#[test]
fn test_color_to_bits_lsb() {
let bits = color_to_bits(0x000001);
assert!(bits[23], "LSB should be set");
assert!(bits[..23].iter().all(|&b| !b), "all other bits should be 0");
}
#[test]
fn spi_data_len_zero() {
assert_eq!(spi_data_len(0), 0);
}
#[test]
fn spi_data_len_one() {
assert_eq!(spi_data_len(1), 12);
}
#[test]
fn spi_data_len_twelve() {
assert_eq!(spi_data_len(12), 144);
}
#[test]
fn prerender_spi_black() {
let colors = [RGB8::new(0, 0, 0)];
let mut buf = [0u8; 12];
prerender_spi(&colors, &mut buf).unwrap();
assert!(buf.iter().all(|&b| b == 0x88), "all-zero bits → 0x88");
}
#[test]
fn prerender_spi_white() {
let colors = [RGB8::new(255, 255, 255)];
let mut buf = [0u8; 12];
prerender_spi(&colors, &mut buf).unwrap();
assert!(buf.iter().all(|&b| b == 0xEE), "all-one bits → 0xEE");
}
#[test]
fn prerender_spi_red() {
let colors = [RGB8::new(255, 0, 0)];
let mut buf = [0u8; 12];
prerender_spi(&colors, &mut buf).unwrap();
let expected = [
0x88, 0x88, 0x88, 0x88, 0xEE, 0xEE, 0xEE, 0xEE, 0x88, 0x88, 0x88, 0x88, ];
assert_eq!(buf, expected);
}
#[test]
fn prerender_spi_green() {
let colors = [RGB8::new(0, 255, 0)];
let mut buf = [0u8; 12];
prerender_spi(&colors, &mut buf).unwrap();
let expected = [
0xEE, 0xEE, 0xEE, 0xEE, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, ];
assert_eq!(buf, expected);
}
#[test]
fn prerender_spi_blue() {
let colors = [RGB8::new(0, 0, 255)];
let mut buf = [0u8; 12];
prerender_spi(&colors, &mut buf).unwrap();
let expected = [
0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0xEE, 0xEE, 0xEE, 0xEE, ];
assert_eq!(buf, expected);
}
#[test]
fn prerender_spi_mixed() {
let colors = [RGB8::new(0x12, 0x34, 0x56)];
let mut buf = [0u8; 12];
prerender_spi(&colors, &mut buf).unwrap();
let expected = [
0x88, 0xEE, 0x8E, 0x88, 0x88, 0x8E, 0x88, 0xE8, 0x8E, 0x8E, 0x8E, 0xE8, ];
assert_eq!(buf, expected);
}
#[test]
fn prerender_spi_buffer_too_small() {
let colors = [RGB8::new(0, 0, 0)];
let mut buf = [0u8; 11];
assert_eq!(
prerender_spi(&colors, &mut buf),
Err(SpiEncodeError::BufferTooSmall)
);
}
#[test]
fn prerender_spi_exact_buffer() {
let colors = [RGB8::new(0, 0, 0)];
let mut buf = [0u8; 12];
assert!(prerender_spi(&colors, &mut buf).is_ok());
}
#[test]
fn prerender_spi_empty() {
let colors: &[RGB8] = &[];
let mut buf: [u8; 0] = [];
assert!(prerender_spi(colors, &mut buf).is_ok());
}
#[test]
fn prerender_spi_multiple_leds() {
let colors = [
RGB8::new(255, 0, 0),
RGB8::new(0, 255, 0),
RGB8::new(0, 0, 255),
];
let mut buf = [0u8; 36];
prerender_spi(&colors, &mut buf).unwrap();
assert_eq!(buf.len(), 36);
assert_eq!(&buf[0..4], &[0x88, 0x88, 0x88, 0x88]);
assert_eq!(&buf[4..8], &[0xEE, 0xEE, 0xEE, 0xEE]);
assert_eq!(&buf[8..12], &[0x88, 0x88, 0x88, 0x88]);
assert_eq!(&buf[24..28], &[0x88, 0x88, 0x88, 0x88]);
assert_eq!(&buf[28..32], &[0x88, 0x88, 0x88, 0x88]);
assert_eq!(&buf[32..36], &[0xEE, 0xEE, 0xEE, 0xEE]);
}
#[test]
fn prerender_spi_oversized_buffer() {
let colors = [RGB8::new(0, 0, 0)];
let mut buf = [0xFFu8; 20];
prerender_spi(&colors, &mut buf).unwrap();
assert!(buf[..12].iter().all(|&b| b == 0x88));
assert!(buf[12..].iter().all(|&b| b == 0xFF));
}
#[test]
fn prerender_spi_conformance_ws2812_spi() {
let colors = [RGB8::new(0xCA, 0xFE, 0x42)];
let mut buf = [0u8; 12];
prerender_spi(&colors, &mut buf).unwrap();
#[rustfmt::skip]
let expected: [u8; 12] = [
0xEE, 0xEE, 0xEE, 0xE8, 0xEE, 0x88, 0xE8, 0xE8, 0x8E, 0x88, 0x88, 0xE8, ];
assert_eq!(
buf, expected,
"must match ws2812-spi v0.5.1 write_byte output"
);
}
}