| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477 |
- /********************************************************************************
- * https://github.com/RoboticsBrno/SmartLeds
- *
- * MIT License
- *
- * Copyright (c) 2017 RoboticsBrno (RobotikaBrno)
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- *******************************************************************************/
- #pragma once
- /*
- * A C++ driver for the WS2812 LEDs using the RMT peripheral on the ESP32.
- *
- * Jan "yaqwsx" Mrázek <email@honzamrazek.cz>
- *
- * Based on the work by Martin F. Falatic - https://github.com/FozzTexx/ws2812-demo
- */
- /*
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
- #include <cassert>
- #include <cstring>
- #include <memory>
- #include <driver/gpio.h>
- #include <driver/spi_master.h>
- #include <esp_intr_alloc.h>
- #include <esp_ipc.h>
- #include <freertos/FreeRTOS.h>
- #include <freertos/semphr.h>
- #include "Color.h"
- #include "RmtDriver.h"
- using LedType = detail::TimingParams;
- // Times are in nanoseconds,
- // The RMT driver runs at 20MHz, so minimal representable time is 50 nanoseconds
- static const LedType LED_WS2812 = { 350, 700, 800, 600, 50000 };
- // longer reset time because https://blog.adafruit.com/2017/05/03/psa-the-ws2812b-rgb-led-has-been-revised-will-require-code-tweak/
- static const LedType LED_WS2812B = { 400, 800, 850, 450, 300000 }; // universal
- static const LedType LED_WS2812B_NEWVARIANT = { 200, 750, 750, 200, 300000 };
- static const LedType LED_WS2812B_OLDVARIANT = { 400, 800, 850, 450, 50000 };
- // This is timing from datasheet, but does not seem to actually work - try LED_WS2812B
- static const LedType LED_WS2812C = { 250, 550, 550, 250, 280000 };
- static const LedType LED_SK6812 = { 300, 600, 900, 600, 80000 };
- static const LedType LED_WS2813 = { 350, 800, 350, 350, 300000 };
- // Single buffer == can't touch the Rgbs between show() and wait()
- enum BufferType { SingleBuffer = 0, DoubleBuffer };
- enum IsrCore { CoreFirst = 0, CoreSecond = 1, CoreCurrent = 2 };
- class SmartLed {
- public:
- friend class detail::RmtDriver;
- // The RMT interrupt must not run on the same core as WiFi interrupts, otherwise SmartLeds
- // can't fill the RMT buffer fast enough, resulting in rendering artifacts.
- // Usually, that means you have to set isrCore == CoreSecond.
- //
- // If you use anything other than CoreCurrent, the FreeRTOS scheduler MUST be already running,
- // so you can't use it if you define SmartLed as global variable.
- //
- // Does nothing on chips that only have one core.
- SmartLed(const LedType& type, int count, int pin, int channel = 0, BufferType doubleBuffer = DoubleBuffer,
- IsrCore isrCore = CoreCurrent)
- : _finishedFlag(xSemaphoreCreateBinary())
- , _driver(type, count, pin, channel, _finishedFlag)
- , _channel(channel)
- , _count(count)
- , _firstBuffer(new Rgb[count])
- , _secondBuffer(doubleBuffer ? new Rgb[count] : nullptr) {
- assert(channel >= 0 && channel < detail::CHANNEL_COUNT);
- assert(ledForChannel(channel) == nullptr);
- xSemaphoreGive(_finishedFlag);
- _driver.init();
- #if !defined(SOC_CPU_CORES_NUM) || SOC_CPU_CORES_NUM > 1
- if (!anyAlive() && isrCore != CoreCurrent) {
- _interruptCore = isrCore;
- ESP_ERROR_CHECK(esp_ipc_call_blocking(isrCore, registerInterrupt, (void*)this));
- } else
- #endif
- {
- registerInterrupt((void*)this);
- }
- ledForChannel(channel) = this;
- }
- ~SmartLed() {
- ledForChannel(_channel) = nullptr;
- #if !defined(SOC_CPU_CORES_NUM) || SOC_CPU_CORES_NUM > 1
- if (!anyAlive() && _interruptCore != CoreCurrent) {
- ESP_ERROR_CHECK(esp_ipc_call_blocking(_interruptCore, unregisterInterrupt, (void*)this));
- } else
- #endif
- {
- unregisterInterrupt((void*)this);
- }
- vSemaphoreDelete(_finishedFlag);
- }
- Rgb& operator[](int idx) { return _firstBuffer[idx]; }
- const Rgb& operator[](int idx) const { return _firstBuffer[idx]; }
- esp_err_t show() {
- esp_err_t err = startTransmission();
- swapBuffers();
- return err;
- }
- bool wait(TickType_t timeout = portMAX_DELAY) {
- if (xSemaphoreTake(_finishedFlag, timeout) == pdTRUE) {
- xSemaphoreGive(_finishedFlag);
- return true;
- }
- return false;
- }
- int size() const { return _count; }
- int channel() const { return _channel; }
- Rgb* begin() { return _firstBuffer.get(); }
- const Rgb* begin() const { return _firstBuffer.get(); }
- const Rgb* cbegin() const { return _firstBuffer.get(); }
- Rgb* end() { return _firstBuffer.get() + _count; }
- const Rgb* end() const { return _firstBuffer.get() + _count; }
- const Rgb* cend() const { return _firstBuffer.get() + _count; }
- private:
- static IsrCore _interruptCore;
- static void registerInterrupt(void* selfVoid) {
- auto* self = (SmartLed*)selfVoid;
- ESP_ERROR_CHECK(self->_driver.registerIsr(!anyAlive()));
- }
- static void unregisterInterrupt(void* selfVoid) {
- auto* self = (SmartLed*)selfVoid;
- ESP_ERROR_CHECK(self->_driver.unregisterIsr());
- }
- static SmartLed*& IRAM_ATTR ledForChannel(int channel);
- static bool anyAlive() {
- for (int i = 0; i != detail::CHANNEL_COUNT; i++)
- if (ledForChannel(i) != nullptr)
- return true;
- return false;
- }
- void swapBuffers() {
- if (_secondBuffer)
- _firstBuffer.swap(_secondBuffer);
- }
- esp_err_t startTransmission() {
- // Invalid use of the library, you must wait() fir previous frame to get processed first
- if (xSemaphoreTake(_finishedFlag, 0) != pdTRUE)
- abort();
- auto err = _driver.transmit(_firstBuffer.get());
- if (err != ESP_OK) {
- return err;
- }
- return ESP_OK;
- }
- SemaphoreHandle_t _finishedFlag;
- detail::RmtDriver _driver;
- int _channel;
- int _count;
- std::unique_ptr<Rgb[]> _firstBuffer;
- std::unique_ptr<Rgb[]> _secondBuffer;
- };
- #if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3)
- #define _SMARTLEDS_SPI_HOST SPI2_HOST
- #define _SMARTLEDS_SPI_DMA_CHAN SPI_DMA_CH_AUTO
- #else
- #define _SMARTLEDS_SPI_HOST HSPI_HOST
- #define _SMARTLEDS_SPI_DMA_CHAN 1
- #endif
- class Apa102 {
- public:
- struct ApaRgb {
- ApaRgb(uint8_t r = 0, uint8_t g = 0, uint32_t b = 0, uint32_t v = 0xFF)
- : v(0xE0 | v)
- , b(b)
- , g(g)
- , r(r) {}
- ApaRgb& operator=(const Rgb& o) {
- r = o.r;
- g = o.g;
- b = o.b;
- return *this;
- }
- ApaRgb& operator=(const Hsv& o) {
- *this = Rgb { o };
- return *this;
- }
- uint8_t v, b, g, r;
- };
- static const int FINAL_FRAME_SIZE = 4;
- static const int TRANS_COUNT = 2 + 8;
- Apa102(int count, int clkpin, int datapin, BufferType doubleBuffer = SingleBuffer, int clock_speed_hz = 1000000)
- : _count(count)
- , _firstBuffer(new ApaRgb[count])
- , _secondBuffer(doubleBuffer ? new ApaRgb[count] : nullptr)
- , _transCount(0)
- , _initFrame(0) {
- spi_bus_config_t buscfg;
- memset(&buscfg, 0, sizeof(buscfg));
- buscfg.mosi_io_num = datapin;
- buscfg.miso_io_num = -1;
- buscfg.sclk_io_num = clkpin;
- buscfg.quadwp_io_num = -1;
- buscfg.quadhd_io_num = -1;
- buscfg.max_transfer_sz = 65535;
- spi_device_interface_config_t devcfg;
- memset(&devcfg, 0, sizeof(devcfg));
- devcfg.clock_speed_hz = clock_speed_hz;
- devcfg.mode = 0;
- devcfg.spics_io_num = -1;
- devcfg.queue_size = TRANS_COUNT;
- devcfg.pre_cb = nullptr;
- auto ret = spi_bus_initialize(_SMARTLEDS_SPI_HOST, &buscfg, _SMARTLEDS_SPI_DMA_CHAN);
- assert(ret == ESP_OK);
- ret = spi_bus_add_device(_SMARTLEDS_SPI_HOST, &devcfg, &_spi);
- assert(ret == ESP_OK);
- std::fill_n(_finalFrame, FINAL_FRAME_SIZE, 0xFFFFFFFF);
- }
- ~Apa102() {
- // ToDo
- }
- ApaRgb& operator[](int idx) { return _firstBuffer[idx]; }
- const ApaRgb& operator[](int idx) const { return _firstBuffer[idx]; }
- void show() {
- _buffer = _firstBuffer.get();
- startTransmission();
- swapBuffers();
- }
- void wait() {
- for (int i = 0; i != _transCount; i++) {
- spi_transaction_t* t;
- spi_device_get_trans_result(_spi, &t, portMAX_DELAY);
- }
- }
- private:
- void swapBuffers() {
- if (_secondBuffer)
- _firstBuffer.swap(_secondBuffer);
- }
- void startTransmission() {
- for (int i = 0; i != TRANS_COUNT; i++) {
- _transactions[i].cmd = 0;
- _transactions[i].addr = 0;
- _transactions[i].flags = 0;
- _transactions[i].rxlength = 0;
- _transactions[i].rx_buffer = nullptr;
- }
- // Init frame
- _transactions[0].length = 32;
- _transactions[0].tx_buffer = &_initFrame;
- spi_device_queue_trans(_spi, _transactions + 0, portMAX_DELAY);
- // Data
- _transactions[1].length = 32 * _count;
- _transactions[1].tx_buffer = _buffer;
- spi_device_queue_trans(_spi, _transactions + 1, portMAX_DELAY);
- _transCount = 2;
- // End frame
- for (int i = 0; i != 1 + _count / 32 / FINAL_FRAME_SIZE; i++) {
- _transactions[2 + i].length = 32 * FINAL_FRAME_SIZE;
- _transactions[2 + i].tx_buffer = _finalFrame;
- spi_device_queue_trans(_spi, _transactions + 2 + i, portMAX_DELAY);
- _transCount++;
- }
- }
- spi_device_handle_t _spi;
- int _count;
- std::unique_ptr<ApaRgb[]> _firstBuffer, _secondBuffer;
- ApaRgb* _buffer;
- spi_transaction_t _transactions[TRANS_COUNT];
- int _transCount;
- uint32_t _initFrame;
- uint32_t _finalFrame[FINAL_FRAME_SIZE];
- };
- class LDP8806 {
- public:
- struct LDP8806_GRB {
- LDP8806_GRB(uint8_t g_7bit = 0, uint8_t r_7bit = 0, uint32_t b_7bit = 0)
- : g(g_7bit)
- , r(r_7bit)
- , b(b_7bit) {}
- LDP8806_GRB& operator=(const Rgb& o) {
- //Convert 8->7bit colour
- r = (o.r * 127 / 256) | 0x80;
- g = (o.g * 127 / 256) | 0x80;
- b = (o.b * 127 / 256) | 0x80;
- return *this;
- }
- LDP8806_GRB& operator=(const Hsv& o) {
- *this = Rgb { o };
- return *this;
- }
- uint8_t g, r, b;
- };
- static const int LED_FRAME_SIZE_BYTES = sizeof(LDP8806_GRB);
- static const int LATCH_FRAME_SIZE_BYTES = 3;
- static const int TRANS_COUNT_MAX = 20; //Arbitrary, supports up to 600 LED
- LDP8806(
- int count, int clkpin, int datapin, BufferType doubleBuffer = SingleBuffer, uint32_t clock_speed_hz = 2000000)
- : _count(count)
- , _firstBuffer(new LDP8806_GRB[count])
- , _secondBuffer(doubleBuffer ? new LDP8806_GRB[count] : nullptr)
- ,
- // one 'latch'/start-of-data mark frame for every 32 leds
- _latchFrames((count + 31) / 32) {
- spi_bus_config_t buscfg;
- memset(&buscfg, 0, sizeof(buscfg));
- buscfg.mosi_io_num = datapin;
- buscfg.miso_io_num = -1;
- buscfg.sclk_io_num = clkpin;
- buscfg.quadwp_io_num = -1;
- buscfg.quadhd_io_num = -1;
- buscfg.max_transfer_sz = 65535;
- spi_device_interface_config_t devcfg;
- memset(&devcfg, 0, sizeof(devcfg));
- devcfg.clock_speed_hz = clock_speed_hz;
- devcfg.mode = 0;
- devcfg.spics_io_num = -1;
- devcfg.queue_size = TRANS_COUNT_MAX;
- devcfg.pre_cb = nullptr;
- auto ret = spi_bus_initialize(_SMARTLEDS_SPI_HOST, &buscfg, _SMARTLEDS_SPI_DMA_CHAN);
- assert(ret == ESP_OK);
- ret = spi_bus_add_device(_SMARTLEDS_SPI_HOST, &devcfg, &_spi);
- assert(ret == ESP_OK);
- std::fill_n(_latchBuffer, LATCH_FRAME_SIZE_BYTES, 0x0);
- }
- ~LDP8806() {
- // noop
- }
- LDP8806_GRB& operator[](int idx) { return _firstBuffer[idx]; }
- const LDP8806_GRB& operator[](int idx) const { return _firstBuffer[idx]; }
- void show() {
- _buffer = _firstBuffer.get();
- startTransmission();
- swapBuffers();
- }
- void wait() {
- while (_transCount--) {
- spi_transaction_t* t;
- spi_device_get_trans_result(_spi, &t, portMAX_DELAY);
- }
- }
- private:
- void swapBuffers() {
- if (_secondBuffer)
- _firstBuffer.swap(_secondBuffer);
- }
- void startTransmission() {
- _transCount = 0;
- for (int i = 0; i != TRANS_COUNT_MAX; i++) {
- _transactions[i].cmd = 0;
- _transactions[i].addr = 0;
- _transactions[i].flags = 0;
- _transactions[i].rxlength = 0;
- _transactions[i].rx_buffer = nullptr;
- }
- // LED Data
- _transactions[0].length = (LED_FRAME_SIZE_BYTES * 8) * _count;
- _transactions[0].tx_buffer = _buffer;
- spi_device_queue_trans(_spi, _transactions + _transCount, portMAX_DELAY);
- _transCount++;
- // 'latch'/start-of-data marker frames
- for (int i = 0; i < _latchFrames; i++) {
- _transactions[_transCount].length = (LATCH_FRAME_SIZE_BYTES * 8);
- _transactions[_transCount].tx_buffer = _latchBuffer;
- spi_device_queue_trans(_spi, _transactions + _transCount, portMAX_DELAY);
- _transCount++;
- }
- }
- spi_device_handle_t _spi;
- int _count;
- std::unique_ptr<LDP8806_GRB[]> _firstBuffer, _secondBuffer;
- LDP8806_GRB* _buffer;
- spi_transaction_t _transactions[TRANS_COUNT_MAX];
- int _transCount;
- int _latchFrames;
- uint8_t _latchBuffer[LATCH_FRAME_SIZE_BYTES];
- };
|