|
|
@@ -1,7 +1,30 @@
|
|
|
-#pragma once
|
|
|
+/********************************************************************************
|
|
|
+ * 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.
|
|
|
+ *******************************************************************************/
|
|
|
|
|
|
-#ifndef SMARTLEDS_H
|
|
|
-#define SMARTLEDS_H
|
|
|
+#pragma once
|
|
|
|
|
|
/*
|
|
|
* A C++ driver for the WS2812 LEDs using the RMT peripheral on the ESP32.
|
|
|
@@ -31,305 +54,196 @@
|
|
|
* THE SOFTWARE.
|
|
|
*/
|
|
|
|
|
|
-#include <memory>
|
|
|
#include <cassert>
|
|
|
#include <cstring>
|
|
|
+#include <memory>
|
|
|
|
|
|
-#include "esp_idf_version.h"
|
|
|
-#if (ESP_IDF_VERSION_MAJOR >= 5)
|
|
|
-#include "soc/periph_defs.h"
|
|
|
-#include "esp_private/periph_ctrl.h"
|
|
|
-#include "soc/gpio_sig_map.h"
|
|
|
-#include "soc/gpio_periph.h"
|
|
|
-#include "soc/io_mux_reg.h"
|
|
|
-#include "esp_rom_gpio.h"
|
|
|
-#define gpio_pad_select_gpio esp_rom_gpio_pad_select_gpio
|
|
|
-#define gpio_matrix_in(a,b,c) esp_rom_gpio_connect_in_signal(a,b,c)
|
|
|
-#define gpio_matrix_out(a,b,c,d) esp_rom_gpio_connect_out_signal(a,b,c,d)
|
|
|
-#define ets_delay_us(a) esp_rom_delay_us(a)
|
|
|
-#endif
|
|
|
-
|
|
|
-#if defined ( ARDUINO )
|
|
|
- extern "C" { // ...someone forgot to put in the includes...
|
|
|
- #include "esp32-hal.h"
|
|
|
- #include "esp_intr_alloc.h"
|
|
|
- #include "esp_ipc.h"
|
|
|
- #include "driver/gpio.h"
|
|
|
- #include "driver/periph_ctrl.h"
|
|
|
- #include "freertos/semphr.h"
|
|
|
- #include "soc/rmt_struct.h"
|
|
|
- #include <driver/spi_master.h>
|
|
|
- #include "esp_idf_version.h"
|
|
|
-#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL( 4, 0, 0 )
|
|
|
- #include "soc/dport_reg.h"
|
|
|
-#endif
|
|
|
- }
|
|
|
-#elif defined ( ESP_PLATFORM )
|
|
|
- extern "C" { // ...someone forgot to put in the includes...
|
|
|
- #include <esp_intr_alloc.h>
|
|
|
- #include <esp_ipc.h>
|
|
|
- #include <driver/gpio.h>
|
|
|
- #include <freertos/FreeRTOS.h>
|
|
|
- #include <freertos/semphr.h>
|
|
|
- #include <soc/dport_reg.h>
|
|
|
- #include <soc/gpio_sig_map.h>
|
|
|
- #include <soc/rmt_struct.h>
|
|
|
- #include <driver/spi_master.h>
|
|
|
- }
|
|
|
- #include <stdio.h>
|
|
|
-#endif
|
|
|
-
|
|
|
-#if (ESP_IDF_VERSION_MAJOR >= 4) && (ESP_IDF_VERSION_MINOR > 1)
|
|
|
-#include "hal/gpio_ll.h"
|
|
|
-#else
|
|
|
-#include "soc/gpio_periph.h"
|
|
|
-#define esp_rom_delay_us ets_delay_us
|
|
|
-static inline int gpio_ll_get_level(gpio_dev_t *hw, int gpio_num)
|
|
|
-{
|
|
|
- if (gpio_num < 32) {
|
|
|
- return (hw->in >> gpio_num) & 0x1;
|
|
|
- } else {
|
|
|
- return (hw->in1.data >> (gpio_num - 32)) & 0x1;
|
|
|
- }
|
|
|
-}
|
|
|
-#endif
|
|
|
-
|
|
|
-#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0))
|
|
|
-#if !(configENABLE_BACKWARD_COMPATIBILITY == 1)
|
|
|
-#define xSemaphoreHandle SemaphoreHandle_t
|
|
|
-#endif
|
|
|
-#endif
|
|
|
+#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"
|
|
|
|
|
|
-namespace detail {
|
|
|
-
|
|
|
-struct TimingParams {
|
|
|
- uint32_t T0H;
|
|
|
- uint32_t T1H;
|
|
|
- uint32_t T0L;
|
|
|
- uint32_t T1L;
|
|
|
- uint32_t TRS;
|
|
|
-};
|
|
|
-
|
|
|
-union RmtPulsePair {
|
|
|
- struct {
|
|
|
- int duration0:15;
|
|
|
- int level0:1;
|
|
|
- int duration1:15;
|
|
|
- int level1:1;
|
|
|
- };
|
|
|
- uint32_t value;
|
|
|
-};
|
|
|
-
|
|
|
-static const int DIVIDER = 4; // 8 still seems to work, but timings become marginal
|
|
|
-static const int MAX_PULSES = 32; // A channel has a 64 "pulse" buffer - we use half per pass
|
|
|
-static const double RMT_DURATION_NS = 12.5; // minimum time of a single RMT duration based on clock ns
|
|
|
-
|
|
|
-} // namespace detail
|
|
|
+#include "RmtDriver.h"
|
|
|
|
|
|
using LedType = detail::TimingParams;
|
|
|
|
|
|
-static const LedType LED_WS2812 = { 350, 700, 800, 600, 50000 };
|
|
|
-static const LedType LED_WS2812B = { 400, 850, 850, 400, 50100 };
|
|
|
-static const LedType LED_SK6812 = { 300, 600, 900, 600, 80000 };
|
|
|
-static const LedType LED_WS2813 = { 350, 800, 350, 350, 300000 };
|
|
|
-
|
|
|
+// 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};
|
|
|
+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.
|
|
|
- SmartLed( const LedType& type, int count, int pin, int channel = 0, BufferType doubleBuffer = SingleBuffer, IsrCore isrCore = CoreCurrent)
|
|
|
- : _timing( type ),
|
|
|
- _channel( channel ),
|
|
|
- _count( count ),
|
|
|
- _firstBuffer( new Rgb[ count ] ),
|
|
|
- _secondBuffer( doubleBuffer ? new Rgb[ count ] : nullptr ),
|
|
|
- _finishedFlag( xSemaphoreCreateBinary() )
|
|
|
- {
|
|
|
- assert( channel >= 0 && channel < 8 );
|
|
|
- assert( ledForChannel( channel ) == nullptr );
|
|
|
-
|
|
|
- xSemaphoreGive( _finishedFlag );
|
|
|
-
|
|
|
- DPORT_SET_PERI_REG_MASK( DPORT_PERIP_CLK_EN_REG, DPORT_RMT_CLK_EN );
|
|
|
- DPORT_CLEAR_PERI_REG_MASK( DPORT_PERIP_RST_EN_REG, DPORT_RMT_RST );
|
|
|
-
|
|
|
- PIN_FUNC_SELECT( GPIO_PIN_MUX_REG[ pin ], 2 );
|
|
|
- gpio_set_direction( static_cast< gpio_num_t >( pin ), GPIO_MODE_OUTPUT );
|
|
|
- gpio_matrix_out( static_cast< gpio_num_t >( pin ), RMT_SIG_OUT0_IDX + _channel, 0, 0 );
|
|
|
- initChannel( _channel );
|
|
|
-
|
|
|
- RMT.tx_lim_ch[ _channel ].limit = detail::MAX_PULSES;
|
|
|
- RMT.int_ena.val |= 1 << ( 24 + _channel );
|
|
|
- RMT.int_ena.val |= 1 << ( 3 * _channel );
|
|
|
-
|
|
|
- _bitToRmt[ 0 ].level0 = 1;
|
|
|
- _bitToRmt[ 0 ].level1 = 0;
|
|
|
- _bitToRmt[ 0 ].duration0 = _timing.T0H / ( detail::RMT_DURATION_NS * detail::DIVIDER );
|
|
|
- _bitToRmt[ 0 ].duration1 = _timing.T0L / ( detail::RMT_DURATION_NS * detail::DIVIDER );
|
|
|
-
|
|
|
- _bitToRmt[ 1 ].level0 = 1;
|
|
|
- _bitToRmt[ 1 ].level1 = 0;
|
|
|
- _bitToRmt[ 1 ].duration0 = _timing.T1H / ( detail::RMT_DURATION_NS * detail::DIVIDER );
|
|
|
- _bitToRmt[ 1 ].duration1 = _timing.T1L / ( detail::RMT_DURATION_NS * detail::DIVIDER );
|
|
|
-
|
|
|
- if ( !anyAlive() ) {
|
|
|
+ //
|
|
|
+ // 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;
|
|
|
- if(isrCore != CoreCurrent) {
|
|
|
- ESP_ERROR_CHECK(esp_ipc_call_blocking(isrCore, registerInterrupt, NULL));
|
|
|
- } else {
|
|
|
- registerInterrupt(NULL);
|
|
|
- }
|
|
|
+ ESP_ERROR_CHECK(esp_ipc_call_blocking(isrCore, registerInterrupt, (void*)this));
|
|
|
+ } else
|
|
|
+#endif
|
|
|
+ {
|
|
|
+ registerInterrupt((void*)this);
|
|
|
}
|
|
|
|
|
|
- ledForChannel( channel ) = this;
|
|
|
+ ledForChannel(channel) = this;
|
|
|
}
|
|
|
|
|
|
~SmartLed() {
|
|
|
- ledForChannel( _channel ) = nullptr;
|
|
|
- if ( !anyAlive() ) {
|
|
|
- if(_interruptCore != CoreCurrent) {
|
|
|
- ESP_ERROR_CHECK(esp_ipc_call_blocking(_interruptCore, unregisterInterrupt, NULL));
|
|
|
- } else {
|
|
|
- unregisterInterrupt(NULL);
|
|
|
- }
|
|
|
+ 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 );
|
|
|
+ vSemaphoreDelete(_finishedFlag);
|
|
|
}
|
|
|
|
|
|
- Rgb& operator[]( int idx ) {
|
|
|
- return _firstBuffer[ idx ];
|
|
|
- }
|
|
|
+ Rgb& operator[](int idx) { return _firstBuffer[idx]; }
|
|
|
|
|
|
- const Rgb& operator[]( int idx ) const {
|
|
|
- return _firstBuffer[ idx ];
|
|
|
- }
|
|
|
+ const Rgb& operator[](int idx) const { return _firstBuffer[idx]; }
|
|
|
|
|
|
- void show() {
|
|
|
- _buffer = _firstBuffer.get();
|
|
|
- startTransmission();
|
|
|
+ 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 );
|
|
|
+ bool wait(TickType_t timeout = portMAX_DELAY) {
|
|
|
+ if (xSemaphoreTake(_finishedFlag, timeout) == pdTRUE) {
|
|
|
+ xSemaphoreGive(_finishedFlag);
|
|
|
return true;
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
- int size() const {
|
|
|
- return _count;
|
|
|
- }
|
|
|
+ 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* 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; }
|
|
|
+ Rgb* end() { return _firstBuffer.get() + _count; }
|
|
|
+ const Rgb* end() const { return _firstBuffer.get() + _count; }
|
|
|
+ const Rgb* cend() const { return _firstBuffer.get() + _count; }
|
|
|
|
|
|
private:
|
|
|
- static intr_handle_t _interruptHandle;
|
|
|
static IsrCore _interruptCore;
|
|
|
|
|
|
- static void initChannel( int channel ) {
|
|
|
- RMT.apb_conf.fifo_mask = 1; //enable memory access, instead of FIFO mode.
|
|
|
- RMT.apb_conf.mem_tx_wrap_en = 1; //wrap around when hitting end of buffer
|
|
|
- RMT.conf_ch[ channel ].conf0.div_cnt = detail::DIVIDER;
|
|
|
- RMT.conf_ch[ channel ].conf0.mem_size = 1;
|
|
|
- RMT.conf_ch[ channel ].conf0.carrier_en = 0;
|
|
|
- RMT.conf_ch[ channel ].conf0.carrier_out_lv = 1;
|
|
|
- RMT.conf_ch[ channel ].conf0.mem_pd = 0;
|
|
|
-
|
|
|
- RMT.conf_ch[ channel ].conf1.rx_en = 0;
|
|
|
- RMT.conf_ch[ channel ].conf1.mem_owner = 0;
|
|
|
- RMT.conf_ch[ channel ].conf1.tx_conti_mode = 0; //loop back mode.
|
|
|
- RMT.conf_ch[ channel ].conf1.ref_always_on = 1; // use apb clock: 80M
|
|
|
- RMT.conf_ch[ channel ].conf1.idle_out_en = 1;
|
|
|
- RMT.conf_ch[ channel ].conf1.idle_out_lv = 0;
|
|
|
- }
|
|
|
-
|
|
|
- static void registerInterrupt(void *) {
|
|
|
- ESP_ERROR_CHECK(esp_intr_alloc( ETS_RMT_INTR_SOURCE, 0, interruptHandler, nullptr, &_interruptHandle));
|
|
|
+ static void registerInterrupt(void* selfVoid) {
|
|
|
+ auto* self = (SmartLed*)selfVoid;
|
|
|
+ ESP_ERROR_CHECK(self->_driver.registerIsr(!anyAlive()));
|
|
|
}
|
|
|
|
|
|
- static void unregisterInterrupt(void*) {
|
|
|
- esp_intr_free( _interruptHandle );
|
|
|
+ static void unregisterInterrupt(void* selfVoid) {
|
|
|
+ auto* self = (SmartLed*)selfVoid;
|
|
|
+ ESP_ERROR_CHECK(self->_driver.unregisterIsr());
|
|
|
}
|
|
|
|
|
|
- static SmartLed*& IRAM_ATTR ledForChannel( int channel );
|
|
|
- static void IRAM_ATTR interruptHandler( void* );
|
|
|
+ static SmartLed*& IRAM_ATTR ledForChannel(int channel);
|
|
|
|
|
|
- void IRAM_ATTR copyRmtHalfBlock();
|
|
|
+ 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 );
|
|
|
+ if (_secondBuffer)
|
|
|
+ _firstBuffer.swap(_secondBuffer);
|
|
|
}
|
|
|
|
|
|
- void startTransmission() {
|
|
|
- // Invalid use of the library
|
|
|
- if( xSemaphoreTake( _finishedFlag, 0 ) != pdTRUE )
|
|
|
+ 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();
|
|
|
|
|
|
- _pixelPosition = _componentPosition = _halfIdx = 0;
|
|
|
- copyRmtHalfBlock();
|
|
|
- if ( _pixelPosition < _count )
|
|
|
- copyRmtHalfBlock();
|
|
|
-
|
|
|
- RMT.conf_ch[ _channel ].conf1.mem_rd_rst = 1;
|
|
|
- RMT.conf_ch[ _channel ].conf1.tx_start = 1;
|
|
|
- }
|
|
|
+ auto err = _driver.transmit(_firstBuffer.get());
|
|
|
+ if (err != ESP_OK) {
|
|
|
+ return err;
|
|
|
+ }
|
|
|
|
|
|
- static bool anyAlive() {
|
|
|
- for ( int i = 0; i != 8; i++ )
|
|
|
- if ( ledForChannel( i ) != nullptr ) return true;
|
|
|
- return false;
|
|
|
+ return ESP_OK;
|
|
|
}
|
|
|
|
|
|
- const LedType& _timing;
|
|
|
+ SemaphoreHandle_t _finishedFlag;
|
|
|
+ detail::RmtDriver _driver;
|
|
|
int _channel;
|
|
|
- detail::RmtPulsePair _bitToRmt[ 2 ];
|
|
|
int _count;
|
|
|
- std::unique_ptr< Rgb[] > _firstBuffer;
|
|
|
- std::unique_ptr< Rgb[] > _secondBuffer;
|
|
|
- Rgb *_buffer;
|
|
|
-
|
|
|
- xSemaphoreHandle _finishedFlag;
|
|
|
-
|
|
|
- int _pixelPosition;
|
|
|
- int _componentPosition;
|
|
|
- int _halfIdx;
|
|
|
+ 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(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 ) {
|
|
|
+ ApaRgb& operator=(const Rgb& o) {
|
|
|
r = o.r;
|
|
|
g = o.g;
|
|
|
b = o.b;
|
|
|
return *this;
|
|
|
}
|
|
|
|
|
|
- ApaRgb& operator=( const Hsv& o ) {
|
|
|
- *this = Rgb{ o };
|
|
|
+ ApaRgb& operator=(const Hsv& o) {
|
|
|
+ *this = Rgb { o };
|
|
|
return *this;
|
|
|
}
|
|
|
|
|
|
@@ -339,14 +253,14 @@ public:
|
|
|
static const int FINAL_FRAME_SIZE = 4;
|
|
|
static const int TRANS_COUNT = 2 + 8;
|
|
|
|
|
|
- Apa102( int count, int clkpin, int datapin, BufferType doubleBuffer = SingleBuffer )
|
|
|
- : _count( count ),
|
|
|
- _firstBuffer( new ApaRgb[ count ] ),
|
|
|
- _secondBuffer( doubleBuffer ? new ApaRgb[ count ] : nullptr ),
|
|
|
- _initFrame( 0 )
|
|
|
- {
|
|
|
+ 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 ) );
|
|
|
+ memset(&buscfg, 0, sizeof(buscfg));
|
|
|
buscfg.mosi_io_num = datapin;
|
|
|
buscfg.miso_io_num = -1;
|
|
|
buscfg.sclk_io_num = clkpin;
|
|
|
@@ -355,33 +269,29 @@ public:
|
|
|
buscfg.max_transfer_sz = 65535;
|
|
|
|
|
|
spi_device_interface_config_t devcfg;
|
|
|
- memset( &devcfg, 0, sizeof( devcfg ) );
|
|
|
- devcfg.clock_speed_hz = 1000000;
|
|
|
+ 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( HSPI_HOST, &buscfg, 1 );
|
|
|
- assert( ret == ESP_OK );
|
|
|
+ auto ret = spi_bus_initialize(_SMARTLEDS_SPI_HOST, &buscfg, _SMARTLEDS_SPI_DMA_CHAN);
|
|
|
+ assert(ret == ESP_OK);
|
|
|
|
|
|
- ret = spi_bus_add_device( HSPI_HOST, &devcfg, &_spi );
|
|
|
- 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 );
|
|
|
+ std::fill_n(_finalFrame, FINAL_FRAME_SIZE, 0xFFFFFFFF);
|
|
|
}
|
|
|
|
|
|
~Apa102() {
|
|
|
// ToDo
|
|
|
}
|
|
|
|
|
|
- ApaRgb& operator[]( int idx ) {
|
|
|
- return _firstBuffer[ idx ];
|
|
|
- }
|
|
|
+ ApaRgb& operator[](int idx) { return _firstBuffer[idx]; }
|
|
|
|
|
|
- const ApaRgb& operator[]( int idx ) const {
|
|
|
- return _firstBuffer[ idx ];
|
|
|
- }
|
|
|
+ const ApaRgb& operator[](int idx) const { return _firstBuffer[idx]; }
|
|
|
|
|
|
void show() {
|
|
|
_buffer = _firstBuffer.get();
|
|
|
@@ -390,93 +300,95 @@ public:
|
|
|
}
|
|
|
|
|
|
void wait() {
|
|
|
- for ( int i = 0; i != _transCount; i++ ) {
|
|
|
- spi_transaction_t *t;
|
|
|
- spi_device_get_trans_result( _spi, &t, portMAX_DELAY );
|
|
|
+ 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 );
|
|
|
+ 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;
|
|
|
+ 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 );
|
|
|
+ _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 );
|
|
|
+ _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 );
|
|
|
+ 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;
|
|
|
+ std::unique_ptr<ApaRgb[]> _firstBuffer, _secondBuffer;
|
|
|
+ ApaRgb* _buffer;
|
|
|
|
|
|
- spi_transaction_t _transactions[ TRANS_COUNT ];
|
|
|
+ spi_transaction_t _transactions[TRANS_COUNT];
|
|
|
int _transCount;
|
|
|
|
|
|
uint32_t _initFrame;
|
|
|
- uint32_t _finalFrame[ FINAL_FRAME_SIZE ];
|
|
|
+ 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(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 ) {
|
|
|
+ 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;
|
|
|
+ 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 };
|
|
|
+ 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 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 )
|
|
|
- {
|
|
|
+ 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 ) );
|
|
|
+ memset(&buscfg, 0, sizeof(buscfg));
|
|
|
buscfg.mosi_io_num = datapin;
|
|
|
buscfg.miso_io_num = -1;
|
|
|
buscfg.sclk_io_num = clkpin;
|
|
|
@@ -485,33 +397,29 @@ public:
|
|
|
buscfg.max_transfer_sz = 65535;
|
|
|
|
|
|
spi_device_interface_config_t devcfg;
|
|
|
- memset( &devcfg, 0, sizeof( 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( HSPI_HOST, &buscfg, 1 );
|
|
|
- assert( ret == ESP_OK );
|
|
|
+ auto ret = spi_bus_initialize(_SMARTLEDS_SPI_HOST, &buscfg, _SMARTLEDS_SPI_DMA_CHAN);
|
|
|
+ assert(ret == ESP_OK);
|
|
|
|
|
|
- ret = spi_bus_add_device( HSPI_HOST, &devcfg, &_spi );
|
|
|
- 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 );
|
|
|
+ std::fill_n(_latchBuffer, LATCH_FRAME_SIZE_BYTES, 0x0);
|
|
|
}
|
|
|
|
|
|
~LDP8806() {
|
|
|
// noop
|
|
|
}
|
|
|
|
|
|
- LDP8806_GRB& operator[]( int idx ) {
|
|
|
- return _firstBuffer[ idx ];
|
|
|
- }
|
|
|
+ LDP8806_GRB& operator[](int idx) { return _firstBuffer[idx]; }
|
|
|
|
|
|
- const LDP8806_GRB& operator[]( int idx ) const {
|
|
|
- return _firstBuffer[ idx ];
|
|
|
- }
|
|
|
+ const LDP8806_GRB& operator[](int idx) const { return _firstBuffer[idx]; }
|
|
|
|
|
|
void show() {
|
|
|
_buffer = _firstBuffer.get();
|
|
|
@@ -520,51 +428,50 @@ public:
|
|
|
}
|
|
|
|
|
|
void wait() {
|
|
|
- while ( _transCount-- ) {
|
|
|
- spi_transaction_t *t;
|
|
|
- spi_device_get_trans_result( _spi, &t, portMAX_DELAY );
|
|
|
+ while (_transCount--) {
|
|
|
+ spi_transaction_t* t;
|
|
|
+ spi_device_get_trans_result(_spi, &t, portMAX_DELAY);
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
private:
|
|
|
void swapBuffers() {
|
|
|
- if ( _secondBuffer )
|
|
|
- _firstBuffer.swap( _secondBuffer );
|
|
|
+ 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;
|
|
|
+ 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 );
|
|
|
+ _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 );
|
|
|
+ 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;
|
|
|
+ std::unique_ptr<LDP8806_GRB[]> _firstBuffer, _secondBuffer;
|
|
|
+ LDP8806_GRB* _buffer;
|
|
|
|
|
|
- spi_transaction_t _transactions[ TRANS_COUNT_MAX ];
|
|
|
+ spi_transaction_t _transactions[TRANS_COUNT_MAX];
|
|
|
int _transCount;
|
|
|
|
|
|
int _latchFrames;
|
|
|
- uint8_t _latchBuffer[ LATCH_FRAME_SIZE_BYTES ];
|
|
|
+ uint8_t _latchBuffer[LATCH_FRAME_SIZE_BYTES];
|
|
|
};
|
|
|
-
|
|
|
-#endif //SMARTLEDS_H
|