Skip to content

Commit c31688c

Browse files
authored
feat(neopixel): Add simple neopixel class wrapping RMT led encoding (#374)
* feat(neopixel): Add simple neopixel class wrapping RMT led encoding * update code format * update readme
1 parent a0e3633 commit c31688c

File tree

15 files changed

+409
-5
lines changed

15 files changed

+409
-5
lines changed

.github/workflows/build.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ jobs:
107107
target: esp32s3
108108
- path: 'components/mt6701/example'
109109
target: esp32s3
110+
- path: 'components/neopixel/example'
111+
target: esp32s3
110112
- path: 'components/nvs/example'
111113
target: esp32s3
112114
- path: 'components/pid/example'

components/led_strip/example/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,6 @@ set(
1616
"List of components to include"
1717
)
1818

19-
project(apa102_example)
19+
project(led_strip_example)
2020

2121
set(CMAKE_CXX_STANDARD 20)

components/led_strip/include/led_strip.hpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ class LedStrip : public BaseComponent {
139139
/// \note The index is zero-based.
140140
/// \sa Hsv for more information on the HSV color space
141141
/// \sa show
142-
void set_pixel(int index, Hsv hsv, float brightness = 1.0f) {
142+
void set_pixel(int index, const Hsv &hsv, float brightness = 1.0f) {
143143
set_pixel(index, hsv.rgb(), brightness);
144144
}
145145

@@ -150,7 +150,7 @@ class LedStrip : public BaseComponent {
150150
/// \note The index is zero-based.
151151
/// \sa Rgb for more information on the RGB color space
152152
/// \sa show
153-
void set_pixel(int index, Rgb rgb, float brightness = 1.0f) {
153+
void set_pixel(int index, const Rgb &rgb, float brightness = 1.0f) {
154154
uint8_t brightness_byte = std::clamp<uint8_t>(brightness * 31.0f, 0, 31);
155155
uint8_t r, g, b;
156156
r = std::clamp<uint8_t>(rgb.r * 255.0f, 0, 255);
@@ -209,14 +209,14 @@ class LedStrip : public BaseComponent {
209209
/// \note The index is zero-based.
210210
/// \sa set_pixel
211211
/// \sa show
212-
void set_all(Hsv hsv, float brightness = 1.0f) { set_all(hsv.rgb(), brightness); }
212+
void set_all(const Hsv &hsv, float brightness = 1.0f) { set_all(hsv.rgb(), brightness); }
213213

214214
/// \brief Set the color of all the LEDs
215215
/// \param rgb Color to set the LEDs to
216216
/// \param brightness Brightness of the LEDs
217217
/// \sa set_pixel
218218
/// \sa show
219-
void set_all(Rgb rgb, float brightness = 1.0f) {
219+
void set_all(const Rgb &rgb, float brightness = 1.0f) {
220220
uint8_t brightness_byte = std::clamp<uint8_t>(brightness * 255.0f, 0, 255);
221221
set_all(rgb.r, rgb.g, rgb.b, brightness_byte);
222222
}

components/neopixel/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
idf_component_register(
2+
INCLUDE_DIRS "include"
3+
SRC_DIRS "src"
4+
REQUIRES base_component color rmt
5+
)
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# The following lines of boilerplate have to be in your project's CMakeLists
2+
# in this exact order for cmake to work correctly
3+
cmake_minimum_required(VERSION 3.5)
4+
5+
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
6+
7+
# add the component directories that we want to use
8+
set(EXTRA_COMPONENT_DIRS
9+
"../../../components/"
10+
)
11+
12+
set(
13+
COMPONENTS
14+
"main esptool_py logger task neopixel"
15+
CACHE STRING
16+
"List of components to include"
17+
)
18+
19+
project(neopixel_example)
20+
21+
set(CMAKE_CXX_STANDARD 20)
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Neopixel Example
2+
3+
This example shows the use of the `Neopixel` class to control a an RGB
4+
LED (WS2812).
5+
6+
## How to use example
7+
8+
### Hardware Required
9+
10+
This example is designed to be run on a [QtPy
11+
ESP32-S3](https://www.adafruit.com/product/5426). It uses the QtPy's on-board
12+
neopixel.
13+
14+
15+
### Build and Flash
16+
17+
Build the project and flash it to the board, then run monitor tool to view serial output:
18+
19+
```
20+
idf.py -p PORT flash monitor
21+
```
22+
23+
(Replace PORT with the name of the serial port to use.)
24+
25+
(To exit the serial monitor, type ``Ctrl-]``.)
26+
27+
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
28+
29+
## Example Output
30+
31+
![CleanShot 2025-02-18 at 14 45 15](https://github.com/user-attachments/assets/6c0661b7-26c2-40c9-9b66-f8f528d22d7f)
32+
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
idf_component_register(SRC_DIRS "."
2+
INCLUDE_DIRS ".")
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#include <chrono>
2+
#include <vector>
3+
4+
#include "neopixel.hpp"
5+
#include "task.hpp"
6+
7+
using namespace std::chrono_literals;
8+
9+
extern "C" void app_main(void) {
10+
// create a logger
11+
espp::Logger logger({.tag = "Neopixel example", .level = espp::Logger::Verbosity::INFO});
12+
{
13+
logger.info("starting example");
14+
15+
//! [neopixel ex1]
16+
espp::Neopixel led({
17+
.data_gpio = 39, // Neopixel data pin on QtPy ESP32s3
18+
.power_gpio = 38, // Neopixel power pin on QtPy ESP32s3
19+
});
20+
21+
logger.info("Turning LED off for 1 second");
22+
led.set_color(espp::Rgb(0.0f, 0.0f, 0.0f));
23+
led.show();
24+
std::this_thread::sleep_for(1s);
25+
26+
logger.info("Setting LED to red for 1 second");
27+
led.set_color(espp::Rgb(1.0f, 0.0f, 0.0f));
28+
led.show();
29+
std::this_thread::sleep_for(1s);
30+
31+
logger.info("Setting LED to green for 1 second");
32+
led.set_color(espp::Rgb(0.0f, 1.0f, 0.0f));
33+
led.show();
34+
std::this_thread::sleep_for(1s);
35+
36+
logger.info("Setting LED to blue for 1 second");
37+
led.set_color(espp::Rgb(0.0f, 0.0f, 1.0f));
38+
led.show();
39+
std::this_thread::sleep_for(1s);
40+
41+
// Use a task to rotate the LED through the rainbow using HSV
42+
auto task_fn = [&led](std::mutex &m, std::condition_variable &cv) {
43+
static auto start = std::chrono::high_resolution_clock::now();
44+
auto now = std::chrono::high_resolution_clock::now();
45+
float t = std::chrono::duration<float>(now - start).count();
46+
// rotate through rainbow colors in hsv based on time, hue is 0-360
47+
float hue = (cos(t) * 0.5f + 0.5f) * 360.0f;
48+
espp::Hsv hsv(hue, 1.0f, 1.0f);
49+
// full brightness (1.0, default) is _really_ bright, so tone it down
50+
led.set_color(hsv);
51+
// show the new colors
52+
led.show();
53+
// NOTE: sleeping in this way allows the sleep to exit early when the
54+
// task is being stopped / destroyed
55+
{
56+
std::unique_lock<std::mutex> lk(m);
57+
cv.wait_for(lk, 50ms);
58+
}
59+
// don't want to stop the task
60+
return false;
61+
};
62+
63+
logger.info("Starting task to rotate LED through rainbow colors");
64+
auto task = espp::Task({.callback = task_fn,
65+
.task_config =
66+
{
67+
.name = "Neopixel Task",
68+
.stack_size_bytes = 5 * 1024,
69+
},
70+
.log_level = espp::Logger::Verbosity::WARN});
71+
task.start();
72+
//! [neopixel ex1]
73+
74+
while (true) {
75+
std::this_thread::sleep_for(100ms);
76+
}
77+
}
78+
79+
logger.info("example complete!");
80+
81+
while (true) {
82+
std::this_thread::sleep_for(1s);
83+
}
84+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
CONFIG_IDF_TARGET="esp32s3"
2+
3+
CONFIG_FREERTOS_HZ=1000
4+
5+
# ESP32-specific
6+
#
7+
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
8+
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=240
9+
10+
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
11+
CONFIG_ESPTOOLPY_FLASHSIZE="4MB"
12+
13+
# Common ESP-related
14+
#
15+
CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=4096
16+
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#pragma once
2+
3+
#include <mutex>
4+
5+
#include <driver/gpio.h>
6+
7+
#include "base_component.hpp"
8+
#include "color.hpp"
9+
#include "logger.hpp"
10+
#include "rmt.hpp"
11+
12+
namespace espp {
13+
/// \brief A class for controlling a Neopixel strip.
14+
/// \details This class provides a simple interface for controlling a Neopixel
15+
/// strip. It uses the RMT peripheral to send the data to the strip.
16+
///
17+
/// \section neopixel_ex1 Example 1: QtPy ESP32S3 Neopixel
18+
/// \snippet neopixel_example.cpp neopixel ex1
19+
class Neopixel {
20+
public:
21+
/// The configuration structure for the Neopixel.
22+
struct Config {
23+
int data_gpio; ///< The GPIO pin for the data line
24+
int power_gpio{-1}; ///< The GPIO pin for the power line (optional)
25+
size_t num_leds{1}; ///< The number of LEDs in the strip
26+
espp::Logger::Verbosity log_level{
27+
espp::Logger::Verbosity::WARN}; ///< Verbosity for the neopixel logger
28+
};
29+
30+
/// Constructor for the Neopixel.
31+
/// \param config The configuration structure for the Neopixel.
32+
explicit Neopixel(const espp::Neopixel::Config &config);
33+
34+
/// Get the number of LEDs in the strip.
35+
/// \return The number of LEDs in the strip.
36+
std::size_t num_leds() const { return config_.num_leds; }
37+
38+
/// Set the color of a single pixel.
39+
/// \param rgb The color to set the pixel to.
40+
/// \param index The index of the pixel to set.
41+
void set_color(const espp::Rgb &rgb, std::size_t index = 0);
42+
43+
/// Set the color of a single pixel.
44+
/// \param hsv The color to set the pixel to.
45+
/// \param index The index of the pixel to set.
46+
void set_color(const espp::Hsv &hsv, std::size_t index = 0) { set_color(hsv.rgb(), index); }
47+
48+
/// Set the color of all pixels.
49+
/// \param rgb The color to set all pixels to.
50+
void set_all(const espp::Rgb &rgb);
51+
52+
/// Set the color of all pixels.
53+
/// \param hsv The color to set all pixels to.
54+
void set_all(const espp::Hsv &hsv) { set_all(hsv.rgb()); }
55+
56+
/// Show the current pixel data.
57+
void show();
58+
59+
/// Set the power state of the strip.
60+
/// \param on The power state to set.
61+
/// \details If the power GPIO is set, this function will set the power
62+
/// state of the strip. If the power GPIO is not set, this function
63+
/// will do nothing.
64+
void set_power(bool on);
65+
66+
protected:
67+
static constexpr int WS2812_FREQ_HZ = 10'000'000;
68+
static constexpr int MICROS_PER_SEC = 1'000'000;
69+
70+
std::size_t encode(rmt_channel_handle_t channel, rmt_encoder_t *copy_encoder,
71+
rmt_encoder_t *bytes_encoder, const void *data, std::size_t data_size,
72+
rmt_encode_state_t *ret_state);
73+
74+
void configure_power_pin();
75+
76+
Config config_;
77+
std::shared_ptr<espp::Rmt> rmt_;
78+
int led_encoder_state_{0};
79+
std::vector<uint8_t> led_data_;
80+
}; // class Neopixel
81+
} // namespace espp

0 commit comments

Comments
 (0)