Skip to content

Commit

Permalink
Merge pull request #2 from 0xb-s/feat-allow-user-to-set-number-of-led
Browse files Browse the repository at this point in the history
Feat: allow user to set number of led
  • Loading branch information
0xb-s authored Sep 18, 2024
2 parents e8a5405 + cac0250 commit 271f74f
Show file tree
Hide file tree
Showing 4 changed files with 246 additions and 91 deletions.
97 changes: 67 additions & 30 deletions src/drivers/apa102.rs
Original file line number Diff line number Diff line change
@@ -1,58 +1,95 @@
use embedded_hal_async::spi::SpiBus;
use smart_leds::RGB8;

use crate::drivers::LedDriver;

pub struct Apa102<SPI: SpiBus<u8>> {
/// APA102 LED Driver supporting an arbitrary number of LEDs.
///
/// The caller is responsible for providing a buffer of appropriate size.
/// The buffer size should be:
/// `start_frame_size + (num_leds * led_frame_size) + end_frame_size`,
/// where:
/// - `start_frame_size` = 4 bytes (always zero).
/// - `led_frame_size` = 4 bytes per LED (1 for brightness, 3 for RGB data).
/// - `end_frame_size` = `(num_leds + 15) / 16` bytes.
pub struct Apa102<'a, SPI: SpiBus<u8>> {
spi: SPI,
buffer: [u8; MAX_BUFFER_SIZE],
num_leds: usize,
buffer: &'a mut [u8],
}

const MAX_LEDS: usize = 60; // Adjust as needed
const START_FRAME_SIZE: usize = 4;
const LED_FRAME_SIZE: usize = 4;
const END_FRAME_SIZE: usize = (MAX_LEDS + 15) / 16;
const MAX_BUFFER_SIZE: usize = START_FRAME_SIZE + (MAX_LEDS * LED_FRAME_SIZE) + END_FRAME_SIZE;
impl<'a, SPI: SpiBus<u8>> Apa102<'a, SPI> {
/// Creates a new APA102 driver with the given SPI bus, number of LEDs, and buffer.
///
/// # Arguments
///
/// * `spi` - The SPI bus instance.
/// * `num_leds` - The number of LEDs to control.
/// * `buffer` - A mutable slice that must be large enough to hold the frame data.
///
/// # Panics
///
/// This function will panic if the provided buffer is too small to hold all frame data.
pub fn new(spi: SPI, num_leds: usize, buffer: &'a mut [u8]) -> Self {
let start_frame_size = 4;
let led_frame_size = 4;
let end_frame_size = (num_leds + 15) / 16;
let total_size = start_frame_size + (num_leds * led_frame_size) + end_frame_size;

assert!(
buffer.len() >= total_size,
"Buffer too small: required {}, provided {}",
total_size,
buffer.len()
);

impl<SPI: SpiBus<u8>> Apa102<SPI> {
/// Creates a new APA102 driver with the given SPI bus.
pub fn new(spi: SPI) -> Self {
Self {
spi,
buffer: [0; MAX_BUFFER_SIZE],
num_leds,
buffer,
}
}
}

impl<SPI: SpiBus<u8>> LedDriver<RGB8> for Apa102<SPI> {
impl<'a, SPI: SpiBus<u8>> LedDriver<RGB8> for Apa102<'a, SPI> {
type Error = SPI::Error;

/// Writes the RGB data to the LED strip.
///
/// # Arguments
///
/// * `colors` - A slice of RGB8 colors to write to the strip. This function will write
/// up to the number of LEDs configured at creation time (`num_leds`).
///
/// # Returns
///
/// Returns a `Result` indicating whether the write was successful.
async fn write(&mut self, colors: &[RGB8]) -> Result<(), Self::Error> {
let num_leds = core::cmp::min(colors.len(), MAX_LEDS);
let num_leds = core::cmp::min(colors.len(), self.num_leds);
let end_frame_size = (num_leds + 15) / 16;

// Prepare the buffer.
let buffer = &mut self.buffer[..];
// Frame sizes
let start_frame_size = 4;
let led_frame_size = 4;
let total_size = start_frame_size + (num_leds * led_frame_size) + end_frame_size;

// Start frame
buffer[..START_FRAME_SIZE].fill(0x00);
// Start frame: all zeros
self.buffer[..start_frame_size].fill(0x00);

// LED frames
for (i, &RGB8 { r, g, b }) in colors.iter().enumerate().take(num_leds) {
let offset = START_FRAME_SIZE + i * LED_FRAME_SIZE;
// Data frame: 0xE0 | brightness (set to max 0x1F), then B, G, R.
buffer[offset] = 0xE0 | 0x1F; // Global brightness set to max (0x1F)
buffer[offset + 1] = b;
buffer[offset + 2] = g;
buffer[offset + 3] = r;
let offset = start_frame_size + i * led_frame_size;
// Data frame: 0xE0 | brightness (set to max 0x1F), followed by B, G, R.
self.buffer[offset] = 0xE0 | 0x1F; // Global brightness set to max (0x1F)
self.buffer[offset + 1] = b; // Blue channel
self.buffer[offset + 2] = g; // Green channel
self.buffer[offset + 3] = r; // Red channel
}

// End frame
let end_frame_offset = START_FRAME_SIZE + num_leds * LED_FRAME_SIZE;
buffer[end_frame_offset..end_frame_offset + end_frame_size].fill(0x00);
// End frame: all zeros
let end_frame_offset = start_frame_size + num_leds * led_frame_size;
self.buffer[end_frame_offset..end_frame_offset + end_frame_size].fill(0x00);

// Write the data
let total_size = START_FRAME_SIZE + num_leds * LED_FRAME_SIZE + end_frame_size;
self.spi.write(&buffer[..total_size]).await
// Write the data via SPI
self.spi.write(&self.buffer[..total_size]).await
}
}
96 changes: 68 additions & 28 deletions src/drivers/lpd8806.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,54 +2,94 @@ use crate::drivers::LedDriver;
use embedded_hal_async::spi::SpiBus;
use smart_leds::RGB8;

pub struct Lpd8806<SPI: SpiBus<u8>> {
/// LPD8806 LED Driver supporting an arbitrary number of LEDs.
///
/// The caller is responsible for providing a buffer of appropriate size.
/// The buffer size should be:
/// `start_frame_size + (num_leds * led_frame_size) + end_frame_size`,
/// where:
/// - `start_frame_size` = 4 bytes (always zero).
/// - `led_frame_size` = 3 bytes per LED (each color component is 7 bits, MSB is always 1).
/// - `end_frame_size` = `(num_leds + 31) / 32` bytes.
pub struct Lpd8806<'a, SPI: SpiBus<u8>> {
spi: SPI,
buffer: [u8; MAX_BUFFER_SIZE],
num_leds: usize,
buffer: &'a mut [u8],
}

const MAX_LEDS: usize = 60; // Adjust as needed
const START_FRAME_SIZE: usize = 4;
const LED_FRAME_SIZE: usize = 3;
const END_FRAME_SIZE: usize = (MAX_LEDS + 31) / 32;
const MAX_BUFFER_SIZE: usize = START_FRAME_SIZE + (MAX_LEDS * LED_FRAME_SIZE) + END_FRAME_SIZE;
impl<'a, SPI: SpiBus<u8>> Lpd8806<'a, SPI> {
/// Creates a new LPD8806 driver with the given SPI bus, number of LEDs, and buffer.
///
/// # Arguments
///
/// * `spi` - The SPI bus instance.
/// * `num_leds` - The number of LEDs to control.
/// * `buffer` - A mutable slice that must be large enough to hold the frame data.
///
/// # Panics
///
/// This function will panic if the provided buffer is too small to hold all frame data.
pub fn new(spi: SPI, num_leds: usize, buffer: &'a mut [u8]) -> Self {
let start_frame_size = 4;
let led_frame_size = 3;
let end_frame_size = (num_leds + 31) / 32;
let total_size = start_frame_size + (num_leds * led_frame_size) + end_frame_size;

assert!(
buffer.len() >= total_size,
"Buffer too small: required {}, provided {}",
total_size,
buffer.len()
);

impl<SPI: SpiBus<u8>> Lpd8806<SPI> {
/// Creates a new LPD8806 driver with the given SPI bus.
pub fn new(spi: SPI) -> Self {
Self {
spi,
buffer: [0; MAX_BUFFER_SIZE],
num_leds,
buffer,
}
}
}

impl<SPI: SpiBus<u8>> LedDriver<RGB8> for Lpd8806<SPI> {
impl<'a, SPI: SpiBus<u8>> LedDriver<RGB8> for Lpd8806<'a, SPI> {
type Error = SPI::Error;

/// Writes the RGB data to the LED strip.
///
/// # Arguments
///
/// * `colors` - A slice of RGB8 colors to write to the strip. This function will write
/// up to the number of LEDs configured at creation time (`num_leds`).
///
/// # Returns
///
/// Returns a `Result` indicating whether the write was successful.
async fn write(&mut self, colors: &[RGB8]) -> Result<(), Self::Error> {
let num_leds = core::cmp::min(colors.len(), MAX_LEDS);
let num_leds = core::cmp::min(colors.len(), self.num_leds);
let end_frame_size = (num_leds + 31) / 32;

// Prepare the buffer.
let buffer = &mut self.buffer[..];
// Frame sizes
let start_frame_size = 4;
let led_frame_size = 3;
let total_size = start_frame_size + (num_leds * led_frame_size) + end_frame_size;

// Start frame
buffer[..START_FRAME_SIZE].fill(0x00);
// Start frame: all zeros
self.buffer[..start_frame_size].fill(0x00);

// LED frames
for (i, &RGB8 { r, g, b }) in colors.iter().enumerate().take(num_leds) {
let offset = START_FRAME_SIZE + i * LED_FRAME_SIZE;
// Each color is 7 bits with the highest bit set to 1
buffer[offset] = (g >> 1) | 0x80;
buffer[offset + 1] = (r >> 1) | 0x80;
buffer[offset + 2] = (b >> 1) | 0x80;
let offset = start_frame_size + i * led_frame_size;
// Each color component is 7 bits with the highest bit set to 1.
// This is specific to the LPD8806 protocol.
self.buffer[offset] = (g >> 1) | 0x80; // Green channel (7 bits, MSB set to 1)
self.buffer[offset + 1] = (r >> 1) | 0x80; // Red channel (7 bits, MSB set to 1)
self.buffer[offset + 2] = (b >> 1) | 0x80; // Blue channel (7 bits, MSB set to 1)
}

// End frame
let end_frame_offset = START_FRAME_SIZE + num_leds * LED_FRAME_SIZE;
buffer[end_frame_offset..end_frame_offset + END_FRAME_SIZE].fill(0x00);
// End frame: all zeros
let end_frame_offset = start_frame_size + num_leds * led_frame_size;
self.buffer[end_frame_offset..end_frame_offset + end_frame_size].fill(0x00);

// Write the data
let total_size = START_FRAME_SIZE + num_leds * LED_FRAME_SIZE + END_FRAME_SIZE;
self.spi.write(&buffer[..total_size]).await
// Write the data via SPI
self.spi.write(&self.buffer[..total_size]).await
}
}
64 changes: 48 additions & 16 deletions src/drivers/sk6812.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,52 @@ use crate::encoding::encode_rgbw8_to_spi_data;
use crate::encoding::RGBW8;
use embedded_hal_async::spi::SpiBus;

pub struct Sk6812<SPI: SpiBus<u8>> {
/// SK6812 LED Driver supporting an arbitrary number of LEDs.
///
/// The caller is responsible for providing a buffer of appropriate size.
/// The buffer size should be:
/// `data_size + reset_size`,
/// where:
/// - `data_size` = `num_leds * 4` (4 bytes per LED for RGBW encoding)
/// - `reset_size` = Sufficient size for the reset signal based on SPI timing requirements
pub struct Sk6812<'a, SPI: SpiBus<u8>> {
spi: SPI,
color_order: ColorOrder,
buffer: [u8; MAX_BUFFER_SIZE],
num_leds: usize,
buffer: &'a mut [u8],
}

const MAX_LEDS: usize = 60;
const MAX_BUFFER_SIZE: usize = MAX_LEDS * 16;
impl<'a, SPI: SpiBus<u8>> Sk6812<'a, SPI> {
/// Creates a new SK6812 driver with the given SPI bus, number of LEDs, and buffer.
///
/// # Arguments
///
/// * `spi` - The SPI bus instance.
/// * `num_leds` - The number of LEDs to control.
/// * `buffer` - A mutable slice that must be large enough to hold all frames.
///
/// # Panics
///
/// Panics if the provided buffer is too small.
pub fn new(spi: SPI, num_leds: usize, buffer: &'a mut [u8]) -> Self {
// Each LED requires 4 bytes (RGBW).
let data_size = num_leds * 4;
// Reset signal: At least 80 microseconds low. Assuming a 1 MHz clock, this is about 10 bytes.
let reset_size = 10;
let total_size = data_size + reset_size;

assert!(
buffer.len() >= total_size,
"Buffer too small: required {}, provided {}",
total_size,
buffer.len()
);

impl<SPI: SpiBus<u8>> Sk6812<SPI> {
/// Creates a new SK6812 driver with the given SPI bus.
/// Colors default to RGB order.
pub fn new(spi: SPI) -> Self {
Self {
spi,
color_order: ColorOrder::RGB,
buffer: [0; MAX_BUFFER_SIZE],
num_leds,
buffer,
}
}

Expand All @@ -30,12 +59,14 @@ impl<SPI: SpiBus<u8>> Sk6812<SPI> {
}
}

impl<SPI: SpiBus<u8>> LedDriver<RGBW8> for Sk6812<SPI> {
impl<'a, SPI: SpiBus<u8>> LedDriver<RGBW8> for Sk6812<'a, SPI> {
type Error = SPI::Error;

async fn write(&mut self, colors: &[RGBW8]) -> Result<(), Self::Error> {
let num_leds = core::cmp::min(colors.len(), MAX_LEDS);
let data_size = num_leds * 16;
let num_leds = core::cmp::min(colors.len(), self.num_leds);
let data_size = num_leds * 4; // 4 bytes per LED for RGBW encoding
let reset_size = 10; // (80µs reset signal)
let total_size = data_size + reset_size;

// Encode colors into the buffer
encode_rgbw8_to_spi_data(
Expand All @@ -44,11 +75,12 @@ impl<SPI: SpiBus<u8>> LedDriver<RGBW8> for Sk6812<SPI> {
&mut self.buffer[..data_size],
);

// Write the data to SPI
// Write the color data to SPI
self.spi.write(&self.buffer[..data_size]).await?;

// Write reset signal.
let reset_signal = [0_u8; 140];
self.spi.write(&reset_signal).await
// Write reset signal (at least 80µs low, which can be achieved by sending a few zero bytes)
let reset_signal = &mut self.buffer[data_size..total_size];
reset_signal.fill(0x00); // Reset signal is just zeros
self.spi.write(reset_signal).await
}
}
Loading

0 comments on commit 271f74f

Please sign in to comment.