Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Added ws2812b driver and demo #5

Merged
merged 5 commits into from
Sep 7, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
256 changes: 141 additions & 115 deletions demos/applications/ws2812b.cpp
Original file line number Diff line number Diff line change
@@ -1,138 +1,164 @@
#include <array>
#include <span>

#include <libhal-display/ws2812b.hpp>
#include <libhal-util/serial.hpp>
#include <libhal-util/steady_clock.hpp>

#include <libhal-display/ws2812b.hpp>

#include <resource_list.hpp>

struct rgb888
{
hal::byte r;
hal::byte g;
hal::byte b;
};

template<std::size_t PixelCount>
void set_all_pixel(hal::display::ws2812b_spi_frame<PixelCount>& p_frame, hal::display::rgb888& p_pixel);
void set_all_pixel(hal::display::ws2812b_spi_frame<PixelCount>& p_frame,
rgb888& p_pixel);
template<std::size_t PixelCount>
void set_range_pixel(hal::display::ws2812b_spi_frame<PixelCount>& p_frame, hal::display::rgb888& p_pixel, uint8_t p_start_pixel, uint8_t p_end_pixel) ;
void set_range_ws2812b_frame_pixel(std::span<hal::byte>& p_frame,hal::display::rgb888 p_pixel);

void application (resource_list& p_map)
void set_range_pixel(hal::display::ws2812b_spi_frame<PixelCount>& p_frame,
rgb888& p_pixel,
uint8_t p_start_pixel,
uint8_t p_end_pixel);
void set_range_ws2812b_frame_pixel(std::span<hal::byte>& p_frame,
rgb888& p_pixel);

void application(resource_list& p_map)
{
using namespace hal::literals;

// Grab resources
auto& clock = *p_map.clock.value();
auto& console = *p_map.console.value();
auto& chip_select = *p_map.status_led.value();
auto& spi =*p_map.spi.value();

hal::print(console, "Demo Application Starting...\n\n");

// Set chip_select high
chip_select.level(true);

// Create ws2812b object
hal::display::ws2812b ws2812b_driver(spi, &chip_select);
// Create frame buffer for 5 pixels
constexpr std::size_t PixelCount = 5;
hal::display::ws2812b_spi_frame<PixelCount> spi_frame;
spi_frame.data.fill(0x00);

// Definining some RGB888 Colors as well as an array of colors
hal::display::rgb888 red_color = {255, 0, 0};
hal::display::rgb888 blue_color = {0, 0, 255};
hal::display::rgb888 green_color = {0, 255, 0};
std::array<hal::display::rgb888, 5> rainbow_array = {{
{255, 0, 0},
{255, 255, 0},
{0,255, 0},
{0, 0, 255},
{160, 32, 240}
}};

// Example of using the set_all_pixel function
hal::print(console, "Setting all pixels to the color red...\n");
set_all_pixel(spi_frame, red_color);
ws2812b_driver.update(spi_frame.data);
hal::delay(clock, std::chrono::milliseconds(3000));

// Example of using the set_range_pixel function
hal::print(console, "Setting pixel index 1-3 to the color green...\n");
set_range_pixel(spi_frame, green_color, 1, 3);
ws2812b_driver.update(spi_frame.data);
hal::delay(clock, std::chrono::milliseconds(3000));

// Example of using the set_range_ws2812b_frame_pixel function
hal::print(console, "Setting the span of bytes 0-23 (pixel index 0-1) to the color blue...\n");
std::span<hal::byte> frame_span(spi_frame.data);
std::span<hal::byte> sub_span = frame_span.subspan(0,24);
set_range_ws2812b_frame_pixel(sub_span, blue_color);
ws2812b_driver.update(spi_frame.data);
hal::delay(clock, std::chrono::milliseconds(3000));
// Example of a infinite scrolling array of colors
hal::print(console, "Starting rainbow loop...\n");
int current_color = 0;
while (true) {
using namespace std::literals;

set_range_pixel(spi_frame, rainbow_array[current_color], 0, 0);
set_range_pixel(spi_frame, rainbow_array[(current_color + 1) % 5], 1, 1);
set_range_pixel(spi_frame, rainbow_array[(current_color + 2) % 5], 2, 2);
set_range_pixel(spi_frame, rainbow_array[(current_color + 3) % 5], 3, 3);
set_range_pixel(spi_frame, rainbow_array[(current_color + 4) % 5], 4, 4);

current_color = (current_color + 1) % 5;
ws2812b_driver.update(spi_frame.data);
hal::delay(clock, 100ms);
}
using namespace hal::literals;

// Grab resources
auto& clock = *p_map.clock.value();
auto& console = *p_map.console.value();
auto& chip_select = *p_map.status_led.value();
auto& spi = *p_map.spi.value();

hal::print(console, "Demo Application Starting...\n\n");

// Set chip_select high
chip_select.level(true);

// Create ws2812b object
hal::display::ws2812b ws2812b_driver(spi, chip_select);

// Create frame buffer for 5 pixels
constexpr std::size_t PixelCount = 5;
hal::display::ws2812b_spi_frame<PixelCount> spi_frame;
spi_frame.data.fill(0x00);

// Definining some RGB888 Colors as well as an array of colors
rgb888 red_color = { 255, 0, 0 };
rgb888 blue_color = { 0, 0, 255 };
rgb888 green_color = { 0, 255, 0 };
std::array<rgb888, 5> rainbow_array = { { { 255, 0, 0 },
{ 255, 255, 0 },
{ 0, 255, 0 },
{ 0, 0, 255 },
{ 160, 32, 240 } } };

// Example of using the set_all_pixel function
hal::print(console, "Setting all pixels to the color red...\n");
set_all_pixel(spi_frame, red_color);
ws2812b_driver.update(spi_frame);
hal::delay(clock, std::chrono::milliseconds(3000));

// Example of using the set_range_pixel function
hal::print(console, "Setting pixel index 1-3 to the color green...\n");
set_range_pixel(spi_frame, green_color, 1, 3);
ws2812b_driver.update(spi_frame);
hal::delay(clock, std::chrono::milliseconds(3000));

// Example of using the set_range_ws2812b_frame_pixel function
hal::print(
console,
"Setting the span of bytes 0-23 (pixel index 0-1) to the color blue...\n");
std::span<hal::byte> frame_span(spi_frame.data);
std::span<hal::byte> sub_span = frame_span.subspan(0, 24);
set_range_ws2812b_frame_pixel(sub_span, blue_color);
ws2812b_driver.update(spi_frame);
hal::delay(clock, std::chrono::milliseconds(3000));

// Example of a infinite scrolling array of colors
hal::print(console, "Starting rainbow loop...\n");
int current_color = 0;
while (true) {
using namespace std::literals;

set_range_pixel(spi_frame, rainbow_array[current_color], 0, 0);
set_range_pixel(spi_frame, rainbow_array[(current_color + 1) % 5], 1, 1);
set_range_pixel(spi_frame, rainbow_array[(current_color + 2) % 5], 2, 2);
set_range_pixel(spi_frame, rainbow_array[(current_color + 3) % 5], 3, 3);
set_range_pixel(spi_frame, rainbow_array[(current_color + 4) % 5], 4, 4);

current_color = (current_color + 1) % 5;
ws2812b_driver.update(spi_frame);
hal::delay(clock, 100ms);
}
}

template<std::size_t PixelCount>
void set_all_pixel(hal::display::ws2812b_spi_frame<PixelCount>& p_frame, hal::display::rgb888& p_pixel) {
set_range_pixel(p_frame, p_pixel, 0, (PixelCount - 1));
void set_all_pixel(hal::display::ws2812b_spi_frame<PixelCount>& p_frame,
rgb888& p_pixel)
{
set_range_pixel(p_frame, p_pixel, 0, (PixelCount - 1));
}

template<std::size_t PixelCount>
void set_range_pixel(hal::display::ws2812b_spi_frame<PixelCount>& p_frame, hal::display::rgb888& p_pixel, uint8_t p_start_pixel, uint8_t p_end_pixel) {
void set_range_pixel(hal::display::ws2812b_spi_frame<PixelCount>& p_frame,
rgb888& p_pixel,
uint8_t p_start_pixel,
uint8_t p_end_pixel)
{

std::size_t num_pixels = (p_end_pixel - p_start_pixel + 1);
constexpr std::size_t bytes_per_pixel = 12;
std::size_t num_bytes = num_pixels * bytes_per_pixel;
std::size_t num_pixels = (p_end_pixel - p_start_pixel + 1);
constexpr std::size_t bytes_per_pixel = 12;
std::size_t num_bytes = num_pixels * bytes_per_pixel;

std::span<hal::byte> frame_span(p_frame.data);
std::span<hal::byte> sub_span = frame_span.subspan((p_start_pixel * bytes_per_pixel), num_bytes);
set_range_ws2812b_frame_pixel(sub_span, p_pixel);
std::span<hal::byte> frame_span(p_frame.data);
std::span<hal::byte> sub_span =
frame_span.subspan((p_start_pixel * bytes_per_pixel), num_bytes);
set_range_ws2812b_frame_pixel(sub_span, p_pixel);
}

void set_range_ws2812b_frame_pixel(std::span<hal::byte>& p_frame, hal::display::rgb888 p_pixel) {
static constexpr uint32_t bytes_per_pixel = 12;
hal::byte current_byte_within_pixel, shift_byte;

uint32_t pixel_count = p_frame.size() / bytes_per_pixel;
uint32_t formatted_color_data = p_pixel.g << 16 | p_pixel.r << 8 | p_pixel.b;

for (std::size_t current_pixel = 0; current_pixel < pixel_count; current_pixel++) {
current_byte_within_pixel = 0;
shift_byte = 1;

for (int bit_shift_amount = 23; bit_shift_amount >= 0; bit_shift_amount--) {
hal::byte formatted_data_current_bit = (formatted_color_data >> bit_shift_amount) & 0x01;
hal::byte bit_encoding = (formatted_data_current_bit == 1) ? 0b1110 : 0b1000;

if (shift_byte == 1) {
p_frame[current_pixel * bytes_per_pixel + current_byte_within_pixel] &= 0x0F;
p_frame[current_pixel * bytes_per_pixel + current_byte_within_pixel] |= (bit_encoding << 4);
}
else if (shift_byte == 0) {
p_frame[current_pixel * bytes_per_pixel + current_byte_within_pixel] &= 0xF0;
p_frame[current_pixel * bytes_per_pixel + current_byte_within_pixel] |= bit_encoding;
}

shift_byte = 1 - shift_byte;

if (shift_byte == 1) {
current_byte_within_pixel++;
}
}
void set_range_ws2812b_frame_pixel(std::span<hal::byte>& p_frame,
rgb888& p_pixel)
{
static constexpr uint32_t bytes_per_pixel = 12;
hal::byte current_byte_within_pixel, shift_byte;

uint32_t pixel_count = p_frame.size() / bytes_per_pixel;
uint32_t formatted_color_data = p_pixel.g << 16 | p_pixel.r << 8 | p_pixel.b;

for (std::size_t current_pixel = 0; current_pixel < pixel_count;
current_pixel++) {
current_byte_within_pixel = 0;
shift_byte = 1;

for (int bit_shift_amount = 23; bit_shift_amount >= 0; bit_shift_amount--) {
hal::byte formatted_data_current_bit =
(formatted_color_data >> bit_shift_amount) & 0x01;
hal::byte bit_encoding =
(formatted_data_current_bit == 1) ? 0b1110 : 0b1000;

if (shift_byte == 1) {
p_frame[current_pixel * bytes_per_pixel + current_byte_within_pixel] &=
0x0F;
p_frame[current_pixel * bytes_per_pixel + current_byte_within_pixel] |=
(bit_encoding << 4);
} else if (shift_byte == 0) {
p_frame[current_pixel * bytes_per_pixel + current_byte_within_pixel] &=
0xF0;
p_frame[current_pixel * bytes_per_pixel + current_byte_within_pixel] |=
bit_encoding;
}

shift_byte = 1 - shift_byte;

if (shift_byte == 1) {
current_byte_within_pixel++;
}
}
}
}
51 changes: 40 additions & 11 deletions include/libhal-display/ws2812b.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,43 +16,72 @@

#include <array>

#include <libhal-soft/inert_drivers/inert_output_pin.hpp>
#include <libhal-util/output_pin.hpp>
#include <libhal-util/spi.hpp>
#include <libhal-soft/inert_drivers/inert_output_pin.hpp>

namespace hal::display {

/**
* @brief Represents the frame of data for the ws2812b pixels to be transmitted
* over SPI.
*
* This struct is used to store the frame of color data that will be used to
* update the pixels values over SPI. It calculates the necessary size of the
* data array based on the number of pixels the user specifies.
*
* @tparam PixelCount - The number of pixels that are intended to be used.
*/
template<std::size_t PixelCount>
struct ws2812b_spi_frame
{
/// The three LEDs internal to each pixel: Red, Green, and Blue.
static constexpr std::size_t colors_available = 3;
/// The amount of bits used to represent each internal LEDs value.
static constexpr std::size_t bits_per_pixel_color = 8;
/// The amount of bits SPI needs to generate the proper pulses for the data.
static constexpr std::size_t spi_bits_to_encode_each_bit = 4;
/// Calculates the amount of bytes needed to store the data for one pixel.
static constexpr std::size_t bytes_to_store_one_pixels_data =
(colors_available * bits_per_pixel_color * spi_bits_to_encode_each_bit) / 8;

// Calculates the array size with the user specified amount of pixels.
static constexpr std::size_t array_length =
PixelCount * bytes_to_store_one_pixels_data;

// The array to store the pixels' data.
std::array<hal::byte, array_length> data;
};

struct rgb888
{
hal::byte r;
hal::byte g;
hal::byte b;
};

/**
* @brief Driver for the ws2812b individually addressable RGB LED strip
*
*/
class ws2812b
{
public:
ws2812b(hal::spi& p_spi, hal::output_pin* p_chip_select = &hal::soft::default_inert_output_pin());
/**
* @brief Construct a ws2812b driver.
*
* @param p_spi - The driver for the SPI bus the ws2812b is connected to.
* @param p_chip_select - The driver for the output pin to be used as the chip
* select if the devices data line is connected to a multiplexer/switch.
* Defaults to an inert output pin if one is not provided.
*/
50xp50 marked this conversation as resolved.
Show resolved Hide resolved
ws2812b(
hal::spi& p_spi,
hal::output_pin& p_chip_select = hal::soft::default_inert_output_pin());

50xp50 marked this conversation as resolved.
Show resolved Hide resolved
/**
* @brief Update the pixels to the currently stored color information.
*
* @tparam PixelCount - The amount of pixels the ws2812b device is using.
* @param p_spi_frame - The frame storing the pixels' color information.
*/
template<std::size_t PixelCount>
void update(std::array<hal::byte,PixelCount>& p_frame_data)
void update(ws2812b_spi_frame<PixelCount>& p_spi_frame)
{
update(std::span<hal::byte>(p_frame_data));
update(p_spi_frame.data);
}

private:
Expand Down
Loading