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

✨ Add driver for apa102 LEDs #6

Merged
merged 7 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions .clang-format
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ FixNamespaceComments: true
SpacesBeforeTrailingComments: 2
ColumnLimit: 80
QualifierAlignment: Right
InsertNewlineAtEOF: true
11 changes: 11 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,19 @@ libhal_test_and_make_library(

SOURCES
src/display.cpp
src/apa102.cpp

TEST_SOURCES
tests/display.test.cpp
tests/main.test.cpp
tests/apa102.test.cpp

INCLUDES
.

LINK_LIBRARIES
libhal::soft

PACKAGES
libhal-soft
)
3 changes: 2 additions & 1 deletion conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ def requirements(self):
# consumers get the libhal and libhal-util headers downstream.
bootstrap = self.python_requires["libhal-bootstrap"]
bootstrap.module.add_library_requirements(self)

self.requires("libhal-soft/[^5.3.0]")

def package_info(self):
self.cpp_info.libs = ["libhal-display"]
self.cpp_info.set_property("cmake_target_name", "libhal::display")
1 change: 1 addition & 0 deletions demos/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ project(demos LANGUAGES CXX)
libhal_build_demos(
DEMOS
display
apa102

INCLUDES
.
Expand Down
148 changes: 148 additions & 0 deletions demos/applications/apa102.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// Copyright 2024 Khalil Estell
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

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

#include "../resource_list.hpp"

hal::byte build_brightness_byte(unsigned p_brightness)
{
hal::byte starting_bits = 0b11100000;
if (p_brightness > 31) {
p_brightness = 31;
}
return (starting_bits | p_brightness);
}

template<std::size_t PixelCount>
void update_single(hal::display::apa102_pixel& p_rgb,
unsigned p_brightness,
size_t p_led_number,
hal::display::apa102_frame<PixelCount>& p_led_frames)
{
if (p_led_number < PixelCount) {
p_led_frames.pixels[p_led_number].brightness =
build_brightness_byte(p_brightness);
p_led_frames.pixels[p_led_number].blue = p_rgb.blue;
p_led_frames.pixels[p_led_number].green = p_rgb.green;
p_led_frames.pixels[p_led_number].red = p_rgb.red;
}
}

template<std::size_t PixelCount>
void update_all(std::span<hal::display::apa102_pixel> p_leds,
unsigned p_brightness,
hal::display::apa102_frame<PixelCount>& p_led_frames)
{
for (size_t i = 0; i < p_leds.size(); i++) {
update_single(p_leds[i], p_brightness, i, p_led_frames);
}
}

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

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

// variables for LEDs
constexpr std::size_t led_count = 4;
hal::display::apa102_frame<led_count> apa_frame;
unsigned brightness = 1;

// predefined colors
hal::display::apa102_pixel red{ .blue = 0x00, .green = 0x00, .red = 0xFF };
hal::display::apa102_pixel green{ .blue = 0x00, .green = 0xFF, .red = 0x00 };
hal::display::apa102_pixel blue{ .blue = 0xFF, .green = 0x00, .red = 0x00 };
hal::display::apa102_pixel white{ .blue = 0xFF, .green = 0xFF, .red = 0xFF };
std::array<hal::display::apa102_pixel, led_count> all_off{};

std::array<hal::display::apa102_pixel, led_count> rgb_array = {
{ white, blue, green, red }
};

hal::print(console, "Demo Application Starting...\n\n");
hal::display::apa102 led_strip(spi, chip_select);
while (true) {
// reset LEDs by turning them all off
update_all(all_off, brightness, apa_frame);

// update one at a time, all other LEDs should remain the same state
// added delays to visually see individual activations
hal::print(console, "Updating single LEDS\n");
update_single(red, brightness, 0, apa_frame);
led_strip.update(apa_frame);
hal::delay(clock, 500ms);
update_single(green, brightness, 1, apa_frame);
led_strip.update(apa_frame);
hal::delay(clock, 500ms);
update_single(blue, brightness, 2, apa_frame);
led_strip.update(apa_frame);
hal::delay(clock, 500ms);
update_single(white, brightness, 3, apa_frame);
led_strip.update(apa_frame);

// update all LEDs at once
hal::delay(clock, 3s);
hal::print(console, "Updating all LEDS\n");
update_all(rgb_array, brightness, apa_frame);
led_strip.update(apa_frame);

// cycle through RGB colors, start and end with red
hal::delay(clock, 3s);
hal::display::apa102_pixel rainbow = red;
hal::print(console, "Rainbow Cycle\n");
for (int i = 0; i <= 255; i++) {
// decrease red, increase blue
rainbow.red = 255 - i;
rainbow.blue = i;
std::array<hal::display::apa102_pixel, led_count> rainbow_array = {
{ rainbow, rainbow, rainbow, rainbow }
};
update_all(rainbow_array, brightness, apa_frame);
led_strip.update(apa_frame);
hal::delay(clock, 10ms);
}
for (int i = 0; i <= 255; i++) {
// decrease blue, increase green
rainbow.blue = 255 - i;
rainbow.green = i;
std::array<hal::display::apa102_pixel, led_count> rainbow_array = {
{ rainbow, rainbow, rainbow, rainbow }
};
update_all(rainbow_array, brightness, apa_frame);
led_strip.update(apa_frame);
hal::delay(clock, 10ms);
}
for (int i = 0; i <= 255; i++) {
// decrease green, increase red
rainbow.green = 255 - i;
rainbow.red = i;
std::array<hal::display::apa102_pixel, led_count> rainbow_array = {
{ rainbow, rainbow, rainbow, rainbow }
};
update_all(rainbow_array, brightness, apa_frame);
led_strip.update(apa_frame);
hal::delay(clock, 10ms);
}
}
hal::delay(clock, 50ms);
}
51 changes: 42 additions & 9 deletions demos/platforms/stm32f103c8.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,23 @@
#include <libhal-armcortex/startup.hpp>
#include <libhal-armcortex/system_control.hpp>

#include <libhal-stm32f1/clock.hpp>
#include <libhal-stm32f1/constants.hpp>
#include <libhal-stm32f1/output_pin.hpp>
#include <libhal-stm32f1/uart.hpp>
#include <libhal-arm-mcu/stm32f1/clock.hpp>
#include <libhal-arm-mcu/stm32f1/constants.hpp>
#include <libhal-arm-mcu/stm32f1/input_pin.hpp>
#include <libhal-arm-mcu/stm32f1/output_pin.hpp>
#include <libhal-arm-mcu/stm32f1/spi.hpp>
#include <libhal-arm-mcu/stm32f1/uart.hpp>
#include <libhal-soft/bit_bang_spi.hpp>
#include <libhal-util/steady_clock.hpp>

#include <resource_list.hpp>

resource_list initialize_platform()
{
using namespace hal::literals;

constexpr bool use_bit_bang_spi = true;

// Set the MCU to the maximum clock speed
hal::stm32f1::maximum_speed_using_internal_oscillator();

Expand All @@ -41,12 +47,39 @@ resource_list initialize_platform()
.baud_rate = 115200,
});

auto cpu_frequency = hal::stm32f1::frequency(hal::stm32f1::peripheral::cpu);
static hal::cortex_m::dwt_counter steady_clock(cpu_frequency);
static hal::stm32f1::output_pin led('C', 13);
static hal::stm32f1::output_pin spi_chip_select('A', 4);
spi_chip_select.level(true);

return {
.reset = +[]() { hal::cortex_m::reset(); },
.console = &uart1,
.clock = &counter,
.status_led = &led,
hal::spi* spi = nullptr;
static hal::spi::settings spi_settings{
.clock_rate = 250.0_kHz,
.clock_polarity = false,
.clock_phase = true,
};

if constexpr (use_bit_bang_spi) {
static hal::stm32f1::output_pin sck('A', 5);
static hal::stm32f1::output_pin copi('A', 6);
static hal::stm32f1::input_pin cipo('A', 7);
static hal::soft::bit_bang_spi::pins bit_bang_spi_pins{ .sck = &sck,
.copi = &copi,
.cipo = &cipo };

static hal::soft::bit_bang_spi bit_bang_spi(
bit_bang_spi_pins, steady_clock, spi_settings);
spi = &bit_bang_spi;
} else {
static hal::stm32f1::spi spi1(hal::bus<1>, spi_settings);
spi = &spi1;
}

return { .reset = +[]() { hal::cortex_m::reset(); },
.console = &uart1,
.clock = &counter,
.status_led = &led,
.spi = spi,
.spi_chip_select = &spi_chip_select };
}
3 changes: 3 additions & 0 deletions demos/resource_list.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <libhal/functional.hpp>
#include <libhal/output_pin.hpp>
#include <libhal/serial.hpp>
#include <libhal/spi.hpp>
#include <libhal/steady_clock.hpp>

struct resource_list
Expand All @@ -27,6 +28,8 @@ struct resource_list
std::optional<hal::serial*> console;
std::optional<hal::steady_clock*> clock;
std::optional<hal::output_pin*> status_led;
std::optional<hal::spi*> spi = std::nullopt;
std::optional<hal::output_pin*> spi_chip_select = std::nullopt;
MaliaLabor marked this conversation as resolved.
Show resolved Hide resolved
// Add more driver interfaces here ...
};

Expand Down
86 changes: 86 additions & 0 deletions include/libhal-display/apa102.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright 2024 Khalil Estell
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#pragma once

#include <array>
#include <span>

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

namespace hal::display {

struct apa102_pixel
{
/// bits 7 - 5 must be all 1's, otherwise undefined behavior
hal::byte brightness = 0b1111'1111;
hal::byte blue = 0;
hal::byte green = 0;
hal::byte red = 0;
};

static_assert(4U == sizeof(apa102_pixel),
"APA102 Pixel structure must be 4 bytes in length");
/**
* @brief Contains data to send over SPI and information about size of the data
* to send over
*
* @tparam pixel_count - Number of pixels to control
*/
template<std::size_t pixel_count>
struct apa102_frame
{
std::array<apa102_pixel, pixel_count> pixels;
};

/**
* @brief Driver for apa102 RGB LEDs
*
*/
class apa102
{
public:
/**
* @brief Construct a new apa102 object
*
* @param p_spi the spi bus that controls the LEDs
* @param p_chip_select output pin acting as the chip select for the spi bus
*/
apa102(
hal::spi& p_spi,
hal::output_pin& p_chip_select = hal::soft::default_inert_output_pin());

/**
* @brief Update the state of the LEDs
*
* @tparam pixel_count - Number of pixels to control is set implicitly, user
* should not set it manually
* @param p_spi_frame spi frame to send to control LEDs
*/
template<std::size_t pixel_count>
void update(apa102_frame<pixel_count>& p_spi_frame)
{
update(p_spi_frame.pixels);
}

private:
void update(std::span<apa102_pixel> p_data);
MaliaLabor marked this conversation as resolved.
Show resolved Hide resolved

hal::spi* m_spi;

hal::output_pin* m_chip_select;
};
} // namespace hal::display
26 changes: 26 additions & 0 deletions src/apa102.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#include <libhal-display/apa102.hpp>

#include <libhal-util/as_bytes.hpp>
#include <libhal-util/spi.hpp>
#include <span>

namespace hal::display {

apa102::apa102(hal::spi& p_spi, hal::output_pin& p_chip_select)
: m_spi(&p_spi)
, m_chip_select(&p_chip_select)
{
// 1 MHz is max speed LEDs can handle
m_spi->configure(hal::spi::settings{ 1.0_MHz, { false }, { false } });
}

// public
void apa102::update(std::span<apa102_pixel> p_pixels)
{
m_chip_select->level(false);
hal::write(*m_spi, std::array<hal::byte, 4>{ 0x00, 0x00, 0x00, 0x00 });
hal::write(*m_spi, hal::as_bytes(p_pixels));
hal::write(*m_spi, std::array<hal::byte, 4>{ 0xFF, 0xFF, 0xFF, 0xFF });
m_chip_select->level(true);
}
} // namespace hal::display
Loading