Skip to content

Commit

Permalink
Merge pull request #64 from esp-cpp/example/led-strip-update
Browse files Browse the repository at this point in the history
Example/led strip update
  • Loading branch information
finger563 authored Jun 4, 2023
2 parents c9c57e0 + 0ee27ad commit a0a5c6a
Show file tree
Hide file tree
Showing 76 changed files with 378 additions and 231 deletions.
2 changes: 1 addition & 1 deletion components/led_strip/example/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ set(EXTRA_COMPONENT_DIRS

set(
COMPONENTS
"main esptool_py driver logger task led_strip"
"main esptool_py driver logger task rmt led_strip"
CACHE STRING
"List of components to include"
)
Expand Down
199 changes: 132 additions & 67 deletions components/led_strip/example/main/led_strip_example.cpp
Original file line number Diff line number Diff line change
@@ -1,108 +1,173 @@
#include <chrono>
#include <vector>

#include "driver/gpio.h"
#include "driver/spi_master.h"
#include <driver/gpio.h>

#include "led_strip.hpp"
#include "logger.hpp"
#include "rmt.hpp"
#include "task.hpp"

using namespace std::chrono_literals;

static constexpr auto BUS_NUM = (SPI2_HOST);
static constexpr auto CLOCK_IO = (GPIO_NUM_12); // TinyPICO: APA102 CLK
static constexpr auto DATA_IO = (GPIO_NUM_2); // TinyPICO: APA102 DATA
static constexpr auto POWER_IO = (GPIO_NUM_13); // TinyPICO: APA102 PWR
// The Neopixel BFF has an array of 5x5 SK6805-2427 LEDs which are compatible with
// WS2812 LEDs. The data line is connected to GPIO8.
static constexpr auto NEO_BFF_IO = (GPIO_NUM_8); // QtPy ESP32s3 A3
static constexpr int NEO_BFF_NUM_LEDS = 5 * 5;
static constexpr int SK6805_FREQ_HZ = 10000000; // 10MHz
static constexpr int MICROSECONDS_PER_SECOND = 1000000;
static constexpr int SK6805_RESET_US = 80;

extern "C" void app_main(void) {
esp_err_t err;
// create a logger
espp::Logger logger({.tag = "Led Strip example", .level = espp::Logger::Verbosity::INFO});
{
// configure the power pin
gpio_config_t power_pin_config = {
.pin_bit_mask = (1ULL << POWER_IO),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&power_pin_config);
// turn on the power (active low on TinyPICO to save power)
gpio_set_level(POWER_IO, 0);

// configure the spi bus
spi_bus_config_t bus_config;
memset(&bus_config, 0, sizeof(bus_config));
bus_config.miso_io_num = -1;
bus_config.mosi_io_num = DATA_IO;
bus_config.sclk_io_num = CLOCK_IO;
bus_config.quadwp_io_num = -1;
bus_config.quadhd_io_num = -1;
bus_config.max_transfer_sz = 100; // NOTE: should make this num_leds * 4
// initialize the bus
err = spi_bus_initialize(BUS_NUM, &bus_config, 1);
if (err != ESP_OK) {
logger.error("Could not initialize SPI bus: {} - {}", err, esp_err_to_name(err));
}
// add the leds to the bus
spi_device_interface_config_t devcfg;
memset(&devcfg, 0, sizeof(devcfg));
devcfg.mode = 0; // SPI mode 0
devcfg.address_bits = 0;
devcfg.command_bits = 0;
devcfg.dummy_bits = 0;
devcfg.clock_speed_hz = 1 * 1000 * 1000; // Clock out at 1 MHz
devcfg.input_delay_ns = 0;
devcfg.spics_io_num = -1;
devcfg.queue_size = 1;
// devcfg.flags = SPI_DEVICE_NO_DUMMY;
spi_device_handle_t spi;
err = spi_bus_add_device(BUS_NUM, &devcfg, &spi);
if (err != ESP_OK) {
logger.error("Could not add SPI device: {} - {}", err, esp_err_to_name(err));
}
int led_encoder_state = 0;
auto led_encoder =
std::make_unique<espp::RmtEncoder>(espp::RmtEncoder::Config{
// NOTE: we're using the 10MHz clock so we could use the pre-defined
// SK6805 encoder config but we're using a custom encoder here to
// demonstrate how to use the RmtEncoder interface. The values we
// use here are based on the SK6805 encoder, which gets its values
// from the datasheet for the SK6805
// (https://cdn-shop.adafruit.com/product-files/3484/3484_Datasheet.pdf)
.bytes_encoder_config = {.bit0 =
{
.duration0 = static_cast<uint16_t>(
SK6805_FREQ_HZ / MICROSECONDS_PER_SECOND * 0.3),
.level0 = 1,
.duration1 = static_cast<uint16_t>(
SK6805_FREQ_HZ / MICROSECONDS_PER_SECOND * 0.9),
.level1 = 0,
},
.bit1 =
{
.duration0 = static_cast<uint16_t>(
SK6805_FREQ_HZ / MICROSECONDS_PER_SECOND * 0.6),
.level0 = 1,
.duration1 =
static_cast<uint16_t>(
SK6805_FREQ_HZ / MICROSECONDS_PER_SECOND *
0.6),
.level1 = 0,
},
.flags =
{
.msb_first = 1, // SK6805 transfer bit order: G7 G6 ...
// G0 R7 R6 ... R0 B7 B6 ... B0
}},
.encode = [&led_encoder_state](auto channel, auto *copy_encoder, auto *bytes_encoder,
const void *data, size_t data_size,
rmt_encode_state_t *ret_state) -> size_t {
// divide the transmit resolution (10MHz) by 1,000,000 to get the
// number of ticks per microsecond
// we divide by two since we have both duration0 and duration1 in the
// reset code
static uint16_t reset_ticks =
SK6805_FREQ_HZ / MICROSECONDS_PER_SECOND * SK6805_RESET_US / 2;
static rmt_symbol_word_t led_reset_code = (rmt_symbol_word_t){
.duration0 = reset_ticks,
.level0 = 0,
.duration1 = reset_ticks,
.level1 = 0,
};
rmt_encode_state_t session_state = RMT_ENCODING_RESET;
int state = RMT_ENCODING_RESET;
size_t encoded_symbols = 0;
switch (led_encoder_state) {
case 0: // send RGB data
encoded_symbols +=
bytes_encoder->encode(bytes_encoder, channel, data, data_size, &session_state);
if (session_state & RMT_ENCODING_COMPLETE) {
led_encoder_state =
1; // switch to next state when current encoding session finished
}
if (session_state & RMT_ENCODING_MEM_FULL) {
state |= RMT_ENCODING_MEM_FULL;
goto out; // yield if there's no free space for encoding artifacts
}
// fall-through
case 1: // send reset code
encoded_symbols += copy_encoder->encode(copy_encoder, channel, &led_reset_code,
sizeof(led_reset_code), &session_state);
if (session_state & RMT_ENCODING_COMPLETE) {
led_encoder_state = RMT_ENCODING_RESET; // back to the initial encoding session
state |= RMT_ENCODING_COMPLETE;
}
if (session_state & RMT_ENCODING_MEM_FULL) {
state |= RMT_ENCODING_MEM_FULL;
goto out; // yield if there's no free space for encoding artifacts
}
}
out:
*ret_state = static_cast<rmt_encode_state_t>(state);
return encoded_symbols;
},
.del = [](auto *base_encoder) -> esp_err_t {
// we don't have any extra resources to free, so just return ESP_OK
return ESP_OK;
},
.reset = [&led_encoder_state](auto *base_encoder) -> esp_err_t {
// all we have is some extra state to reset
led_encoder_state = 0;
return ESP_OK;
},
});

//! [led strip ex1]
// create the rmt object
espp::Rmt rmt(espp::Rmt::Config{
.gpio_num = NEO_BFF_IO,
.resolution_hz = SK6805_FREQ_HZ,
.log_level = espp::Logger::Verbosity::INFO,
});

// tell the RMT object to use the led_encoder (espp::RmtEncoder) that's
// defined above
rmt.set_encoder(std::move(led_encoder));

// create the write function we'll use
auto apa102_write = [&spi](const uint8_t *data, size_t len) {
auto neopixel_write = [&rmt](const uint8_t *data, size_t len) {
if (len == 0) {
return;
}
static spi_transaction_t t;
memset(&t, 0, sizeof(t));
t.length = len * 8;
t.tx_buffer = data;
spi_device_polling_transmit(spi, &t);
// send the data to the RMT object
rmt.transmit(data, len);
};

// now create the LedStrip object
espp::LedStrip led_strip(espp::LedStrip::Config{
.num_leds = 1,
.write = apa102_write,
.send_brightness = true,
.byte_order = espp::LedStrip::ByteOrder::BGR,
.start_frame = espp::LedStrip::APA102_START_FRAME,
.num_leds = NEO_BFF_NUM_LEDS,
.write = neopixel_write,
.send_brightness = false,
.byte_order = espp::LedStrip::ByteOrder::GRB,
.start_frame = {},
.end_frame = {},
.log_level = espp::Logger::Verbosity::INFO,
});

// Set first pixel using RGB
led_strip.set_pixel(0, espp::Rgb(0, 255, 255));
// Set all pixels
led_strip.set_all(espp::Rgb(0, 0, 0));
// And show it
led_strip.show();

std::this_thread::sleep_for(1s);

// Use a task to rotate the LED through the rainbow using HSV
auto task_fn = [&led_strip](std::mutex &m, std::condition_variable &cv) {
static auto start = std::chrono::high_resolution_clock::now();
auto now = std::chrono::high_resolution_clock::now();
float t = std::chrono::duration<float>(now - start).count();
// rotate through rainbow colors in hsv based on time, hue is 0-360
float hue = (cos(t) * 0.5f + 0.5f) * 360.0f;
espp::Hsv hsv(hue, 1.0f, 1.0f);
fmt::print("hsv: {}\n", hsv);
// full brightness (1.0, default) is _really_ bright, so tone it down
led_strip.set_pixel(0, hsv, 0.05f);
// set each LED in sequence and shift the hue by 10 degrees for each LED
for (int i = 0; i < NEO_BFF_NUM_LEDS; i++) {
espp::Hsv hsv(hue, 1.0f, 1.0f);
// full brightness (1.0, default) is _really_ bright, so tone it down
led_strip.set_pixel(i, hsv, 0.05f);
hue = std::fmod(hue + 10.0f, 360.0f);
}
// show the new colors
led_strip.show();
// NOTE: sleeping in this way allows the sleep to exit early when the
// task is being stopped / destroyed
Expand Down
2 changes: 2 additions & 0 deletions components/led_strip/example/sdkconfig.defaults
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
CONFIG_IDF_TARGET="esp32s3"

CONFIG_FREERTOS_HZ=1000

# ESP32-specific
Expand Down
8 changes: 7 additions & 1 deletion components/led_strip/include/led_strip.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,12 @@ class LedStrip {
// set the brightness byte (encoded as 0b111nnnnn where nnnnn is the
// brightness value), this means the brightness value is 0-31
data_[offset++] = 0b11100000 | brightness;
} else {
// we multiply the brightness by the color value to get the correct
// brightness
r = (r * brightness) >> 5;
g = (g * brightness) >> 5;
b = (b * brightness) >> 5;
}
// ensure the byte order is correct
switch (byte_order_) {
Expand Down Expand Up @@ -201,7 +207,7 @@ class LedStrip {
/// \sa set_pixel
/// \sa set_all
void show() {
logger_.debug("writing data");
logger_.debug("writing data {::02x}", data_);
write_(&data_[0], data_.size());
}

Expand Down
10 changes: 8 additions & 2 deletions components/rmt/example/main/rmt_example.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,18 @@ extern "C" void app_main(void) {
// This code is copied from the led_stip example in the esp-idf
// (https://github.com/espressif/esp-idf/tree/master/examples/peripherals/rmt/led_strip/main)
int led_encoder_state = 0;
static constexpr int WS2812_FREQ_HZ = 10000000;
static constexpr int MICROS_PER_SEC = 1000000;
auto led_encoder = std::make_unique<espp::RmtEncoder>(espp::RmtEncoder::Config{
.bytes_encoder_config = espp::RmtEncoder::ws2812_bytes_encoder_config,
// NOTE: since we're using the 10MHz RMT clock, we can use the pre-defined
// ws2812_10mhz_bytes_encoder_config
.bytes_encoder_config = espp::RmtEncoder::ws2812_10mhz_bytes_encoder_config,
.encode = [&led_encoder_state](auto channel, auto *copy_encoder, auto *bytes_encoder,
const void *data, size_t data_size,
rmt_encode_state_t *ret_state) -> size_t {
// divide by 2 since we have both duration0 and duration1 in the reset code
static uint16_t reset_ticks =
10000000 / 1000000 * 50 / 2; // reset code duration defaults to 50us
WS2812_FREQ_HZ / MICROS_PER_SEC * 50 / 2; // reset code duration defaults to 50us
static rmt_symbol_word_t led_reset_code = (rmt_symbol_word_t){
.duration0 = reset_ticks,
.level0 = 0,
Expand Down Expand Up @@ -103,6 +108,7 @@ extern "C" void app_main(void) {
// create the rmt object
espp::Rmt rmt(espp::Rmt::Config{
.gpio_num = 18, // WS2812B data pin on the TinyS3
.resolution_hz = WS2812_FREQ_HZ,
.log_level = espp::Logger::Verbosity::INFO,
});

Expand Down
3 changes: 2 additions & 1 deletion components/rmt/include/rmt.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class Rmt {
///< without DMA is >= 64, with DMA is >= 1024.
size_t resolution_hz = 10000000; ///< Resolution of the RMT peripheral
int transaction_queue_depth =
4; ///< Depth of the RMT transaction queue (number of transactions that can be queued)
1; ///< Depth of the RMT transaction queue (number of transactions that can be queued)
Logger::Verbosity log_level = Logger::Verbosity::WARN; ///< Log level for this class
};

Expand Down Expand Up @@ -93,6 +93,7 @@ class Rmt {
memset(&tx_channel_config, 0, sizeof(tx_channel_config));
tx_channel_config.clk_src = config.clock_src;
tx_channel_config.gpio_num = static_cast<gpio_num_t>(config.gpio_num);
tx_channel_config.flags.invert_out = false;
tx_channel_config.flags.with_dma = config.dma_enabled;
tx_channel_config.mem_block_symbols = config.block_size;
tx_channel_config.resolution_hz = config.resolution_hz;
Expand Down
33 changes: 32 additions & 1 deletion components/rmt/include/rmt_encoder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,43 @@ namespace espp {
/// \snippet rmt_example.cpp rmt encoder example
class RmtEncoder {
public:
/// \brief Configuration for the byte encoding for SK6805 LEDs
/// \details This configuration is used to encode bytes for SK6085 LEDs.
/// \note This configuration can be provided to the configuration for this
/// class. These values are based on the timing values provided in the
/// SK6805 datasheet (https://cdn-shop.adafruit.com/product-files/3484/3484_Datasheet.pdf)
/// \sa Config
static constexpr rmt_bytes_encoder_config_t sk6805_10mhz_bytes_encoder_config = {
.bit0 =
{
// divide the rmt transmit resolution (10 MHz) by 1,000,000
.duration0 = static_cast<uint16_t>(0.3 * 10000000 / 1000000), // T0H=0.3us
.level0 = 1,
// divide the rmt transmit resolution (10 MHz) by 1,000,000
.duration1 = static_cast<uint16_t>(0.9 * 10000000 / 1000000), // T0L=0.9us
.level1 = 0,
},
.bit1 =
{
// divide the rmt transmit resolution (10 MHz) by 1,000,000
.duration0 = static_cast<uint16_t>(0.6 * 10000000 / 1000000), // T1H=0.6us
.level0 = 1,
// divide the rmt transmit resolution (10 MHz) by 1,000,000
.duration1 = static_cast<uint16_t>(0.6 * 10000000 / 1000000), // T1L=0.6us
.level1 = 0,
},
.flags =
{
.msb_first = 1 // SK6805 transfer bit order: G7...G0R7...R0B7...B0
},
};

/// \brief Configuration for the byte encoding for WS2812 LEDs
/// \details This configuration is used to encode bytes for WS2812 LEDs.
/// \note This configuration can be provided to the configuration for this
/// class.
/// \sa Config
static constexpr rmt_bytes_encoder_config_t ws2812_bytes_encoder_config = {
static constexpr rmt_bytes_encoder_config_t ws2812_10mhz_bytes_encoder_config = {
.bit0 =
{
// divide the rmt transmit resolution (10 MHz) by 1,000,000
Expand Down
2 changes: 1 addition & 1 deletion docs/adc/adc_types.html
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@
<li><a href="index.html">ADC APIs</a> &raquo;</li>
<li>ADC Types</li>
<li class="wy-breadcrumbs-aside">
<a href="https://github.com/esp-cpp/espp/blob/72a7a43/docs/en/adc/adc_types.rst" class="fa fa-github"> Edit on GitHub</a>
<a href="https://github.com/esp-cpp/espp/blob/7acebeb/docs/en/adc/adc_types.rst" class="fa fa-github"> Edit on GitHub</a>
</li>
</ul>
<hr/>
Expand Down
4 changes: 2 additions & 2 deletions docs/adc/ads1x15.html
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@
<li><a href="index.html">ADC APIs</a> &raquo;</li>
<li>ADS1x15 I2C ADC</li>
<li class="wy-breadcrumbs-aside">
<a href="https://github.com/esp-cpp/espp/blob/72a7a43/docs/en/adc/ads1x15.rst" class="fa fa-github"> Edit on GitHub</a>
<a href="https://github.com/esp-cpp/espp/blob/7acebeb/docs/en/adc/ads1x15.rst" class="fa fa-github"> Edit on GitHub</a>
</li>
</ul>
<hr/>
Expand All @@ -154,7 +154,7 @@ <h2>API Reference<a class="headerlink" href="#api-reference" title="Permalink to
<section id="header-file">
<h3>Header File<a class="headerlink" href="#header-file" title="Permalink to this headline"></a></h3>
<ul class="simple">
<li><p><a class="reference external" href="https://github.com/esp-cpp/espp/blob/72a7a43/components/ads1x15/include/ads1x15.hpp">components/ads1x15/include/ads1x15.hpp</a></p></li>
<li><p><a class="reference external" href="https://github.com/esp-cpp/espp/blob/7acebeb/components/ads1x15/include/ads1x15.hpp">components/ads1x15/include/ads1x15.hpp</a></p></li>
</ul>
</section>
<section id="classes">
Expand Down
Loading

0 comments on commit a0a5c6a

Please sign in to comment.