From 8f726b02327e95e53ef55a25a3b662f5217be77c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Rohl=C3=ADnek?= Date: Tue, 13 Apr 2021 23:30:43 +0200 Subject: [PATCH 01/34] Add alpha blending --- src/Color.cpp | 34 ++++++++++++++++++++++++++++++++++ src/Color.h | 9 +++++---- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/Color.cpp b/src/Color.cpp index 94d61bd..2d61c2a 100644 --- a/src/Color.cpp +++ b/src/Color.cpp @@ -1,6 +1,7 @@ #include "Color.h" #include #include +#include namespace { @@ -9,6 +10,25 @@ int up( int x ) { return x * 255; } } // namespace +int iRgbSqrt(int num) { + // https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Binary_numeral_system_.28base_2.29 + assert(("sqrt input should be non-negative", num >= 0)); + assert(("sqrt input should no exceed 16 bits", num <= 0xFFFF)); + int res = 0; + int bit = 1 << 16; + while (bit > num) + bit >>= 2; + while (bit != 0) { + if (num >= res + bit) { + num -= res + bit; + res = (res >> 1) + bit; + } else + res >>= 1; + bit >>= 2; + } + return res; +} + Rgb::Rgb( Hsv y ) { // https://stackoverflow.com/questions/24152553/hsv-to-rgb-and-back-without-floating-point-math-in-python // greyscale @@ -33,6 +53,8 @@ Rgb::Rgb( Hsv y ) { case 5: r = y.v; g = p; b = q; break; default: __builtin_trap(); } + + a = y.a; } Rgb& Rgb::operator=( Hsv hsv ) { @@ -57,6 +79,16 @@ Rgb& Rgb::operator+=( Rgb in ) { return *this; } +Rgb& Rgb::blend( Rgb in ) { + unsigned int inAlpha = in.a * ( 255 - a ); + unsigned int alpha = a + inAlpha; + r = iRgbSqrt( ( ( r * r * a ) + ( in.r * in.r * inAlpha ) ) / alpha ); + g = iRgbSqrt( ( ( g * g * a ) + ( in.g * in.g * inAlpha ) ) / alpha ); + b = iRgbSqrt( ( ( b * b * a ) + ( in.b * in.b * inAlpha ) ) / alpha ); + a = alpha; + return *this; +} + uint8_t IRAM_ATTR Rgb::getGrb( int idx ) { switch ( idx ) { case 0: return g; @@ -89,6 +121,8 @@ Hsv::Hsv( Rgb r ) { if ( hh < 0 ) hh += 255; h = hh; + + a = r.a; } Hsv& Hsv::operator=( Rgb rgb ) { diff --git a/src/Color.h b/src/Color.h index 368d2ea..d522568 100644 --- a/src/Color.h +++ b/src/Color.h @@ -6,16 +6,17 @@ union Hsv; union Rgb { struct __attribute__ ((packed)) { - uint8_t r, g, b, _extra; + uint8_t r, g, b, a; }; uint32_t value; - Rgb( uint8_t r = 0, uint8_t g = 0, uint8_t b = 0 ) : r( r ), g( g ), b( b ), _extra( 0 ) {} + Rgb( uint8_t r = 0, uint8_t g = 0, uint8_t b = 0, uint8_t a = 255) : r( r ), g( g ), b( b ), a( a ) {} Rgb( Hsv c ); Rgb& operator=( Rgb rgb ) { swap( rgb ); return *this; } Rgb& operator=( Hsv hsv ); Rgb operator+( Rgb in ) const; Rgb& operator+=( Rgb in ); + Rgb& blend( Rgb in ); void swap( Rgb& o ) { value = o.value; } void linearize() { r = ( static_cast< int >( r ) * static_cast< int >( r ) ) >> 8; @@ -43,11 +44,11 @@ union Rgb { union Hsv { struct __attribute__ ((packed)) { - uint8_t h, s, v, _extra; + uint8_t h, s, v, a; }; uint32_t value; - Hsv( uint8_t h, uint8_t s = 0, uint8_t v = 0 ) : h( h ), s( s ), v( v ), _extra( 0 ) {} + Hsv( uint8_t h, uint8_t s = 0, uint8_t v = 0, uint8_t a = 255 ) : h( h ), s( s ), v( v ), a( a ) {} Hsv( Rgb r ); Hsv& operator=( Hsv h ) { swap( h ); return *this; } Hsv& operator=( Rgb rgb ); From a36b5aefaea30272789a933b1c37718dabcd73cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Rohl=C3=ADnek?= Date: Wed, 14 Apr 2021 21:56:01 +0200 Subject: [PATCH 02/34] Add equality operator Fixes #7 --- src/Color.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Color.h b/src/Color.h index d522568..fa9db6c 100644 --- a/src/Color.h +++ b/src/Color.h @@ -16,6 +16,7 @@ union Rgb { Rgb& operator=( Hsv hsv ); Rgb operator+( Rgb in ) const; Rgb& operator+=( Rgb in ); + bool operator==( Rgb in ) const { return in.value == value; } Rgb& blend( Rgb in ); void swap( Rgb& o ) { value = o.value; } void linearize() { @@ -52,5 +53,6 @@ union Hsv { Hsv( Rgb r ); Hsv& operator=( Hsv h ) { swap( h ); return *this; } Hsv& operator=( Rgb rgb ); + bool operator==( Hsv in ) const { return in.value == value; } void swap( Hsv& o ) { value = o.value; } }; From 6f54b2fa883009464e62d297530069264a6918e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Rohl=C3=ADnek?= Date: Wed, 14 Apr 2021 10:30:22 +0200 Subject: [PATCH 03/34] Fix missing include closes #30 --- src/SmartLeds.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/SmartLeds.h b/src/SmartLeds.h index 7404a37..12d45b9 100644 --- a/src/SmartLeds.h +++ b/src/SmartLeds.h @@ -41,6 +41,10 @@ #include "freertos/semphr.h" #include "soc/rmt_struct.h" #include + #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... From 0003d4c1a2c9a5365afea841688d1e9a33fd8ed9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Rohl=C3=ADnek?= Date: Thu, 24 Jun 2021 18:27:53 +0200 Subject: [PATCH 04/34] Fix assert warning Fixes #35 --- src/Color.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Color.cpp b/src/Color.cpp index 2d61c2a..5d96fff 100644 --- a/src/Color.cpp +++ b/src/Color.cpp @@ -12,8 +12,8 @@ int up( int x ) { return x * 255; } int iRgbSqrt(int num) { // https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Binary_numeral_system_.28base_2.29 - assert(("sqrt input should be non-negative", num >= 0)); - assert(("sqrt input should no exceed 16 bits", num <= 0xFFFF)); + assert(("sqrt input should be non-negative" && num >= 0)); + assert(("sqrt input should no exceed 16 bits" && num <= 0xFFFF)); int res = 0; int bit = 1 << 16; while (bit > num) From 1fd43aa598b2c65924b5df66677f238fb7685016 Mon Sep 17 00:00:00 2001 From: Grant Rolls Date: Tue, 22 Jun 2021 17:17:28 +1000 Subject: [PATCH 05/34] Add LED control class for LD8806 --- README.md | 1 + src/SmartLeds.h | 128 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+) diff --git a/README.md b/README.md index 1e1104a..397edf8 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ Simple & intuitive way to drive various smart LEDs on ESP32. - SK6812 (RMT driver) - WS2813 (RMT driver) - APA102 (SPI driver) +- LPD8806 (SPI driver) All the LEDs are driven by hardware peripherals in order to achieve high performance. diff --git a/src/SmartLeds.h b/src/SmartLeds.h index 12d45b9..c96ab33 100644 --- a/src/SmartLeds.h +++ b/src/SmartLeds.h @@ -368,3 +368,131 @@ class Apa102 { 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( HSPI_HOST, &buscfg, 1 ); + assert( ret==ESP_OK ); + + ret=spi_bus_add_device( HSPI_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 ]; +}; \ No newline at end of file From 0fd159bfc8781e50607d0f77aeb6d9a424024dba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Rohl=C3=ADnek?= Date: Thu, 8 Jul 2021 16:39:24 +0200 Subject: [PATCH 06/34] Fix formatting --- src/Color.cpp | 4 ++-- src/SmartLeds.h | 32 ++++++++++++++++---------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/Color.cpp b/src/Color.cpp index 5d96fff..6bb1c2e 100644 --- a/src/Color.cpp +++ b/src/Color.cpp @@ -12,8 +12,8 @@ int up( int x ) { return x * 255; } int iRgbSqrt(int num) { // https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Binary_numeral_system_.28base_2.29 - assert(("sqrt input should be non-negative" && num >= 0)); - assert(("sqrt input should no exceed 16 bits" && num <= 0xFFFF)); + assert("sqrt input should be non-negative" && num >= 0); + assert("sqrt input should no exceed 16 bits" && num <= 0xFFFF); int res = 0; int bit = 1 << 16; while (bit > num) diff --git a/src/SmartLeds.h b/src/SmartLeds.h index c96ab33..5543503 100644 --- a/src/SmartLeds.h +++ b/src/SmartLeds.h @@ -42,7 +42,7 @@ #include "soc/rmt_struct.h" #include #include "esp_idf_version.h" -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0) +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL( 4, 0, 0 ) #include "soc/dport_reg.h" #endif } @@ -110,7 +110,7 @@ class SmartLed { assert( channel >= 0 && channel < 8 ); assert( ledForChannel( channel ) == nullptr ); - xSemaphoreGive(_finishedFlag); + 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 ); @@ -161,8 +161,8 @@ class SmartLed { swapBuffers(); } - bool wait(TickType_t timeout = portMAX_DELAY) { - if(xSemaphoreTake( _finishedFlag, timeout ) == pdTRUE) { + bool wait( TickType_t timeout = portMAX_DELAY ) { + if( xSemaphoreTake( _finishedFlag, timeout ) == pdTRUE ) { xSemaphoreGive( _finishedFlag ); return true; } @@ -199,7 +199,7 @@ class SmartLed { RMT.conf_ch[ channel ].conf1.idle_out_lv = 0; } - static SmartLed*& IRAM_ATTR ledForChannel(int channel); + static SmartLed*& IRAM_ATTR ledForChannel( int channel ); static void IRAM_ATTR interruptHandler(void*); void IRAM_ATTR copyRmtHalfBlock(); @@ -210,7 +210,7 @@ class SmartLed { void startTransmission() { // Invalid use of the library - if(xSemaphoreTake( _finishedFlag, 0 ) != pdTRUE) + if( xSemaphoreTake( _finishedFlag, 0 ) != pdTRUE ) abort(); _pixelPosition = _componentPosition = _halfIdx = 0; @@ -287,16 +287,16 @@ class Apa102 { spi_device_interface_config_t devcfg; memset( &devcfg, 0, sizeof( devcfg ) ); devcfg.clock_speed_hz = 1000000; - devcfg.mode=0; + 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( HSPI_HOST, &buscfg, 1 ); + assert( ret == ESP_OK ); - ret=spi_bus_add_device( HSPI_HOST, &devcfg, &_spi ); - assert(ret==ESP_OK); + ret = spi_bus_add_device( HSPI_HOST, &devcfg, &_spi ); + assert( ret == ESP_OK ); std::fill_n( _finalFrame, FINAL_FRAME_SIZE, 0xFFFFFFFF ); } @@ -417,16 +417,16 @@ class LDP8806 { spi_device_interface_config_t devcfg; memset( &devcfg, 0, sizeof( devcfg ) ); devcfg.clock_speed_hz = clock_speed_hz; - devcfg.mode=0; + 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( HSPI_HOST, &buscfg, 1 ); + assert( ret == ESP_OK ); - ret=spi_bus_add_device( HSPI_HOST, &devcfg, &_spi ); - assert( ret==ESP_OK ); + ret = spi_bus_add_device( HSPI_HOST, &devcfg, &_spi ); + assert( ret == ESP_OK ); std::fill_n( _latchBuffer, LATCH_FRAME_SIZE_BYTES, 0x0 ); } From f5805d3c55b08e76976727d04b8b1bef96a27e1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Rohl=C3=ADnek?= Date: Thu, 8 Jul 2021 16:53:11 +0200 Subject: [PATCH 07/34] Fix more formatting --- src/Color.cpp | 26 +++++++++++++------------- src/Color.h | 2 +- src/SmartLeds.cpp | 24 ++++++++++++------------ src/SmartLeds.h | 8 ++++---- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/Color.cpp b/src/Color.cpp index 6bb1c2e..b2beb9a 100644 --- a/src/Color.cpp +++ b/src/Color.cpp @@ -10,18 +10,18 @@ int up( int x ) { return x * 255; } } // namespace -int iRgbSqrt(int num) { +int iRgbSqrt( int num ) { // https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Binary_numeral_system_.28base_2.29 - assert("sqrt input should be non-negative" && num >= 0); - assert("sqrt input should no exceed 16 bits" && num <= 0xFFFF); + assert( "sqrt input should be non-negative" && num >= 0 ); + assert( "sqrt input should no exceed 16 bits" && num <= 0xFFFF ); int res = 0; int bit = 1 << 16; - while (bit > num) + while ( bit > num ) bit >>= 2; - while (bit != 0) { - if (num >= res + bit) { + while ( bit != 0 ) { + if ( num >= res + bit ) { num -= res + bit; - res = (res >> 1) + bit; + res = ( res >> 1 ) + bit; } else res >>= 1; bit >>= 2; @@ -32,19 +32,19 @@ int iRgbSqrt(int num) { Rgb::Rgb( Hsv y ) { // https://stackoverflow.com/questions/24152553/hsv-to-rgb-and-back-without-floating-point-math-in-python // greyscale - if(y.s == 0) { + if( y.s == 0 ) { r = g = b = y.v; return; } const int region = y.h / 43; - const int remainder = (y.h - (region * 43)) * 6; + const int remainder = ( y.h - ( region * 43 ) ) * 6; - const int p = (y.v * (255 - y.s)) >> 8; - const int q = (y.v * (255 - ((y.s * remainder) >> 8))) >> 8; - const int t = (y.v * (255 - ((y.s * (255 -remainder)) >> 8))) >> 8; + const int p = ( y.v * ( 255 - y.s ) ) >> 8; + const int q = ( y.v * ( 255 - ( ( y.s * remainder ) >> 8 ) ) ) >> 8; + const int t = ( y.v * ( 255 - ( ( y.s * (255 -remainder ) ) >> 8 ) ) ) >> 8; - switch(region) { + switch( region ) { case 0: r = y.v; g = t; b = p; break; case 1: r = q; g = y.v; b = p; break; case 2: r = p; g = y.v; b = t; break; diff --git a/src/Color.h b/src/Color.h index fa9db6c..deaa504 100644 --- a/src/Color.h +++ b/src/Color.h @@ -10,7 +10,7 @@ union Rgb { }; uint32_t value; - Rgb( uint8_t r = 0, uint8_t g = 0, uint8_t b = 0, uint8_t a = 255) : r( r ), g( g ), b( b ), a( a ) {} + Rgb( uint8_t r = 0, uint8_t g = 0, uint8_t b = 0, uint8_t a = 255 ) : r( r ), g( g ), b( b ), a( a ) {} Rgb( Hsv c ); Rgb& operator=( Rgb rgb ) { swap( rgb ); return *this; } Rgb& operator=( Hsv hsv ); diff --git a/src/SmartLeds.cpp b/src/SmartLeds.cpp index 6a4bc1c..5892b6e 100644 --- a/src/SmartLeds.cpp +++ b/src/SmartLeds.cpp @@ -1,23 +1,23 @@ #include "SmartLeds.h" -SmartLed*& IRAM_ATTR SmartLed::ledForChannel(int channel) { +SmartLed*& IRAM_ATTR SmartLed::ledForChannel( int channel ) { static SmartLed* table[8] = { nullptr }; - assert(channel < 8); - return table[channel]; + assert( channel < 8 ); + return table[ channel ]; } void IRAM_ATTR SmartLed::interruptHandler(void*) { for (int channel = 0; channel != 8; channel++) { - auto self = ledForChannel(channel); + auto self = ledForChannel( channel ); - if (RMT.int_st.val & (1 << (24 + channel))) { // tx_thr_event - if (self) + if ( RMT.int_st.val & (1 << (24 + channel ) ) ) { // tx_thr_event + if ( self ) self->copyRmtHalfBlock(); - RMT.int_clr.val |= 1 << (24 + channel); - } else if (RMT.int_st.val & (1 << (3 * channel))) { // tx_end - if (self) - xSemaphoreGiveFromISR(self->_finishedFlag, nullptr); - RMT.int_clr.val |= 1 << (3 * channel); + RMT.int_clr.val |= 1 << ( 24 + channel ); + } else if ( RMT.int_st.val & ( 1 << (3 * channel ) ) ) { // tx_end + if ( self ) + xSemaphoreGiveFromISR( self->_finishedFlag, nullptr ); + RMT.int_clr.val |= 1 << ( 3 * channel ); } } } @@ -30,7 +30,7 @@ void IRAM_ATTR SmartLed::copyRmtHalfBlock() { if ( !len ) { for ( int i = 0; i < detail::MAX_PULSES; i++) { - RMTMEM.chan[_channel].data32[i + offset].val = 0; + RMTMEM.chan[ _channel].data32[i + offset ].val = 0; } } diff --git a/src/SmartLeds.h b/src/SmartLeds.h index 5543503..8d54291 100644 --- a/src/SmartLeds.h +++ b/src/SmartLeds.h @@ -200,7 +200,7 @@ class SmartLed { } static SmartLed*& IRAM_ATTR ledForChannel( int channel ); - static void IRAM_ATTR interruptHandler(void*); + static void IRAM_ATTR interruptHandler( void* ); void IRAM_ATTR copyRmtHalfBlock(); void swapBuffers() { @@ -247,7 +247,7 @@ class SmartLed { class Apa102 { public: struct ApaRgb { - ApaRgb( uint8_t r = 0, uint8_t g = 0, uint32_t b = 0, uint32_t v = 0xFF) + 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 ) {} @@ -258,7 +258,7 @@ class Apa102 { return *this; } - ApaRgb& operator=(const Hsv& o ) { + ApaRgb& operator=( const Hsv& o ) { *this = Rgb{ o }; return *this; } @@ -401,7 +401,7 @@ class LDP8806 { 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), + _secondBuffer( doubleBuffer ? new LDP8806_GRB[ count ] : nullptr ), // one 'latch'/start-of-data mark frame for every 32 leds _latchFrames( ( count + 31 ) / 32 ) { From 8b799c58b9dae6ac765a2b184b391f9bcda50410 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Mr=C3=A1zek?= Date: Tue, 13 Jul 2021 14:58:34 +0200 Subject: [PATCH 08/34] Improve gamma correction --- src/Color.h | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Color.h b/src/Color.h index deaa504..3523284 100644 --- a/src/Color.h +++ b/src/Color.h @@ -20,9 +20,9 @@ union Rgb { Rgb& blend( Rgb in ); void swap( Rgb& o ) { value = o.value; } void linearize() { - r = ( static_cast< int >( r ) * static_cast< int >( r ) ) >> 8; - g = ( static_cast< int >( g ) * static_cast< int >( g ) ) >> 8; - b = ( static_cast< int >( b ) * static_cast< int >( b ) ) >> 8; + r = channelGamma(r); + g = channelGamma(g); + b = channelGamma(b); } uint8_t IRAM_ATTR getGrb( int idx ); @@ -41,6 +41,17 @@ union Rgb { uint8_t stretch( int value, uint8_t max ) { return ( value * max ) >> 8; } + + uint8_t channelGamma( int channel ) { + /* The optimal gamma correction is x^2.8. However, this is expensive to + * compute. Therefore, we use x^3 for gamma correction. Also, we add a + * bias as the WS2812 LEDs do not turn on for values less than 4. */ + if (channel == 0) + return channel; + channel = channel * channel * channel * 251; + channel >>= 24; + return static_cast< uin8_t >( 4 + channel ); + } }; union Hsv { From 048b25093a6a8ceb955e8e47ba5a197e3d7f8581 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Rohl=C3=ADnek?= Date: Wed, 14 Jul 2021 10:34:54 +0200 Subject: [PATCH 09/34] Fix typo --- src/Color.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Color.h b/src/Color.h index 3523284..1923cf0 100644 --- a/src/Color.h +++ b/src/Color.h @@ -50,7 +50,7 @@ union Rgb { return channel; channel = channel * channel * channel * 251; channel >>= 24; - return static_cast< uin8_t >( 4 + channel ); + return static_cast< uint8_t >( 4 + channel ); } }; From 396a663bf41108c3942bd377bbebc88dc08dad9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Bo=C4=8Dek?= Date: Fri, 16 Jul 2021 12:22:06 +0200 Subject: [PATCH 10/34] Allow users to choose which core is the RMT interrupt is allocated on --- src/SmartLeds.cpp | 3 +++ src/SmartLeds.h | 50 ++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/src/SmartLeds.cpp b/src/SmartLeds.cpp index 5892b6e..a2ba7b2 100644 --- a/src/SmartLeds.cpp +++ b/src/SmartLeds.cpp @@ -1,5 +1,8 @@ #include "SmartLeds.h" +IsrCore SmartLed::_interruptCore = CoreCurrent; +intr_handle_t SmartLed::_interruptHandle = NULL; + SmartLed*& IRAM_ATTR SmartLed::ledForChannel( int channel ) { static SmartLed* table[8] = { nullptr }; assert( channel < 8 ); diff --git a/src/SmartLeds.h b/src/SmartLeds.h index 8d54291..824eb33 100644 --- a/src/SmartLeds.h +++ b/src/SmartLeds.h @@ -36,6 +36,7 @@ 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" @@ -49,6 +50,7 @@ #elif defined ( ESP_PLATFORM ) extern "C" { // ...someone forgot to put in the includes... #include + #include #include #include #include @@ -97,9 +99,17 @@ static const LedType LED_WS2813 = { 350, 800, 350, 350, 300000 }; enum BufferType { SingleBuffer = 0, DoubleBuffer }; +enum IsrCore { CoreFirst = 0, CoreSecond = 1, CoreCurrent = 2}; + class SmartLed { public: - SmartLed( const LedType& type, int count, int pin, int channel = 0, BufferType doubleBuffer = SingleBuffer ) + // 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 ), @@ -134,16 +144,27 @@ class SmartLed { _bitToRmt[ 1 ].duration0 = _timing.T1H / ( detail::RMT_DURATION_NS * detail::DIVIDER ); _bitToRmt[ 1 ].duration1 = _timing.T1L / ( detail::RMT_DURATION_NS * detail::DIVIDER ); - if ( !anyAlive() ) - esp_intr_alloc( ETS_RMT_INTR_SOURCE, 0, interruptHandler, nullptr, &_interruptHandle ); + if ( !anyAlive() ) { + _interruptCore = isrCore; + if(isrCore != CoreCurrent) { + ESP_ERROR_CHECK(esp_ipc_call_blocking(isrCore, registerInterrupt, NULL)); + } else { + registerInterrupt(NULL); + } + } ledForChannel( channel ) = this; } ~SmartLed() { ledForChannel( _channel ) = nullptr; - if ( !anyAlive() ) - esp_intr_free( _interruptHandle ); + if ( !anyAlive() ) { + if(_interruptCore != CoreCurrent) { + ESP_ERROR_CHECK(esp_ipc_call_blocking(_interruptCore, unregisterInterrupt, NULL)); + } else { + unregisterInterrupt(NULL); + } + } vSemaphoreDelete( _finishedFlag ); } @@ -182,6 +203,9 @@ class SmartLed { 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 @@ -199,8 +223,17 @@ class SmartLed { 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 unregisterInterrupt(void*) { + esp_intr_free( _interruptHandle ); + } + static SmartLed*& IRAM_ATTR ledForChannel( int channel ); static void IRAM_ATTR interruptHandler( void* ); + void IRAM_ATTR copyRmtHalfBlock(); void swapBuffers() { @@ -235,7 +268,6 @@ class SmartLed { std::unique_ptr< Rgb[] > _firstBuffer; std::unique_ptr< Rgb[] > _secondBuffer; Rgb *_buffer; - intr_handle_t _interruptHandle; xSemaphoreHandle _finishedFlag; @@ -384,7 +416,7 @@ class LDP8806 { g = ( o.g * 127 / 256 ) | 0x80; b = ( o.b * 127 / 256 ) | 0x80; return *this; - } + } LDP8806_GRB& operator=( const Hsv& o ) { *this = Rgb{ o }; @@ -402,7 +434,7 @@ class LDP8806 { : _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 + // one 'latch'/start-of-data mark frame for every 32 leds _latchFrames( ( count + 31 ) / 32 ) { spi_bus_config_t buscfg; @@ -495,4 +527,4 @@ class LDP8806 { int _latchFrames; uint8_t _latchBuffer[ LATCH_FRAME_SIZE_BYTES ]; -}; \ No newline at end of file +}; From 832939c0c4373a63789c90749f0f78053575f99e Mon Sep 17 00:00:00 2001 From: Chris Snijder Date: Thu, 27 Oct 2022 00:59:31 +0200 Subject: [PATCH 11/34] Add RGB minus operator --- src/Color.cpp | 13 +++++++++++++ src/Color.h | 2 ++ 2 files changed, 15 insertions(+) diff --git a/src/Color.cpp b/src/Color.cpp index b2beb9a..aabde09 100644 --- a/src/Color.cpp +++ b/src/Color.cpp @@ -79,6 +79,19 @@ Rgb& Rgb::operator+=( Rgb in ) { return *this; } +Rgb Rgb::operator-( Rgb in ) const { + auto copy = *this; + copy -= in; + return copy; +} + +Rgb& Rgb::operator-=( Rgb in ) { + r = ( in.r > r ) ? 0 : r - in.r; + g = ( in.g > g ) ? 0 : g - in.g; + b = ( in.b > b ) ? 0 : b - in.b; + return *this; +} + Rgb& Rgb::blend( Rgb in ) { unsigned int inAlpha = in.a * ( 255 - a ); unsigned int alpha = a + inAlpha; diff --git a/src/Color.h b/src/Color.h index 1923cf0..6590ef0 100644 --- a/src/Color.h +++ b/src/Color.h @@ -16,6 +16,8 @@ union Rgb { Rgb& operator=( Hsv hsv ); Rgb operator+( Rgb in ) const; Rgb& operator+=( Rgb in ); + Rgb operator-(Rgb in) const; + Rgb &operator-=(Rgb in); bool operator==( Rgb in ) const { return in.value == value; } Rgb& blend( Rgb in ); void swap( Rgb& o ) { value = o.value; } From d8ae06fd334328cfa968c269362c9ff988878378 Mon Sep 17 00:00:00 2001 From: Vojtech Bocek Date: Tue, 14 Feb 2023 20:13:14 +0100 Subject: [PATCH 12/34] Use the RMT driver instead of registers directly --- src/Color.cpp | 2 +- src/Color.h | 2 +- src/SmartLeds.cpp | 53 +---------- src/SmartLeds.h | 222 ++++++++++++++++++++-------------------------- 4 files changed, 99 insertions(+), 180 deletions(-) diff --git a/src/Color.cpp b/src/Color.cpp index aabde09..244416e 100644 --- a/src/Color.cpp +++ b/src/Color.cpp @@ -102,7 +102,7 @@ Rgb& Rgb::blend( Rgb in ) { return *this; } -uint8_t IRAM_ATTR Rgb::getGrb( int idx ) { +uint8_t Rgb::getGrb( int idx ) const { switch ( idx ) { case 0: return g; case 1: return r; diff --git a/src/Color.h b/src/Color.h index 6590ef0..7f7d63d 100644 --- a/src/Color.h +++ b/src/Color.h @@ -27,7 +27,7 @@ union Rgb { b = channelGamma(b); } - uint8_t IRAM_ATTR getGrb( int idx ); + uint8_t getGrb( int idx ) const; void stretchChannels( uint8_t maxR, uint8_t maxG, uint8_t maxB ) { r = stretch( r, maxR ); diff --git a/src/SmartLeds.cpp b/src/SmartLeds.cpp index a2ba7b2..d86ef47 100644 --- a/src/SmartLeds.cpp +++ b/src/SmartLeds.cpp @@ -1,7 +1,6 @@ #include "SmartLeds.h" IsrCore SmartLed::_interruptCore = CoreCurrent; -intr_handle_t SmartLed::_interruptHandle = NULL; SmartLed*& IRAM_ATTR SmartLed::ledForChannel( int channel ) { static SmartLed* table[8] = { nullptr }; @@ -9,55 +8,7 @@ SmartLed*& IRAM_ATTR SmartLed::ledForChannel( int channel ) { return table[ channel ]; } -void IRAM_ATTR SmartLed::interruptHandler(void*) { - for (int channel = 0; channel != 8; channel++) { - auto self = ledForChannel( channel ); - - if ( RMT.int_st.val & (1 << (24 + channel ) ) ) { // tx_thr_event - if ( self ) - self->copyRmtHalfBlock(); - RMT.int_clr.val |= 1 << ( 24 + channel ); - } else if ( RMT.int_st.val & ( 1 << (3 * channel ) ) ) { // tx_end - if ( self ) - xSemaphoreGiveFromISR( self->_finishedFlag, nullptr ); - RMT.int_clr.val |= 1 << ( 3 * channel ); - } - } +void IRAM_ATTR SmartLed::txEndCallback(rmt_channel_t channel, void *arg) { + xSemaphoreGiveFromISR(ledForChannel(channel)->_finishedFlag, nullptr); } -void IRAM_ATTR SmartLed::copyRmtHalfBlock() { - int offset = detail::MAX_PULSES * _halfIdx; - _halfIdx = !_halfIdx; - int len = 3 - _componentPosition + 3 * ( _count - 1 ); - len = std::min( len, detail::MAX_PULSES / 8 ); - - if ( !len ) { - for ( int i = 0; i < detail::MAX_PULSES; i++) { - RMTMEM.chan[ _channel].data32[i + offset ].val = 0; - } - } - - int i; - for ( i = 0; i != len && _pixelPosition != _count; i++ ) { - uint8_t val = _buffer[ _pixelPosition ].getGrb( _componentPosition ); - for ( int j = 0; j != 8; j++, val <<= 1 ) { - int bit = val >> 7; - int idx = i * 8 + offset + j; - RMTMEM.chan[ _channel ].data32[ idx ].val = _bitToRmt[ bit & 0x01 ].value; - } - if ( _pixelPosition == _count - 1 && _componentPosition == 2 ) { - RMTMEM.chan[ _channel ].data32[ i * 8 + offset + 7 ].duration1 = - _timing.TRS / ( detail::RMT_DURATION_NS * detail::DIVIDER ); - } - - _componentPosition++; - if ( _componentPosition == 3 ) { - _componentPosition = 0; - _pixelPosition++; - } - } - - for ( i *= 8; i != detail::MAX_PULSES; i++ ) { - RMTMEM.chan[ _channel ].data32[ i + offset ].val = 0; - } -} diff --git a/src/SmartLeds.h b/src/SmartLeds.h index 824eb33..3e80d8e 100644 --- a/src/SmartLeds.h +++ b/src/SmartLeds.h @@ -32,35 +32,13 @@ #include #include -#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 - #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 - #include - #include - #include - #include - #include - #include - #include - #include - } - #include -#endif +#include +#include +#include +#include +#include +#include +#include #include "Color.h" @@ -74,18 +52,7 @@ struct TimingParams { 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 @@ -97,6 +64,7 @@ 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 }; +// Does nothing, kept for backwards compatibility. enum BufferType { SingleBuffer = 0, DoubleBuffer }; enum IsrCore { CoreFirst = 0, CoreSecond = 1, CoreCurrent = 2}; @@ -111,28 +79,25 @@ class SmartLed { // 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 ), + _channel((rmt_channel_t) channel ), _count( count ), - _firstBuffer( new Rgb[ count ] ), - _secondBuffer( doubleBuffer ? new Rgb[ count ] : nullptr ), + _framebuffer( new Rgb[ count ] ), _finishedFlag( xSemaphoreCreateBinary() ) { - assert( channel >= 0 && channel < 8 ); + assert( channel >= 0 && channel < RMT_CHANNEL_MAX ); 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 ); + _rmtBuffer.reset(new rmt_item32_t [ count * 3 * 8 ]); - 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_config_t config = RMT_DEFAULT_CONFIG_TX((gpio_num_t)pin, _channel); + config.rmt_mode = RMT_MODE_TX; + config.clk_div = detail::DIVIDER; + config.mem_block_num = 1; + config.tx_config.idle_output_en = true; - RMT.tx_lim_ch[ _channel ].limit = detail::MAX_PULSES; - RMT.int_ena.val |= 1 << ( 24 + _channel ); - RMT.int_ena.val |= 1 << ( 3 * _channel ); + ESP_ERROR_CHECK(rmt_config(&config)); _bitToRmt[ 0 ].level0 = 1; _bitToRmt[ 0 ].level1 = 0; @@ -144,13 +109,16 @@ class SmartLed { _bitToRmt[ 1 ].duration0 = _timing.T1H / ( detail::RMT_DURATION_NS * detail::DIVIDER ); _bitToRmt[ 1 ].duration1 = _timing.T1L / ( detail::RMT_DURATION_NS * detail::DIVIDER ); - if ( !anyAlive() ) { + const auto anyAliveCached = anyAlive(); + if (!anyAliveCached && 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*)((intptr_t)_channel))); + } else { + registerInterrupt((void*)((intptr_t)_channel)); + } + + if(!anyAliveCached) { + rmt_register_tx_end_callback(txEndCallback, NULL); } ledForChannel( channel ) = this; @@ -158,28 +126,24 @@ class SmartLed { ~SmartLed() { ledForChannel( _channel ) = nullptr; - if ( !anyAlive() ) { - if(_interruptCore != CoreCurrent) { - ESP_ERROR_CHECK(esp_ipc_call_blocking(_interruptCore, unregisterInterrupt, NULL)); - } else { - unregisterInterrupt(NULL); - } + if ( !anyAlive() && _interruptCore != CoreCurrent ) { + ESP_ERROR_CHECK(esp_ipc_call_blocking(_interruptCore, unregisterInterrupt, (void*)((intptr_t)_channel))); + } else { + unregisterInterrupt((void*)((intptr_t)_channel)); } vSemaphoreDelete( _finishedFlag ); } Rgb& operator[]( int idx ) { - return _firstBuffer[ idx ]; + return _framebuffer[ idx ]; } const Rgb& operator[]( int idx ) const { - return _firstBuffer[ idx ]; + return _framebuffer[ idx ]; } void show() { - _buffer = _firstBuffer.get(); startTransmission(); - swapBuffers(); } bool wait( TickType_t timeout = portMAX_DELAY ) { @@ -194,88 +158,92 @@ class SmartLed { return _count; } - Rgb *begin() { return _firstBuffer.get(); } - const Rgb *begin() const { return _firstBuffer.get(); } - const Rgb *cbegin() const { return _firstBuffer.get(); } + Rgb *begin() { return _framebuffer.get(); } + const Rgb *begin() const { return _framebuffer.get(); } + const Rgb *cbegin() const { return _framebuffer.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 _framebuffer.get() + _count; } + const Rgb *end() const { return _framebuffer.get() + _count; } + const Rgb *cend() const { return _framebuffer.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 *channelVoid) { + ESP_ERROR_CHECK(rmt_driver_install((rmt_channel_t)((intptr_t)channelVoid), 0, ESP_INTR_FLAG_IRAM)); } - static void unregisterInterrupt(void*) { - esp_intr_free( _interruptHandle ); + static void unregisterInterrupt(void *channelVoid) { + ESP_ERROR_CHECK(rmt_driver_uninstall((rmt_channel_t)((intptr_t)channelVoid))); } static SmartLed*& IRAM_ATTR ledForChannel( int channel ); - static void IRAM_ATTR interruptHandler( void* ); - - void IRAM_ATTR copyRmtHalfBlock(); - void swapBuffers() { - if ( _secondBuffer ) - _firstBuffer.swap( _secondBuffer ); + static bool anyAlive() { + for ( int i = 0; i != 8; i++ ) + if ( ledForChannel( i ) != nullptr ) return true; + return false; } - void startTransmission() { + static void IRAM_ATTR txEndCallback(rmt_channel_t channel, void *arg); + + esp_err_t startTransmission() { // Invalid use of the library if( xSemaphoreTake( _finishedFlag, 0 ) != pdTRUE ) abort(); - _pixelPosition = _componentPosition = _halfIdx = 0; - copyRmtHalfBlock(); - if ( _pixelPosition < _count ) - copyRmtHalfBlock(); + // each pixel + size_t rmt_idx = 0; + for (size_t i = 0; i < _count; ++i) { + const auto& pixel = _framebuffer[i]; - RMT.conf_ch[ _channel ].conf1.mem_rd_rst = 1; - RMT.conf_ch[ _channel ].conf1.tx_start = 1; + // each color channel + for(int c = 0; c < 3; ++c) { + uint8_t val = pixel.getGrb(c); + + // each bit, from highest to lowest + for ( int j = 0; j != 8; j++, val <<= 1 ) { + _rmtBuffer[rmt_idx++].val = _bitToRmt[ val >> 7 ].val; + } + } + + // delay after each pixel + if(rmt_idx > 0) { + _rmtBuffer[rmt_idx-1].duration1 = + _timing.TRS / ( detail::RMT_DURATION_NS * detail::DIVIDER ); + } + } + + auto err = rmt_write_items(_channel, _rmtBuffer.get(), bitCount(), false); + if(err != ESP_OK) { + return err; + } + + return ESP_OK; } - static bool anyAlive() { - for ( int i = 0; i != 8; i++ ) - if ( ledForChannel( i ) != nullptr ) return true; - return false; + size_t bitCount() const { + return size_t(_count) * 3 * 8; } const LedType& _timing; - int _channel; - detail::RmtPulsePair _bitToRmt[ 2 ]; + rmt_channel_t _channel; + rmt_item32_t _bitToRmt[ 2 ]; int _count; - std::unique_ptr< Rgb[] > _firstBuffer; - std::unique_ptr< Rgb[] > _secondBuffer; - Rgb *_buffer; + std::unique_ptr< Rgb[] > _framebuffer; + std::unique_ptr _rmtBuffer; xSemaphoreHandle _finishedFlag; - - int _pixelPosition; - int _componentPosition; - int _halfIdx; }; +#ifdef CONFIG_IDF_TARGET_ESP32 +#define _SMARTLEDS_SPI_HOST HSPI_HOST +#elif defined(CONFIG_IDF_TARGET_ESP32S3) +#define _SMARTLEDS_SPI_HOST SPI2_HOST +#else +#error "SmartLeds SPI host not defined for this chip/esp-idf version." +#endif + class Apa102 { public: struct ApaRgb { @@ -324,10 +292,10 @@ class Apa102 { devcfg.queue_size = TRANS_COUNT; devcfg.pre_cb = nullptr; - auto ret = spi_bus_initialize( HSPI_HOST, &buscfg, 1 ); + auto ret = spi_bus_initialize( _SMARTLEDS_SPI_HOST, &buscfg, 1 ); assert( ret == ESP_OK ); - ret = spi_bus_add_device( HSPI_HOST, &devcfg, &_spi ); + ret = spi_bus_add_device( _SMARTLEDS_SPI_HOST, &devcfg, &_spi ); assert( ret == ESP_OK ); std::fill_n( _finalFrame, FINAL_FRAME_SIZE, 0xFFFFFFFF ); @@ -454,10 +422,10 @@ class LDP8806 { devcfg.queue_size = TRANS_COUNT_MAX; devcfg.pre_cb = nullptr; - auto ret = spi_bus_initialize( HSPI_HOST, &buscfg, 1 ); + auto ret = spi_bus_initialize( _SMARTLEDS_SPI_HOST, &buscfg, 1 ); assert( ret == ESP_OK ); - ret = spi_bus_add_device( HSPI_HOST, &devcfg, &_spi ); + ret = spi_bus_add_device( _SMARTLEDS_SPI_HOST, &devcfg, &_spi ); assert( ret == ESP_OK ); std::fill_n( _latchBuffer, LATCH_FRAME_SIZE_BYTES, 0x0 ); From b75166a87824abd2fb5d975943ed7e2e2a07e124 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Bo=C4=8Dek?= Date: Thu, 16 Feb 2023 19:50:17 +0100 Subject: [PATCH 13/34] Use RMT translator feature to consume less memory --- src/Color.cpp | 18 ++++----- src/Color.h | 35 ++++++++-------- src/SmartLeds.cpp | 40 +++++++++++++++++++ src/SmartLeds.h | 100 +++++++++++++++++++++------------------------- 4 files changed, 112 insertions(+), 81 deletions(-) diff --git a/src/Color.cpp b/src/Color.cpp index 244416e..e1d1eda 100644 --- a/src/Color.cpp +++ b/src/Color.cpp @@ -29,7 +29,7 @@ int iRgbSqrt( int num ) { return res; } -Rgb::Rgb( Hsv y ) { +Rgb::Rgb(const Hsv& y ) { // https://stackoverflow.com/questions/24152553/hsv-to-rgb-and-back-without-floating-point-math-in-python // greyscale if( y.s == 0 ) { @@ -57,19 +57,19 @@ Rgb::Rgb( Hsv y ) { a = y.a; } -Rgb& Rgb::operator=( Hsv hsv ) { +Rgb& Rgb::operator=( const Hsv& hsv ) { Rgb r{ hsv }; swap( r ); return *this; } -Rgb Rgb::operator+( Rgb in ) const { +Rgb Rgb::operator+( const Rgb& in ) const { auto copy = *this; copy += in; return copy; } -Rgb& Rgb::operator+=( Rgb in ) { +Rgb& Rgb::operator+=( const Rgb& in ) { unsigned int red = r + in.r; r = ( red < 255 ) ? red : 255; unsigned int green = g + in.g; @@ -79,20 +79,20 @@ Rgb& Rgb::operator+=( Rgb in ) { return *this; } -Rgb Rgb::operator-( Rgb in ) const { +Rgb Rgb::operator-( const Rgb& in ) const { auto copy = *this; copy -= in; return copy; } -Rgb& Rgb::operator-=( Rgb in ) { +Rgb& Rgb::operator-=( const Rgb& in ) { r = ( in.r > r ) ? 0 : r - in.r; g = ( in.g > g ) ? 0 : g - in.g; b = ( in.b > b ) ? 0 : b - in.b; return *this; } -Rgb& Rgb::blend( Rgb in ) { +Rgb& Rgb::blend( const Rgb& in ) { unsigned int inAlpha = in.a * ( 255 - a ); unsigned int alpha = a + inAlpha; r = iRgbSqrt( ( ( r * r * a ) + ( in.r * in.r * inAlpha ) ) / alpha ); @@ -111,7 +111,7 @@ uint8_t Rgb::getGrb( int idx ) const { __builtin_unreachable(); } -Hsv::Hsv( Rgb r ) { +Hsv::Hsv( const Rgb& r ) { int min = std::min( r.r, std::min( r.g, r.b ) ); int max = std::max( r.r, std::max( r.g, r.b ) ); int chroma = max - min; @@ -138,7 +138,7 @@ Hsv::Hsv( Rgb r ) { a = r.a; } -Hsv& Hsv::operator=( Rgb rgb ) { +Hsv& Hsv::operator=( const Rgb& rgb ) { Hsv h{ rgb }; swap( h ); return *this; diff --git a/src/Color.h b/src/Color.h index 7f7d63d..d6aaeaa 100644 --- a/src/Color.h +++ b/src/Color.h @@ -6,21 +6,22 @@ union Hsv; union Rgb { struct __attribute__ ((packed)) { - uint8_t r, g, b, a; + uint8_t g, r, b, a; }; uint32_t value; - Rgb( uint8_t r = 0, uint8_t g = 0, uint8_t b = 0, uint8_t a = 255 ) : r( r ), g( g ), b( b ), a( a ) {} - Rgb( Hsv c ); - Rgb& operator=( Rgb rgb ) { swap( rgb ); return *this; } - Rgb& operator=( Hsv hsv ); - Rgb operator+( Rgb in ) const; - Rgb& operator+=( Rgb in ); - Rgb operator-(Rgb in) const; - Rgb &operator-=(Rgb in); - bool operator==( Rgb in ) const { return in.value == value; } - Rgb& blend( Rgb in ); - void swap( Rgb& o ) { value = o.value; } + Rgb( uint8_t r = 0, uint8_t g = 0, uint8_t b = 0, uint8_t a = 255 ) : g( g ), r( r ), b( b ), a( a ) {} + Rgb( const Hsv& c ); + Rgb(const Rgb&) = default; + Rgb& operator=( const Rgb& rgb ) { swap( rgb ); return *this; } + Rgb& operator=( const Hsv& hsv ); + Rgb operator+( const Rgb& in ) const; + Rgb& operator+=( const Rgb& in ); + Rgb operator-(const Rgb& in) const; + Rgb &operator-=(const Rgb& in); + bool operator==(const Rgb& in ) const { return in.value == value; } + Rgb& blend( const Rgb& in ); + void swap( const Rgb& o ) { value = o.value; } void linearize() { r = channelGamma(r); g = channelGamma(g); @@ -63,9 +64,9 @@ union Hsv { uint32_t value; Hsv( uint8_t h, uint8_t s = 0, uint8_t v = 0, uint8_t a = 255 ) : h( h ), s( s ), v( v ), a( a ) {} - Hsv( Rgb r ); - Hsv& operator=( Hsv h ) { swap( h ); return *this; } - Hsv& operator=( Rgb rgb ); - bool operator==( Hsv in ) const { return in.value == value; } - void swap( Hsv& o ) { value = o.value; } + Hsv( const Rgb& r ); + Hsv& operator=( const Hsv& h ) { swap( h ); return *this; } + Hsv& operator=( const Rgb& rgb ); + bool operator==( const Hsv& in ) const { return in.value == value; } + void swap( const Hsv& o ) { value = o.value; } }; diff --git a/src/SmartLeds.cpp b/src/SmartLeds.cpp index d86ef47..5046136 100644 --- a/src/SmartLeds.cpp +++ b/src/SmartLeds.cpp @@ -12,3 +12,43 @@ void IRAM_ATTR SmartLed::txEndCallback(rmt_channel_t channel, void *arg) { xSemaphoreGiveFromISR(ledForChannel(channel)->_finishedFlag, nullptr); } + +void IRAM_ATTR SmartLed::translateForRmt(const void *src, rmt_item32_t *dest, size_t src_size, + size_t wanted_rmt_items_num, size_t *out_consumed_src_bytes, size_t *out_used_rmt_items) { + SmartLed *self; + ESP_ERROR_CHECK(rmt_translator_get_context(out_used_rmt_items, (void**)&self)); + + const auto& _bitToRmt = self->_bitToRmt; + const auto src_offset = self->_translatorSourceOffset; + + const auto pixel_delay = self->_timing.TRS / ( detail::RMT_DURATION_NS * detail::DIVIDER ); + + auto *src_components = (const uint8_t *)src; + size_t consumed_src_bytes = 0; + size_t used_rmt_items = 0; + + while (consumed_src_bytes < src_size && used_rmt_items + 7 < wanted_rmt_items_num) { + uint8_t val = *src_components; + + // each bit, from highest to lowest + for ( uint8_t j = 0; j != 8; j++, val <<= 1 ) { + dest->val = _bitToRmt[ val >> 7 ].val; + ++dest; + } + + used_rmt_items += 8; + ++src_components; + ++consumed_src_bytes; + + // skip alpha byte, delay after each pixel + if(((src_offset + consumed_src_bytes) % 4) == 3) { + (dest-1)->duration1 = pixel_delay; + ++src_components; + ++consumed_src_bytes; + } + } + + self->_translatorSourceOffset = src_offset + consumed_src_bytes; + *out_consumed_src_bytes = consumed_src_bytes; + *out_used_rmt_items = used_rmt_items; +} diff --git a/src/SmartLeds.h b/src/SmartLeds.h index 3e80d8e..0aa8217 100644 --- a/src/SmartLeds.h +++ b/src/SmartLeds.h @@ -38,6 +38,7 @@ #include #include #include + #include #include "Color.h" @@ -64,7 +65,7 @@ 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 }; -// Does nothing, kept for backwards compatibility. +// Single buffer == can't touch the Rgbs between show() and wait() enum BufferType { SingleBuffer = 0, DoubleBuffer }; enum IsrCore { CoreFirst = 0, CoreSecond = 1, CoreCurrent = 2}; @@ -77,28 +78,19 @@ class SmartLed { // // 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((rmt_channel_t) channel ), - _count( count ), - _framebuffer( new Rgb[ count ] ), - _finishedFlag( xSemaphoreCreateBinary() ) + SmartLed(const LedType &type, int count, int pin, int channel = 0, BufferType doubleBuffer = DoubleBuffer, IsrCore isrCore = CoreCurrent) + : _timing(type), + _channel((rmt_channel_t)channel), + _count(count), + _firstBuffer(new Rgb[count]), + _secondBuffer( doubleBuffer ? new Rgb[ count ] : nullptr ), + _finishedFlag(xSemaphoreCreateBinary()) { assert( channel >= 0 && channel < RMT_CHANNEL_MAX ); assert( ledForChannel( channel ) == nullptr ); xSemaphoreGive( _finishedFlag ); - _rmtBuffer.reset(new rmt_item32_t [ count * 3 * 8 ]); - - rmt_config_t config = RMT_DEFAULT_CONFIG_TX((gpio_num_t)pin, _channel); - config.rmt_mode = RMT_MODE_TX; - config.clk_div = detail::DIVIDER; - config.mem_block_num = 1; - config.tx_config.idle_output_en = true; - - ESP_ERROR_CHECK(rmt_config(&config)); - _bitToRmt[ 0 ].level0 = 1; _bitToRmt[ 0 ].level1 = 0; _bitToRmt[ 0 ].duration0 = _timing.T0H / ( detail::RMT_DURATION_NS * detail::DIVIDER ); @@ -109,6 +101,14 @@ class SmartLed { _bitToRmt[ 1 ].duration0 = _timing.T1H / ( detail::RMT_DURATION_NS * detail::DIVIDER ); _bitToRmt[ 1 ].duration1 = _timing.T1L / ( detail::RMT_DURATION_NS * detail::DIVIDER ); + rmt_config_t config = RMT_DEFAULT_CONFIG_TX((gpio_num_t)pin, _channel); + config.rmt_mode = RMT_MODE_TX; + config.clk_div = detail::DIVIDER; + config.mem_block_num = 1; + config.tx_config.idle_output_en = true; + + ESP_ERROR_CHECK(rmt_config(&config)); + const auto anyAliveCached = anyAlive(); if (!anyAliveCached && isrCore != CoreCurrent) { _interruptCore = isrCore; @@ -121,6 +121,9 @@ class SmartLed { rmt_register_tx_end_callback(txEndCallback, NULL); } + ESP_ERROR_CHECK(rmt_translator_init(_channel, translateForRmt)); + ESP_ERROR_CHECK(rmt_translator_set_context(_channel, this)); + ledForChannel( channel ) = this; } @@ -135,15 +138,16 @@ class SmartLed { } Rgb& operator[]( int idx ) { - return _framebuffer[ idx ]; + return _firstBuffer[ idx ]; } const Rgb& operator[]( int idx ) const { - return _framebuffer[ idx ]; + return _firstBuffer[ idx ]; } void show() { startTransmission(); + swapBuffers(); } bool wait( TickType_t timeout = portMAX_DELAY ) { @@ -158,13 +162,13 @@ class SmartLed { return _count; } - Rgb *begin() { return _framebuffer.get(); } - const Rgb *begin() const { return _framebuffer.get(); } - const Rgb *cbegin() const { return _framebuffer.get(); } + Rgb *begin() { return _firstBuffer.get(); } + const Rgb *begin() const { return _firstBuffer.get(); } + const Rgb *cbegin() const { return _firstBuffer.get(); } - Rgb *end() { return _framebuffer.get() + _count; } - const Rgb *end() const { return _framebuffer.get() + _count; } - const Rgb *cend() const { return _framebuffer.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 IsrCore _interruptCore; @@ -187,34 +191,22 @@ class SmartLed { static void IRAM_ATTR txEndCallback(rmt_channel_t channel, void *arg); + static void IRAM_ATTR translateForRmt(const void *src, rmt_item32_t *dest, size_t src_size, + size_t wanted_rmt_items_num, size_t *out_consumed_src_bytes, size_t *out_used_rmt_items); + + void swapBuffers() { + if (_secondBuffer) + _firstBuffer.swap(_secondBuffer); + } + esp_err_t startTransmission() { - // Invalid use of the library + // Invalid use of the library, you must wait() fir previous frame to get processed first if( xSemaphoreTake( _finishedFlag, 0 ) != pdTRUE ) abort(); - // each pixel - size_t rmt_idx = 0; - for (size_t i = 0; i < _count; ++i) { - const auto& pixel = _framebuffer[i]; - - // each color channel - for(int c = 0; c < 3; ++c) { - uint8_t val = pixel.getGrb(c); - - // each bit, from highest to lowest - for ( int j = 0; j != 8; j++, val <<= 1 ) { - _rmtBuffer[rmt_idx++].val = _bitToRmt[ val >> 7 ].val; - } - } - - // delay after each pixel - if(rmt_idx > 0) { - _rmtBuffer[rmt_idx-1].duration1 = - _timing.TRS / ( detail::RMT_DURATION_NS * detail::DIVIDER ); - } - } + _translatorSourceOffset = 0; - auto err = rmt_write_items(_channel, _rmtBuffer.get(), bitCount(), false); + auto err = rmt_write_sample(_channel, (const uint8_t *)_firstBuffer.get(), _count * 4, false); if(err != ESP_OK) { return err; } @@ -222,18 +214,16 @@ class SmartLed { return ESP_OK; } - size_t bitCount() const { - return size_t(_count) * 3 * 8; - } - const LedType& _timing; rmt_channel_t _channel; rmt_item32_t _bitToRmt[ 2 ]; int _count; - std::unique_ptr< Rgb[] > _framebuffer; - std::unique_ptr _rmtBuffer; + std::unique_ptr _firstBuffer; + std::unique_ptr _secondBuffer; + + size_t _translatorSourceOffset; - xSemaphoreHandle _finishedFlag; + SemaphoreHandle_t _finishedFlag; }; #ifdef CONFIG_IDF_TARGET_ESP32 From 43dde2314fe457de77f06d0e9cca312fd52a3144 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Bo=C4=8Dek?= Date: Sat, 18 Feb 2023 16:23:20 +0100 Subject: [PATCH 14/34] Remove useless delay after each pixel, make it after whole strip only As per WS2812 datasheet --- src/SmartLeds.cpp | 10 ++++++---- src/SmartLeds.h | 5 +++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/SmartLeds.cpp b/src/SmartLeds.cpp index 5046136..5a22e93 100644 --- a/src/SmartLeds.cpp +++ b/src/SmartLeds.cpp @@ -21,8 +21,6 @@ void IRAM_ATTR SmartLed::translateForRmt(const void *src, rmt_item32_t *dest, si const auto& _bitToRmt = self->_bitToRmt; const auto src_offset = self->_translatorSourceOffset; - const auto pixel_delay = self->_timing.TRS / ( detail::RMT_DURATION_NS * detail::DIVIDER ); - auto *src_components = (const uint8_t *)src; size_t consumed_src_bytes = 0; size_t used_rmt_items = 0; @@ -40,11 +38,15 @@ void IRAM_ATTR SmartLed::translateForRmt(const void *src, rmt_item32_t *dest, si ++src_components; ++consumed_src_bytes; - // skip alpha byte, delay after each pixel + // skip alpha byte if(((src_offset + consumed_src_bytes) % 4) == 3) { - (dest-1)->duration1 = pixel_delay; ++src_components; ++consumed_src_bytes; + + // TRST delay after last pixel in strip + if(consumed_src_bytes == src_size) { + (dest-1)->duration1 = self->_timing.TRS / ( detail::RMT_DURATION_NS * detail::DIVIDER ); + } } } diff --git a/src/SmartLeds.h b/src/SmartLeds.h index 0aa8217..e654aeb 100644 --- a/src/SmartLeds.h +++ b/src/SmartLeds.h @@ -145,9 +145,10 @@ class SmartLed { return _firstBuffer[ idx ]; } - void show() { - startTransmission(); + esp_err_t show() { + esp_err_t err = startTransmission(); swapBuffers(); + return err; } bool wait( TickType_t timeout = portMAX_DELAY ) { From 7e2c93dfe679e16f61ecd1ca6845a1d237635b73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Bo=C4=8Dek?= Date: Sat, 18 Feb 2023 16:24:15 +0100 Subject: [PATCH 15/34] Release 2.0.0 --- library.json | 2 +- library.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library.json b/library.json index e8bbadd..6a6a01c 100644 --- a/library.json +++ b/library.json @@ -15,7 +15,7 @@ "maintainer": true } ], - "version": "1.2.1", + "version": "2.0.0", "frameworks": ["arduino","espidf"], "platforms": "espressif32" } diff --git a/library.properties b/library.properties index cb36524..2c43807 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=SmartLeds -version=1.2.1 +version=2.0.0 author=Jan Mrázek maintainer=Jan Mrázek sentence=Simple & intuitive way to drive various smart LEDs on ESP32. From 7e92147351c969b16513b3bf39db2f65b6c8811a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Bo=C4=8Dek?= Date: Sun, 19 Feb 2023 13:30:21 +0100 Subject: [PATCH 16/34] Refactor to use the new driver on IDF 5.0 --- .clang-format | 127 +++++++++++++ src/Color.cpp | 148 ++++++++------- src/Color.h | 84 +++++---- src/RmtDriver.h | 30 +++ src/RmtDriver4.cpp | 111 +++++++++++ src/RmtDriver4.h | 42 +++++ src/RmtDriver5.cpp | 173 +++++++++++++++++ src/RmtDriver5.h | 65 +++++++ src/SmartLeds.cpp | 55 +----- src/SmartLeds.h | 394 +++++++++++++++++---------------------- test/catch.hpp | 1 - test/colorConversion.cpp | 132 ++++++------- test/main.cpp | 2 +- 13 files changed, 929 insertions(+), 435 deletions(-) create mode 100644 .clang-format create mode 100644 src/RmtDriver.h create mode 100644 src/RmtDriver4.cpp create mode 100644 src/RmtDriver4.h create mode 100644 src/RmtDriver5.cpp create mode 100644 src/RmtDriver5.h diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..bbb6cfb --- /dev/null +++ b/.clang-format @@ -0,0 +1,127 @@ +--- +Language: Cpp +# BasedOnStyle: WebKit +AccessModifierOffset: -4 +AlignAfterOpenBracket: DontAlign +AlignConsecutiveMacros: false +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Right +AlignOperands: false +AlignTrailingComments: false +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: false + AfterClass: true + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: All +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeComma +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 0 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: false +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: false +IndentPPDirectives: None +IndentWidth: 4 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 4 +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Left +ReflowComments: false +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseTab: Never +ColumnLimit: 120 +... diff --git a/src/Color.cpp b/src/Color.cpp index e1d1eda..9ab4fcc 100644 --- a/src/Color.cpp +++ b/src/Color.cpp @@ -1,27 +1,27 @@ #include "Color.h" #include -#include #include +#include namespace { // Int -> fixed point -int up( int x ) { return x * 255; } +int up(int x) { return x * 255; } } // namespace -int iRgbSqrt( int num ) { +int iRgbSqrt(int num) { // https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Binary_numeral_system_.28base_2.29 - assert( "sqrt input should be non-negative" && num >= 0 ); - assert( "sqrt input should no exceed 16 bits" && num <= 0xFFFF ); + assert("sqrt input should be non-negative" && num >= 0); + assert("sqrt input should no exceed 16 bits" && num <= 0xFFFF); int res = 0; int bit = 1 << 16; - while ( bit > num ) + while (bit > num) bit >>= 2; - while ( bit != 0 ) { - if ( num >= res + bit ) { + while (bit != 0) { + if (num >= res + bit) { num -= res + bit; - res = ( res >> 1 ) + bit; + res = (res >> 1) + bit; } else res >>= 1; bit >>= 2; @@ -29,117 +29,133 @@ int iRgbSqrt( int num ) { return res; } -Rgb::Rgb(const Hsv& y ) { +Rgb::Rgb(const Hsv& y) { // https://stackoverflow.com/questions/24152553/hsv-to-rgb-and-back-without-floating-point-math-in-python // greyscale - if( y.s == 0 ) { + if (y.s == 0) { r = g = b = y.v; return; } const int region = y.h / 43; - const int remainder = ( y.h - ( region * 43 ) ) * 6; - - const int p = ( y.v * ( 255 - y.s ) ) >> 8; - const int q = ( y.v * ( 255 - ( ( y.s * remainder ) >> 8 ) ) ) >> 8; - const int t = ( y.v * ( 255 - ( ( y.s * (255 -remainder ) ) >> 8 ) ) ) >> 8; - - switch( region ) { - case 0: r = y.v; g = t; b = p; break; - case 1: r = q; g = y.v; b = p; break; - case 2: r = p; g = y.v; b = t; break; - case 3: r = p; g = q; b = y.v; break; - case 4: r = t; g = p; b = y.v; break; - case 5: r = y.v; g = p; b = q; break; - default: __builtin_trap(); + const int remainder = (y.h - (region * 43)) * 6; + + const int p = (y.v * (255 - y.s)) >> 8; + const int q = (y.v * (255 - ((y.s * remainder) >> 8))) >> 8; + const int t = (y.v * (255 - ((y.s * (255 - remainder)) >> 8))) >> 8; + + switch (region) { + case 0: + r = y.v; + g = t; + b = p; + break; + case 1: + r = q; + g = y.v; + b = p; + break; + case 2: + r = p; + g = y.v; + b = t; + break; + case 3: + r = p; + g = q; + b = y.v; + break; + case 4: + r = t; + g = p; + b = y.v; + break; + case 5: + r = y.v; + g = p; + b = q; + break; + default: + __builtin_trap(); } a = y.a; } -Rgb& Rgb::operator=( const Hsv& hsv ) { - Rgb r{ hsv }; - swap( r ); +Rgb& Rgb::operator=(const Hsv& hsv) { + Rgb r { hsv }; + swap(r); return *this; } -Rgb Rgb::operator+( const Rgb& in ) const { +Rgb Rgb::operator+(const Rgb& in) const { auto copy = *this; copy += in; return copy; } -Rgb& Rgb::operator+=( const Rgb& in ) { +Rgb& Rgb::operator+=(const Rgb& in) { unsigned int red = r + in.r; - r = ( red < 255 ) ? red : 255; + r = (red < 255) ? red : 255; unsigned int green = g + in.g; - g = ( green < 255 ) ? green : 255; + g = (green < 255) ? green : 255; unsigned int blue = b + in.b; - b = ( blue < 255 ) ? blue : 255; + b = (blue < 255) ? blue : 255; return *this; } -Rgb Rgb::operator-( const Rgb& in ) const { +Rgb Rgb::operator-(const Rgb& in) const { auto copy = *this; copy -= in; return copy; } -Rgb& Rgb::operator-=( const Rgb& in ) { - r = ( in.r > r ) ? 0 : r - in.r; - g = ( in.g > g ) ? 0 : g - in.g; - b = ( in.b > b ) ? 0 : b - in.b; +Rgb& Rgb::operator-=(const Rgb& in) { + r = (in.r > r) ? 0 : r - in.r; + g = (in.g > g) ? 0 : g - in.g; + b = (in.b > b) ? 0 : b - in.b; return *this; } -Rgb& Rgb::blend( const Rgb& in ) { - unsigned int inAlpha = in.a * ( 255 - a ); +Rgb& Rgb::blend(const Rgb& in) { + unsigned int inAlpha = in.a * (255 - a); unsigned int alpha = a + inAlpha; - r = iRgbSqrt( ( ( r * r * a ) + ( in.r * in.r * inAlpha ) ) / alpha ); - g = iRgbSqrt( ( ( g * g * a ) + ( in.g * in.g * inAlpha ) ) / alpha ); - b = iRgbSqrt( ( ( b * b * a ) + ( in.b * in.b * inAlpha ) ) / alpha ); + r = iRgbSqrt(((r * r * a) + (in.r * in.r * inAlpha)) / alpha); + g = iRgbSqrt(((g * g * a) + (in.g * in.g * inAlpha)) / alpha); + b = iRgbSqrt(((b * b * a) + (in.b * in.b * inAlpha)) / alpha); a = alpha; return *this; } -uint8_t Rgb::getGrb( int idx ) const { - switch ( idx ) { - case 0: return g; - case 1: return r; - case 2: return b; - } - __builtin_unreachable(); -} - -Hsv::Hsv( const Rgb& r ) { - int min = std::min( r.r, std::min( r.g, r.b ) ); - int max = std::max( r.r, std::max( r.g, r.b ) ); +Hsv::Hsv(const Rgb& r) { + int min = std::min(r.r, std::min(r.g, r.b)); + int max = std::max(r.r, std::max(r.g, r.b)); int chroma = max - min; v = max; - if ( chroma == 0 ) { + if (chroma == 0) { h = s = 0; return; } - s = up( chroma ) / max; + s = up(chroma) / max; int hh; - if ( max == r.r ) - hh = ( up( int( r.g ) - int( r.b ) ) ) / chroma / 6; - else if ( max == r.g ) - hh = 255 / 3 + ( up( int( r.b ) - int( r.r ) ) ) / chroma / 6; + if (max == r.r) + hh = (up(int(r.g) - int(r.b))) / chroma / 6; + else if (max == r.g) + hh = 255 / 3 + (up(int(r.b) - int(r.r))) / chroma / 6; else - hh = 2 * 255 / 3 + ( up( int( r.r ) - int( r.g ) ) ) / chroma / 6; + hh = 2 * 255 / 3 + (up(int(r.r) - int(r.g))) / chroma / 6; - if ( hh < 0 ) + if (hh < 0) hh += 255; h = hh; a = r.a; } -Hsv& Hsv::operator=( const Rgb& rgb ) { - Hsv h{ rgb }; - swap( h ); +Hsv& Hsv::operator=(const Rgb& rgb) { + Hsv h { rgb }; + swap(h); return *this; } diff --git a/src/Color.h b/src/Color.h index d6aaeaa..2baac12 100644 --- a/src/Color.h +++ b/src/Color.h @@ -1,51 +1,64 @@ #pragma once -#include #include "esp_attr.h" +#include union Hsv; union Rgb { - struct __attribute__ ((packed)) { + struct __attribute__((packed)) { uint8_t g, r, b, a; }; uint32_t value; - Rgb( uint8_t r = 0, uint8_t g = 0, uint8_t b = 0, uint8_t a = 255 ) : g( g ), r( r ), b( b ), a( a ) {} - Rgb( const Hsv& c ); + Rgb(uint8_t r = 0, uint8_t g = 0, uint8_t b = 0, uint8_t a = 255) + : g(g) + , r(r) + , b(b) + , a(a) {} + Rgb(const Hsv& c); Rgb(const Rgb&) = default; - Rgb& operator=( const Rgb& rgb ) { swap( rgb ); return *this; } - Rgb& operator=( const Hsv& hsv ); - Rgb operator+( const Rgb& in ) const; - Rgb& operator+=( const Rgb& in ); + Rgb& operator=(const Rgb& rgb) { + swap(rgb); + return *this; + } + Rgb& operator=(const Hsv& hsv); + Rgb operator+(const Rgb& in) const; + Rgb& operator+=(const Rgb& in); Rgb operator-(const Rgb& in) const; - Rgb &operator-=(const Rgb& in); - bool operator==(const Rgb& in ) const { return in.value == value; } - Rgb& blend( const Rgb& in ); - void swap( const Rgb& o ) { value = o.value; } + Rgb& operator-=(const Rgb& in); + bool operator==(const Rgb& in) const { return in.value == value; } + Rgb& blend(const Rgb& in); + void swap(const Rgb& o) { value = o.value; } void linearize() { r = channelGamma(r); g = channelGamma(g); b = channelGamma(b); } - uint8_t getGrb( int idx ) const; - - void stretchChannels( uint8_t maxR, uint8_t maxG, uint8_t maxB ) { - r = stretch( r, maxR ); - g = stretch( g, maxG ); - b = stretch( b, maxB ); + inline uint8_t IRAM_ATTR getGrb(int idx) { + switch (idx) { + case 0: + return g; + case 1: + return r; + case 2: + return b; + } + __builtin_unreachable(); } - void stretchChannelsEvenly( uint8_t max ) { - stretchChannels( max, max, max ); + void stretchChannels(uint8_t maxR, uint8_t maxG, uint8_t maxB) { + r = stretch(r, maxR); + g = stretch(g, maxG); + b = stretch(b, maxB); } + void stretchChannelsEvenly(uint8_t max) { stretchChannels(max, max, max); } + private: - uint8_t stretch( int value, uint8_t max ) { - return ( value * max ) >> 8; - } + uint8_t stretch(int value, uint8_t max) { return (value * max) >> 8; } - uint8_t channelGamma( int channel ) { + uint8_t channelGamma(int channel) { /* The optimal gamma correction is x^2.8. However, this is expensive to * compute. Therefore, we use x^3 for gamma correction. Also, we add a * bias as the WS2812 LEDs do not turn on for values less than 4. */ @@ -53,20 +66,27 @@ union Rgb { return channel; channel = channel * channel * channel * 251; channel >>= 24; - return static_cast< uint8_t >( 4 + channel ); + return static_cast(4 + channel); } }; union Hsv { - struct __attribute__ ((packed)) { + struct __attribute__((packed)) { uint8_t h, s, v, a; }; uint32_t value; - Hsv( uint8_t h, uint8_t s = 0, uint8_t v = 0, uint8_t a = 255 ) : h( h ), s( s ), v( v ), a( a ) {} - Hsv( const Rgb& r ); - Hsv& operator=( const Hsv& h ) { swap( h ); return *this; } - Hsv& operator=( const Rgb& rgb ); - bool operator==( const Hsv& in ) const { return in.value == value; } - void swap( const Hsv& o ) { value = o.value; } + Hsv(uint8_t h, uint8_t s = 0, uint8_t v = 0, uint8_t a = 255) + : h(h) + , s(s) + , v(v) + , a(a) {} + Hsv(const Rgb& r); + Hsv& operator=(const Hsv& h) { + swap(h); + return *this; + } + Hsv& operator=(const Rgb& rgb); + bool operator==(const Hsv& in) const { return in.value == value; } + void swap(const Hsv& o) { value = o.value; } }; diff --git a/src/RmtDriver.h b/src/RmtDriver.h new file mode 100644 index 0000000..69275a9 --- /dev/null +++ b/src/RmtDriver.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +#if (defined(ESP_IDF_VERSION) && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)) +#define SMARTLEDS_NEW_RMT_DRIVER 1 +#else +#define SMARTLEDS_NEW_RMT_DRIVER 0 +#endif + +namespace detail { + +struct TimingParams { + uint32_t T0H; + uint32_t T1H; + uint32_t T0L; + uint32_t T1L; + uint32_t TRS; +}; + +using LedType = TimingParams; + +} // namespace detail + +#if SMARTLEDS_NEW_RMT_DRIVER +#include "RmtDriver5.h" +#else +#include "RmtDriver4.h" +#endif diff --git a/src/RmtDriver4.cpp b/src/RmtDriver4.cpp new file mode 100644 index 0000000..41e0565 --- /dev/null +++ b/src/RmtDriver4.cpp @@ -0,0 +1,111 @@ +#include "RmtDriver4.h" + +#if !SMARTLEDS_NEW_RMT_DRIVER +#include "SmartLeds.h" + +namespace detail { + +// 8 still seems to work, but timings become marginal +static const int DIVIDER = 4; +// minimum time of a single RMT duration based on clock ns +static const double RMT_DURATION_NS = 12.5; + +RmtDriver::RmtDriver(const LedType& timing, int count, int pin, int channel_num, SemaphoreHandle_t finishedFlag) + : _timing(timing) + , _count(count) + , _pin((gpio_num_t)pin) + , _finishedFlag(finishedFlag) + , _channel((rmt_channel_t)channel_num) { + _bitToRmt[0].level0 = 1; + _bitToRmt[0].level1 = 0; + _bitToRmt[0].duration0 = _timing.T0H / (RMT_DURATION_NS * DIVIDER); + _bitToRmt[0].duration1 = _timing.T0L / (RMT_DURATION_NS * DIVIDER); + + _bitToRmt[1].level0 = 1; + _bitToRmt[1].level1 = 0; + _bitToRmt[1].duration0 = _timing.T1H / (RMT_DURATION_NS * DIVIDER); + _bitToRmt[1].duration1 = _timing.T1L / (RMT_DURATION_NS * DIVIDER); +} + +esp_err_t RmtDriver::init() { + rmt_config_t config = RMT_DEFAULT_CONFIG_TX(_pin, _channel); + config.rmt_mode = RMT_MODE_TX; + config.clk_div = DIVIDER; + config.mem_block_num = 1; + + return rmt_config(&config); +} + +esp_err_t RmtDriver::registerIsr(bool isFirstRegisteredChannel) { + auto err = rmt_driver_install(_channel, 0, ESP_INTR_FLAG_IRAM); + if (err != ESP_OK) { + return err; + } + + if (isFirstRegisteredChannel) { + rmt_register_tx_end_callback(txEndCallback, NULL); + } + + err = rmt_translator_init(_channel, translateSample); + if (err != ESP_OK) { + return err; + } + return rmt_translator_set_context(_channel, this); +} + +esp_err_t RmtDriver::unregisterIsr() { return rmt_driver_uninstall(_channel); } + +void IRAM_ATTR RmtDriver::txEndCallback(rmt_channel_t channel, void* arg) { + xSemaphoreGiveFromISR(SmartLed::ledForChannel(channel)->_finishedFlag, nullptr); +} + +void IRAM_ATTR RmtDriver::translateSample(const void* src, rmt_item32_t* dest, size_t src_size, + size_t wanted_rmt_items_num, size_t* out_consumed_src_bytes, size_t* out_used_rmt_items) { + RmtDriver* self; + ESP_ERROR_CHECK(rmt_translator_get_context(out_used_rmt_items, (void**)&self)); + + const auto& _bitToRmt = self->_bitToRmt; + const auto src_offset = self->_translatorSourceOffset; + + auto* src_components = (const uint8_t*)src; + size_t consumed_src_bytes = 0; + size_t used_rmt_items = 0; + + while (consumed_src_bytes < src_size && used_rmt_items + 7 < wanted_rmt_items_num) { + uint8_t val = *src_components; + + // each bit, from highest to lowest + for (uint8_t j = 0; j != 8; j++, val <<= 1) { + dest->val = _bitToRmt[val >> 7].val; + ++dest; + } + + used_rmt_items += 8; + ++src_components; + ++consumed_src_bytes; + + // skip alpha byte + if (((src_offset + consumed_src_bytes) % 4) == 3) { + ++src_components; + ++consumed_src_bytes; + + // TRST delay after last pixel in strip + if (consumed_src_bytes == src_size) { + (dest - 1)->duration1 = self->_timing.TRS / (detail::RMT_DURATION_NS * detail::DIVIDER); + } + } + } + + self->_translatorSourceOffset = src_offset + consumed_src_bytes; + *out_consumed_src_bytes = consumed_src_bytes; + *out_used_rmt_items = used_rmt_items; +} + +esp_err_t RmtDriver::transmit(const Rgb* buffer) { + static_assert(sizeof(Rgb) == 4); // The translator code above assumes RGB is 4 bytes + + _translatorSourceOffset = 0; + return rmt_write_sample(_channel, (const uint8_t*)buffer, _count * 4, false); +} +}; +#endif // !SMARTLEDS_NEW_RMT_DRIVER diff --git a/src/RmtDriver4.h b/src/RmtDriver4.h new file mode 100644 index 0000000..7a10f9a --- /dev/null +++ b/src/RmtDriver4.h @@ -0,0 +1,42 @@ +#pragma once + +#include "RmtDriver.h" + +#if !SMARTLEDS_NEW_RMT_DRIVER +#include "Color.h" +#include +#include +#include + +namespace detail { + +constexpr const int CHANNEL_COUNT = RMT_CHANNEL_MAX; + +class RmtDriver { +public: + RmtDriver(const LedType& timing, int count, int pin, int channel_num, SemaphoreHandle_t finishedFlag); + RmtDriver(const RmtDriver&) = delete; + + esp_err_t init(); + esp_err_t registerIsr(bool isFirstRegisteredChannel); + esp_err_t unregisterIsr(); + esp_err_t transmit(const Rgb* buffer); + +private: + static void IRAM_ATTR txEndCallback(rmt_channel_t channel, void* arg); + + static void IRAM_ATTR translateSample(const void* src, rmt_item32_t* dest, size_t src_size, + size_t wanted_rmt_items_num, size_t* out_consumed_src_bytes, size_t* out_used_rmt_items); + + const LedType& _timing; + int _count; + gpio_num_t _pin; + SemaphoreHandle_t _finishedFlag; + + rmt_channel_t _channel; + rmt_item32_t _bitToRmt[2]; + size_t _translatorSourceOffset; +}; + +}; +#endif // !SMARTLEDS_NEW_RMT_DRIVER diff --git a/src/RmtDriver5.cpp b/src/RmtDriver5.cpp new file mode 100644 index 0000000..9c04835 --- /dev/null +++ b/src/RmtDriver5.cpp @@ -0,0 +1,173 @@ +#include "RmtDriver5.h" + +#if SMARTLEDS_NEW_RMT_DRIVER +#include + +#include + +#include "SmartLeds.h" + +namespace detail { + +static constexpr const uint32_t RMT_RESOLUTION_HZ = 20 * 1000 * 1000; // 20 MHz +static constexpr const uint32_t RMT_NS_PER_TICK = 1000000000LU / RMT_RESOLUTION_HZ; + +static RmtEncoderWrapper* IRAM_ATTR encSelf(rmt_encoder_t* encoder) { + return (RmtEncoderWrapper*)(((intptr_t)encoder) - offsetof(RmtEncoderWrapper, base)); +} + +static size_t IRAM_ATTR encEncode(rmt_encoder_t* encoder, rmt_channel_handle_t tx_channel, const void* primary_data, + size_t data_size, rmt_encode_state_t* ret_state) { + auto* self = encSelf(encoder); + + // Delay after last pixel + if ((self->last_state & RMT_ENCODING_COMPLETE) && self->frame_idx == data_size) { + *ret_state = (rmt_encode_state_t)0; + return self->copy_encoder->encode( + self->copy_encoder, tx_channel, (const void*)&self->reset_code, sizeof(self->reset_code), ret_state); + } + + if (self->last_state & RMT_ENCODING_COMPLETE) { + Rgb* pixel = ((Rgb*)primary_data) + self->frame_idx; + self->buffer_len = sizeof(self->buffer); + for (size_t i = 0; i < sizeof(self->buffer); ++i) { + self->buffer[i] = pixel->getGrb(self->component_idx); + if (++self->component_idx == 3) { + self->component_idx = 0; + if (++self->frame_idx == data_size) { + self->buffer_len = i + 1; + break; + } + ++pixel; + } + } + } + + self->last_state = (rmt_encode_state_t)0; + auto encoded_symbols = self->bytes_encoder->encode( + self->bytes_encoder, tx_channel, (const void*)&self->buffer, self->buffer_len, &self->last_state); + if (self->last_state & RMT_ENCODING_MEM_FULL) { + *ret_state = RMT_ENCODING_MEM_FULL; + } else { + *ret_state = (rmt_encode_state_t)0; + } + + return encoded_symbols; +} + +static esp_err_t encReset(rmt_encoder_t* encoder) { + auto* self = encSelf(encoder); + rmt_encoder_reset(self->bytes_encoder); + rmt_encoder_reset(self->copy_encoder); + self->last_state = RMT_ENCODING_COMPLETE; + self->frame_idx = 0; + self->component_idx = 0; + return ESP_OK; +} + +static esp_err_t encDelete(rmt_encoder_t* encoder) { + auto* self = encSelf(encoder); + rmt_del_encoder(self->bytes_encoder); + rmt_del_encoder(self->copy_encoder); + return ESP_OK; +} + +RmtDriver::RmtDriver(const LedType& timing, int count, int pin, int channel_num, SemaphoreHandle_t finishedFlag) + : _timing(timing) + , _count(count) + , _pin(pin) + , _finishedFlag(finishedFlag) + , _channel(nullptr) + , _encoder {} {} + +esp_err_t RmtDriver::init() { + _encoder.base.encode = encEncode; + _encoder.base.reset = encReset; + _encoder.base.del = encDelete; + + _encoder.reset_code.duration0 = _timing.TRS / RMT_NS_PER_TICK; + + rmt_bytes_encoder_config_t bytes_cfg = { + .bit0 = { + .duration0 = uint32_t(_timing.T0H / RMT_NS_PER_TICK), + .level0 = 1, + .duration1 = uint32_t(_timing.T0L / RMT_NS_PER_TICK), + .level1 = 0, + }, + .bit1 = { + .duration0 = uint32_t(_timing.T1H / RMT_NS_PER_TICK), + .level0 = 1, + .duration1 = uint32_t(_timing.T1L / RMT_NS_PER_TICK), + .level1 = 0, + }, + .flags = { + .msb_first = 1, + }, + }; + + auto err = rmt_new_bytes_encoder(&bytes_cfg, &_encoder.bytes_encoder); + if (err != ESP_OK) { + return err; + } + + rmt_copy_encoder_config_t copy_cfg = {}; + err = rmt_new_copy_encoder(©_cfg, &_encoder.copy_encoder); + if (err != ESP_OK) { + return err; + } + + // The config must be in registerIsr, because rmt_new_tx_channel + // registers the ISR + return ESP_OK; +} + +esp_err_t RmtDriver::registerIsr(bool isFirstRegisteredChannel) { + rmt_tx_channel_config_t conf = { + .gpio_num = _pin, + .clk_src = RMT_CLK_SRC_APB, + .resolution_hz = RMT_RESOLUTION_HZ, + .mem_block_symbols = 64, + .trans_queue_depth = 1, + .flags = {}, + }; + + auto err = rmt_new_tx_channel(&conf, &_channel); + if (err != ESP_OK) { + return err; + } + + rmt_tx_event_callbacks_t callbacks_cfg = {}; + callbacks_cfg.on_trans_done = txDoneCallback; + + err = rmt_tx_register_event_callbacks(_channel, &callbacks_cfg, this); + if (err != ESP_OK) { + return err; + } + + return rmt_enable(_channel); +} + +esp_err_t RmtDriver::unregisterIsr() { + auto err = rmt_del_encoder(&_encoder.base); + if (err != ESP_OK) { + return err; + } + + return rmt_del_channel(_channel); +} + +bool IRAM_ATTR RmtDriver::txDoneCallback( + rmt_channel_handle_t tx_chan, const rmt_tx_done_event_data_t* edata, void* user_ctx) { + auto* self = (RmtDriver*)user_ctx; + auto taskWoken = pdTRUE; + xSemaphoreGiveFromISR(self->_finishedFlag, &taskWoken); + return taskWoken == pdTRUE; +} + +esp_err_t RmtDriver::transmit(const Rgb* buffer) { + rmt_encoder_reset(&_encoder.base); + rmt_transmit_config_t cfg = {}; + return rmt_transmit(_channel, &_encoder.base, buffer, _count, &cfg); +} +}; +#endif // !SMARTLEDS_NEW_RMT_DRIVER diff --git a/src/RmtDriver5.h b/src/RmtDriver5.h new file mode 100644 index 0000000..c6b08b6 --- /dev/null +++ b/src/RmtDriver5.h @@ -0,0 +1,65 @@ +#pragma once + +#include "RmtDriver.h" + +#if SMARTLEDS_NEW_RMT_DRIVER +#include +#include +#include +#include + +#include "Color.h" + +#if !defined(CONFIG_RMT_ISR_IRAM_SAFE) && !defined(SMARTLEDS_DISABLE_IRAM_WARNING) +#warning "Please enable CONFIG_RMT_ISR_IRAM_SAFE IDF option." \ + "without it, the IDF driver is not able to supply data fast enough." +#endif + +namespace detail { + +constexpr const int CHANNEL_COUNT = SOC_RMT_GROUPS * SOC_RMT_CHANNELS_PER_GROUP; + +class RmtDriver; + +// This is ridiculous +struct RmtEncoderWrapper { + struct rmt_encoder_t base; + struct rmt_encoder_t* bytes_encoder; + struct rmt_encoder_t* copy_encoder; + RmtDriver* driver; + rmt_symbol_word_t reset_code; + + uint8_t buffer[64 / 8]; // RMT peripherial has buffer for 64 bits + rmt_encode_state_t last_state; + size_t frame_idx; + uint8_t component_idx; + uint8_t buffer_len; +}; + +static_assert(std::is_standard_layout::value == true); + +class RmtDriver { +public: + RmtDriver(const LedType& timing, int count, int pin, int channel_num, SemaphoreHandle_t finishedFlag); + RmtDriver(const RmtDriver&) = delete; + + esp_err_t init(); + esp_err_t registerIsr(bool isFirstRegisteredChannel); + esp_err_t unregisterIsr(); + esp_err_t transmit(const Rgb* buffer); + +private: + static bool IRAM_ATTR txDoneCallback( + rmt_channel_handle_t tx_chan, const rmt_tx_done_event_data_t* edata, void* user_ctx); + + const LedType& _timing; + int _count; + int _pin; + SemaphoreHandle_t _finishedFlag; + + rmt_channel_handle_t _channel; + RmtEncoderWrapper _encoder; +}; + +}; +#endif // !SMARTLEDS_NEW_RMT_DRIVER diff --git a/src/SmartLeds.cpp b/src/SmartLeds.cpp index 5a22e93..20f2e39 100644 --- a/src/SmartLeds.cpp +++ b/src/SmartLeds.cpp @@ -2,55 +2,8 @@ IsrCore SmartLed::_interruptCore = CoreCurrent; -SmartLed*& IRAM_ATTR SmartLed::ledForChannel( int channel ) { - static SmartLed* table[8] = { nullptr }; - assert( channel < 8 ); - return table[ channel ]; -} - -void IRAM_ATTR SmartLed::txEndCallback(rmt_channel_t channel, void *arg) { - xSemaphoreGiveFromISR(ledForChannel(channel)->_finishedFlag, nullptr); -} - - -void IRAM_ATTR SmartLed::translateForRmt(const void *src, rmt_item32_t *dest, size_t src_size, - size_t wanted_rmt_items_num, size_t *out_consumed_src_bytes, size_t *out_used_rmt_items) { - SmartLed *self; - ESP_ERROR_CHECK(rmt_translator_get_context(out_used_rmt_items, (void**)&self)); - - const auto& _bitToRmt = self->_bitToRmt; - const auto src_offset = self->_translatorSourceOffset; - - auto *src_components = (const uint8_t *)src; - size_t consumed_src_bytes = 0; - size_t used_rmt_items = 0; - - while (consumed_src_bytes < src_size && used_rmt_items + 7 < wanted_rmt_items_num) { - uint8_t val = *src_components; - - // each bit, from highest to lowest - for ( uint8_t j = 0; j != 8; j++, val <<= 1 ) { - dest->val = _bitToRmt[ val >> 7 ].val; - ++dest; - } - - used_rmt_items += 8; - ++src_components; - ++consumed_src_bytes; - - // skip alpha byte - if(((src_offset + consumed_src_bytes) % 4) == 3) { - ++src_components; - ++consumed_src_bytes; - - // TRST delay after last pixel in strip - if(consumed_src_bytes == src_size) { - (dest-1)->duration1 = self->_timing.TRS / ( detail::RMT_DURATION_NS * detail::DIVIDER ); - } - } - } - - self->_translatorSourceOffset = src_offset + consumed_src_bytes; - *out_consumed_src_bytes = consumed_src_bytes; - *out_used_rmt_items = used_rmt_items; +SmartLed*& IRAM_ATTR SmartLed::ledForChannel(int channel) { + static SmartLed* table[detail::CHANNEL_COUNT] = { nullptr }; + assert(channel < detail::CHANNEL_COUNT); + return table[channel]; } diff --git a/src/SmartLeds.h b/src/SmartLeds.h index e654aeb..c1d6d03 100644 --- a/src/SmartLeds.h +++ b/src/SmartLeds.h @@ -28,122 +28,88 @@ * THE SOFTWARE. */ -#include #include #include +#include -#include -#include -#include -#include #include #include - -#include +#include +#include +#include +#include #include "Color.h" -namespace detail { - -struct TimingParams { - uint32_t T0H; - uint32_t T1H; - uint32_t T0L; - uint32_t T1L; - uint32_t TRS; -}; - -static const int DIVIDER = 4; // 8 still seems to work, but timings become marginal -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 = DoubleBuffer, IsrCore isrCore = CoreCurrent) - : _timing(type), - _channel((rmt_channel_t)channel), - _count(count), - _firstBuffer(new Rgb[count]), - _secondBuffer( doubleBuffer ? new Rgb[ count ] : nullptr ), - _finishedFlag(xSemaphoreCreateBinary()) - { - assert( channel >= 0 && channel < RMT_CHANNEL_MAX ); - assert( ledForChannel( channel ) == nullptr ); - - xSemaphoreGive( _finishedFlag ); - - _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 ); - - rmt_config_t config = RMT_DEFAULT_CONFIG_TX((gpio_num_t)pin, _channel); - config.rmt_mode = RMT_MODE_TX; - config.clk_div = detail::DIVIDER; - config.mem_block_num = 1; - config.tx_config.idle_output_en = true; - - ESP_ERROR_CHECK(rmt_config(&config)); - - const auto anyAliveCached = anyAlive(); - if (!anyAliveCached && isrCore != CoreCurrent) { + 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 (!anyAlive() && isrCore != CoreCurrent) { _interruptCore = isrCore; - ESP_ERROR_CHECK(esp_ipc_call_blocking(isrCore, registerInterrupt, (void*)((intptr_t)_channel))); + ESP_ERROR_CHECK(esp_ipc_call_blocking(isrCore, registerInterrupt, (void*)this)); } else { - registerInterrupt((void*)((intptr_t)_channel)); - } - - if(!anyAliveCached) { - rmt_register_tx_end_callback(txEndCallback, NULL); + registerInterrupt((void*)this); } - ESP_ERROR_CHECK(rmt_translator_init(_channel, translateForRmt)); - ESP_ERROR_CHECK(rmt_translator_set_context(_channel, this)); - - ledForChannel( channel ) = this; + ledForChannel(channel) = this; } ~SmartLed() { - ledForChannel( _channel ) = nullptr; - if ( !anyAlive() && _interruptCore != CoreCurrent ) { - ESP_ERROR_CHECK(esp_ipc_call_blocking(_interruptCore, unregisterInterrupt, (void*)((intptr_t)_channel))); + ledForChannel(_channel) = nullptr; + if (!anyAlive() && _interruptCore != CoreCurrent) { + ESP_ERROR_CHECK(esp_ipc_call_blocking(_interruptCore, unregisterInterrupt, (void*)this)); } else { - unregisterInterrupt((void*)((intptr_t)_channel)); + 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]; } esp_err_t show() { esp_err_t err = startTransmission(); @@ -151,50 +117,46 @@ class SmartLed { 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; } - 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 IsrCore _interruptCore; - static void registerInterrupt(void *channelVoid) { - ESP_ERROR_CHECK(rmt_driver_install((rmt_channel_t)((intptr_t)channelVoid), 0, ESP_INTR_FLAG_IRAM)); + static void registerInterrupt(void* selfVoid) { + auto* self = (SmartLed*)selfVoid; + ESP_ERROR_CHECK(self->_driver.registerIsr(!anyAlive())); } - static void unregisterInterrupt(void *channelVoid) { - ESP_ERROR_CHECK(rmt_driver_uninstall((rmt_channel_t)((intptr_t)channelVoid))); + static void unregisterInterrupt(void* selfVoid) { + auto* self = (SmartLed*)selfVoid; + ESP_ERROR_CHECK(self->_driver.unregisterIsr()); } - static SmartLed*& IRAM_ATTR ledForChannel( int channel ); + static SmartLed*& IRAM_ATTR ledForChannel(int channel); static bool anyAlive() { - for ( int i = 0; i != 8; i++ ) - if ( ledForChannel( i ) != nullptr ) return true; + for (int i = 0; i != detail::CHANNEL_COUNT; i++) + if (ledForChannel(i) != nullptr) + return true; return false; } - static void IRAM_ATTR txEndCallback(rmt_channel_t channel, void *arg); - - static void IRAM_ATTR translateForRmt(const void *src, rmt_item32_t *dest, size_t src_size, - size_t wanted_rmt_items_num, size_t *out_consumed_src_bytes, size_t *out_used_rmt_items); - void swapBuffers() { if (_secondBuffer) _firstBuffer.swap(_secondBuffer); @@ -202,29 +164,23 @@ class SmartLed { esp_err_t startTransmission() { // Invalid use of the library, you must wait() fir previous frame to get processed first - if( xSemaphoreTake( _finishedFlag, 0 ) != pdTRUE ) + if (xSemaphoreTake(_finishedFlag, 0) != pdTRUE) abort(); - _translatorSourceOffset = 0; - - auto err = rmt_write_sample(_channel, (const uint8_t *)_firstBuffer.get(), _count * 4, false); - if(err != ESP_OK) { + auto err = _driver.transmit(_firstBuffer.get()); + if (err != ESP_OK) { return err; } return ESP_OK; } - const LedType& _timing; - rmt_channel_t _channel; - rmt_item32_t _bitToRmt[ 2 ]; + SemaphoreHandle_t _finishedFlag; + detail::RmtDriver _driver; + int _channel; int _count; std::unique_ptr _firstBuffer; std::unique_ptr _secondBuffer; - - size_t _translatorSourceOffset; - - SemaphoreHandle_t _finishedFlag; }; #ifdef CONFIG_IDF_TARGET_ESP32 @@ -238,19 +194,21 @@ class SmartLed { 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; } @@ -260,14 +218,13 @@ class Apa102 { 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) + : _count(count) + , _firstBuffer(new ApaRgb[count]) + , _secondBuffer(doubleBuffer ? new ApaRgb[count] : nullptr) + , _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; @@ -276,33 +233,29 @@ class Apa102 { 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 = 1000000; 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, 1 ); - assert( ret == ESP_OK ); + auto ret = spi_bus_initialize(_SMARTLEDS_SPI_HOST, &buscfg, 1); + assert(ret == ESP_OK); - ret = spi_bus_add_device( _SMARTLEDS_SPI_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(); @@ -311,93 +264,95 @@ class Apa102 { } 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 _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; @@ -406,33 +361,29 @@ class LDP8806 { 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( _SMARTLEDS_SPI_HOST, &buscfg, 1 ); - assert( ret == ESP_OK ); + auto ret = spi_bus_initialize(_SMARTLEDS_SPI_HOST, &buscfg, 1); + assert(ret == ESP_OK); - ret = spi_bus_add_device( _SMARTLEDS_SPI_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(); @@ -441,49 +392,50 @@ class LDP8806 { } 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 _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]; }; diff --git a/test/catch.hpp b/test/catch.hpp index cc3f97c..a1b6ed9 100644 --- a/test/catch.hpp +++ b/test/catch.hpp @@ -14684,4 +14684,3 @@ using Catch::Detail::Approx; // end catch_reenable_warnings.h // end catch.hpp #endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED - diff --git a/test/colorConversion.cpp b/test/colorConversion.cpp index 1fb8d08..c390135 100644 --- a/test/colorConversion.cpp +++ b/test/colorConversion.cpp @@ -1,17 +1,17 @@ #include #include #include -#include #include +#include // Oracle functions, source: https://gist.github.com/yoggy/8999625 // -#define min_f(a, b, c) (fminf(a, fminf(b, c))) -#define max_f(a, b, c) (fmaxf(a, fmaxf(b, c))) +#define min_f(a, b, c) (fminf(a, fminf(b, c))) +#define max_f(a, b, c) (fmaxf(a, fmaxf(b, c))) -void rgb2hsv(const unsigned char &src_r, const unsigned char &src_g, const unsigned char &src_b, unsigned char &dst_h, unsigned char &dst_s, unsigned char &dst_v) -{ +void rgb2hsv(const unsigned char& src_r, const unsigned char& src_g, const unsigned char& src_b, unsigned char& dst_h, + unsigned char& dst_s, unsigned char& dst_v) { float r = src_r / 255.0f; float g = src_g / 255.0f; float b = src_b / 255.0f; @@ -26,53 +26,62 @@ void rgb2hsv(const unsigned char &src_r, const unsigned char &src_g, const unsig if (max == 0.0f) { s = 0; h = 0; - } - else if (max - min == 0.0f) { + } else if (max - min == 0.0f) { s = 0; h = 0; - } - else { + } else { s = (max - min) / max; if (max == r) { h = 60 * ((g - b) / (max - min)) + 0; - } - else if (max == g) { + } else if (max == g) { h = 60 * ((b - r) / (max - min)) + 120; - } - else { + } else { h = 60 * ((r - g) / (max - min)) + 240; } } - if (h < 0) h += 360.0f; + if (h < 0) + h += 360.0f; - dst_h = (unsigned char)(h / 360.0 * 255); // dst_h : 0-180 + dst_h = (unsigned char)(h / 360.0 * 255); // dst_h : 0-180 dst_s = (unsigned char)(s * 255); // dst_s : 0-255 dst_v = (unsigned char)(v * 255); // dst_v : 0-255 } -void hsv2rgb(const unsigned char &src_h, const unsigned char &src_s, const unsigned char &src_v, unsigned char &dst_r, unsigned char &dst_g, unsigned char &dst_b) -{ +void hsv2rgb(const unsigned char& src_h, const unsigned char& src_s, const unsigned char& src_v, unsigned char& dst_r, + unsigned char& dst_g, unsigned char& dst_b) { float h = src_h / 255.0 * 360; // 0-360 float s = src_s / 255.0f; // 0.0-1.0 float v = src_v / 255.0f; // 0.0-1.0 float r, g, b; // 0.0-1.0 - int hi = (int)(h / 60.0f) % 6; - float f = (h / 60.0f) - hi; - float p = v * (1.0f - s); - float q = v * (1.0f - s * f); - float t = v * (1.0f - s * (1.0f - f)); - - switch(hi) { - case 0: r = v, g = t, b = p; break; - case 1: r = q, g = v, b = p; break; - case 2: r = p, g = v, b = t; break; - case 3: r = p, g = q, b = v; break; - case 4: r = t, g = p, b = v; break; - case 5: r = v, g = p, b = q; break; + int hi = (int)(h / 60.0f) % 6; + float f = (h / 60.0f) - hi; + float p = v * (1.0f - s); + float q = v * (1.0f - s * f); + float t = v * (1.0f - s * (1.0f - f)); + + switch (hi) { + case 0: + r = v, g = t, b = p; + break; + case 1: + r = q, g = v, b = p; + break; + case 2: + r = p, g = v, b = t; + break; + case 3: + r = p, g = q, b = v; + break; + case 4: + r = t, g = p, b = v; + break; + case 5: + r = v, g = p, b = q; + break; } dst_r = (unsigned char)(r * 255); // dst_r : 0-255 @@ -82,52 +91,49 @@ void hsv2rgb(const unsigned char &src_h, const unsigned char &src_s, const unsig // End of oracle -std::vector< Rgb > rgbSamples() { - std::vector< Rgb > ret; +std::vector rgbSamples() { + std::vector ret; static const int step = 10; - for ( int r = 0; r <= 255; r += step ) - for ( int g = 0; g <= 255; g += step ) - for ( int b = 0; b <= 255; b += step ) - ret.emplace_back( r, g, b ); + for (int r = 0; r <= 255; r += step) + for (int g = 0; g <= 255; g += step) + for (int b = 0; b <= 255; b += step) + ret.emplace_back(r, g, b); return ret; } -std::vector< Hsv > hsvSamples() { - std::vector< Hsv > ret; +std::vector hsvSamples() { + std::vector ret; static const int step = 10; - for ( int h = 0; h <= 255; h += step ) - for ( int s = 0; s <= 255; s += step ) - for ( int v = 0; v <= 255; v += 255 ) - ret.emplace_back( h, s, v ); + for (int h = 0; h <= 255; h += step) + for (int s = 0; s <= 255; s += step) + for (int v = 0; v <= 255; v += 255) + ret.emplace_back(h, s, v); return ret; } -int dist( int x, int y ) { - return std::abs( x - y ); -} - +int dist(int x, int y) { return std::abs(x - y); } TEST_CASE("RGB <-> HSV", "[rgb<->hsv]") { - for ( Rgb color : rgbSamples() ) { - Hsv hsv{ color }; - Rgb rgb{ hsv }; + for (Rgb color : rgbSamples()) { + Hsv hsv { color }; + Rgb rgb { hsv }; uint8_t h, s, v; - rgb2hsv( color.r, color.g, color.b, h, s, v ); + rgb2hsv(color.r, color.g, color.b, h, s, v); uint8_t r, g, b; - hsv2rgb( hsv.h, hsv.s, hsv.v, r, g, b ); + hsv2rgb(hsv.h, hsv.s, hsv.v, r, g, b); - CAPTURE( int( color.r ), int( color.g ), int( color.b ) ); - CAPTURE( int( rgb.r ), int( rgb.g ), int( rgb.b ) ); - CAPTURE( int( hsv.h ), int( hsv.s ), int( hsv.v ) ); - CAPTURE( int( h ), int( s ), int( v ) ); - CAPTURE( int( r ), int( g ), int( b ) ); + CAPTURE(int(color.r), int(color.g), int(color.b)); + CAPTURE(int(rgb.r), int(rgb.g), int(rgb.b)); + CAPTURE(int(hsv.h), int(hsv.s), int(hsv.v)); + CAPTURE(int(h), int(s), int(v)); + CAPTURE(int(r), int(g), int(b)); - REQUIRE( dist( rgb.r, r ) <= 1 ); - REQUIRE( dist( rgb.g, g ) <= 1 ); - REQUIRE( dist( rgb.b, b ) <= 1 ); + REQUIRE(dist(rgb.r, r) <= 1); + REQUIRE(dist(rgb.g, g) <= 1); + REQUIRE(dist(rgb.b, b) <= 1); - REQUIRE( dist(hsv.h, h ) <= 1 ); - REQUIRE( dist(hsv.s, s ) <= 1 ); - REQUIRE( dist(hsv.v, v ) <= 1 ); + REQUIRE(dist(hsv.h, h) <= 1); + REQUIRE(dist(hsv.s, s) <= 1); + REQUIRE(dist(hsv.v, v) <= 1); } -} \ No newline at end of file +} diff --git a/test/main.cpp b/test/main.cpp index fd9339d..b3143fb 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -1,2 +1,2 @@ #define CATCH_CONFIG_MAIN -#include \ No newline at end of file +#include From 20426e09bdb5887c43390c9de78eb7fceea1614c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Bo=C4=8Dek?= Date: Tue, 11 Apr 2023 18:08:14 +0200 Subject: [PATCH 17/34] Revert "Refactor to use the new driver on IDF 5.0" This reverts commit 7e92147351c969b16513b3bf39db2f65b6c8811a. It got to master branch by mistake, see #45 for progress. --- .clang-format | 127 ------------- src/Color.cpp | 148 +++++++-------- src/Color.h | 84 ++++----- src/RmtDriver.h | 30 --- src/RmtDriver4.cpp | 111 ----------- src/RmtDriver4.h | 42 ----- src/RmtDriver5.cpp | 173 ----------------- src/RmtDriver5.h | 65 ------- src/SmartLeds.cpp | 55 +++++- src/SmartLeds.h | 394 ++++++++++++++++++++++----------------- test/catch.hpp | 1 + test/colorConversion.cpp | 132 +++++++------ test/main.cpp | 2 +- 13 files changed, 435 insertions(+), 929 deletions(-) delete mode 100644 .clang-format delete mode 100644 src/RmtDriver.h delete mode 100644 src/RmtDriver4.cpp delete mode 100644 src/RmtDriver4.h delete mode 100644 src/RmtDriver5.cpp delete mode 100644 src/RmtDriver5.h diff --git a/.clang-format b/.clang-format deleted file mode 100644 index bbb6cfb..0000000 --- a/.clang-format +++ /dev/null @@ -1,127 +0,0 @@ ---- -Language: Cpp -# BasedOnStyle: WebKit -AccessModifierOffset: -4 -AlignAfterOpenBracket: DontAlign -AlignConsecutiveMacros: false -AlignConsecutiveAssignments: false -AlignConsecutiveDeclarations: false -AlignEscapedNewlines: Right -AlignOperands: false -AlignTrailingComments: false -AllowAllArgumentsOnNextLine: true -AllowAllConstructorInitializersOnNextLine: true -AllowAllParametersOfDeclarationOnNextLine: true -AllowShortBlocksOnASingleLine: false -AllowShortCaseLabelsOnASingleLine: false -AllowShortFunctionsOnASingleLine: All -AllowShortLambdasOnASingleLine: All -AllowShortIfStatementsOnASingleLine: Never -AllowShortLoopsOnASingleLine: false -AlwaysBreakAfterDefinitionReturnType: None -AlwaysBreakAfterReturnType: None -AlwaysBreakBeforeMultilineStrings: false -AlwaysBreakTemplateDeclarations: MultiLine -BinPackArguments: true -BinPackParameters: true -BraceWrapping: - AfterCaseLabel: false - AfterClass: true - AfterControlStatement: false - AfterEnum: false - AfterFunction: false - AfterNamespace: false - AfterObjCDeclaration: false - AfterStruct: false - AfterUnion: false - AfterExternBlock: false - BeforeCatch: false - BeforeElse: false - IndentBraces: false - SplitEmptyFunction: true - SplitEmptyRecord: true - SplitEmptyNamespace: true -BreakBeforeBinaryOperators: All -BreakBeforeBraces: Attach -BreakBeforeInheritanceComma: false -BreakInheritanceList: BeforeColon -BreakBeforeTernaryOperators: true -BreakConstructorInitializersBeforeComma: false -BreakConstructorInitializers: BeforeComma -BreakAfterJavaFieldAnnotations: false -BreakStringLiterals: true -ColumnLimit: 0 -CommentPragmas: '^ IWYU pragma:' -CompactNamespaces: false -ConstructorInitializerAllOnOneLineOrOnePerLine: false -ConstructorInitializerIndentWidth: 4 -ContinuationIndentWidth: 4 -Cpp11BracedListStyle: false -DerivePointerAlignment: false -DisableFormat: false -ExperimentalAutoDetectBinPacking: false -FixNamespaceComments: false -ForEachMacros: - - foreach - - Q_FOREACH - - BOOST_FOREACH -IncludeBlocks: Preserve -IncludeCategories: - - Regex: '^"(llvm|llvm-c|clang|clang-c)/' - Priority: 2 - - Regex: '^(<|"(gtest|gmock|isl|json)/)' - Priority: 3 - - Regex: '.*' - Priority: 1 -IncludeIsMainRegex: '(Test)?$' -IndentCaseLabels: false -IndentPPDirectives: None -IndentWidth: 4 -IndentWrappedFunctionNames: false -JavaScriptQuotes: Leave -JavaScriptWrapImports: true -KeepEmptyLinesAtTheStartOfBlocks: true -MacroBlockBegin: '' -MacroBlockEnd: '' -MaxEmptyLinesToKeep: 1 -NamespaceIndentation: None -ObjCBinPackProtocolList: Auto -ObjCBlockIndentWidth: 4 -ObjCSpaceAfterProperty: true -ObjCSpaceBeforeProtocolList: true -PenaltyBreakAssignment: 2 -PenaltyBreakBeforeFirstCallParameter: 19 -PenaltyBreakComment: 300 -PenaltyBreakFirstLessLess: 120 -PenaltyBreakString: 1000 -PenaltyBreakTemplateDeclaration: 10 -PenaltyExcessCharacter: 1000000 -PenaltyReturnTypeOnItsOwnLine: 60 -PointerAlignment: Left -ReflowComments: false -SortIncludes: true -SortUsingDeclarations: true -SpaceAfterCStyleCast: false -SpaceAfterLogicalNot: false -SpaceAfterTemplateKeyword: true -SpaceBeforeAssignmentOperators: true -SpaceBeforeCpp11BracedList: true -SpaceBeforeCtorInitializerColon: true -SpaceBeforeInheritanceColon: true -SpaceBeforeParens: ControlStatements -SpaceBeforeRangeBasedForLoopColon: true -SpaceInEmptyParentheses: false -SpacesBeforeTrailingComments: 1 -SpacesInAngles: false -SpacesInContainerLiterals: true -SpacesInCStyleCastParentheses: false -SpacesInParentheses: false -SpacesInSquareBrackets: false -Standard: Cpp11 -StatementMacros: - - Q_UNUSED - - QT_REQUIRE_VERSION -TabWidth: 8 -UseTab: Never -ColumnLimit: 120 -... diff --git a/src/Color.cpp b/src/Color.cpp index 9ab4fcc..e1d1eda 100644 --- a/src/Color.cpp +++ b/src/Color.cpp @@ -1,27 +1,27 @@ #include "Color.h" #include -#include #include +#include namespace { // Int -> fixed point -int up(int x) { return x * 255; } +int up( int x ) { return x * 255; } } // namespace -int iRgbSqrt(int num) { +int iRgbSqrt( int num ) { // https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Binary_numeral_system_.28base_2.29 - assert("sqrt input should be non-negative" && num >= 0); - assert("sqrt input should no exceed 16 bits" && num <= 0xFFFF); + assert( "sqrt input should be non-negative" && num >= 0 ); + assert( "sqrt input should no exceed 16 bits" && num <= 0xFFFF ); int res = 0; int bit = 1 << 16; - while (bit > num) + while ( bit > num ) bit >>= 2; - while (bit != 0) { - if (num >= res + bit) { + while ( bit != 0 ) { + if ( num >= res + bit ) { num -= res + bit; - res = (res >> 1) + bit; + res = ( res >> 1 ) + bit; } else res >>= 1; bit >>= 2; @@ -29,133 +29,117 @@ int iRgbSqrt(int num) { return res; } -Rgb::Rgb(const Hsv& y) { +Rgb::Rgb(const Hsv& y ) { // https://stackoverflow.com/questions/24152553/hsv-to-rgb-and-back-without-floating-point-math-in-python // greyscale - if (y.s == 0) { + if( y.s == 0 ) { r = g = b = y.v; return; } const int region = y.h / 43; - const int remainder = (y.h - (region * 43)) * 6; - - const int p = (y.v * (255 - y.s)) >> 8; - const int q = (y.v * (255 - ((y.s * remainder) >> 8))) >> 8; - const int t = (y.v * (255 - ((y.s * (255 - remainder)) >> 8))) >> 8; - - switch (region) { - case 0: - r = y.v; - g = t; - b = p; - break; - case 1: - r = q; - g = y.v; - b = p; - break; - case 2: - r = p; - g = y.v; - b = t; - break; - case 3: - r = p; - g = q; - b = y.v; - break; - case 4: - r = t; - g = p; - b = y.v; - break; - case 5: - r = y.v; - g = p; - b = q; - break; - default: - __builtin_trap(); + const int remainder = ( y.h - ( region * 43 ) ) * 6; + + const int p = ( y.v * ( 255 - y.s ) ) >> 8; + const int q = ( y.v * ( 255 - ( ( y.s * remainder ) >> 8 ) ) ) >> 8; + const int t = ( y.v * ( 255 - ( ( y.s * (255 -remainder ) ) >> 8 ) ) ) >> 8; + + switch( region ) { + case 0: r = y.v; g = t; b = p; break; + case 1: r = q; g = y.v; b = p; break; + case 2: r = p; g = y.v; b = t; break; + case 3: r = p; g = q; b = y.v; break; + case 4: r = t; g = p; b = y.v; break; + case 5: r = y.v; g = p; b = q; break; + default: __builtin_trap(); } a = y.a; } -Rgb& Rgb::operator=(const Hsv& hsv) { - Rgb r { hsv }; - swap(r); +Rgb& Rgb::operator=( const Hsv& hsv ) { + Rgb r{ hsv }; + swap( r ); return *this; } -Rgb Rgb::operator+(const Rgb& in) const { +Rgb Rgb::operator+( const Rgb& in ) const { auto copy = *this; copy += in; return copy; } -Rgb& Rgb::operator+=(const Rgb& in) { +Rgb& Rgb::operator+=( const Rgb& in ) { unsigned int red = r + in.r; - r = (red < 255) ? red : 255; + r = ( red < 255 ) ? red : 255; unsigned int green = g + in.g; - g = (green < 255) ? green : 255; + g = ( green < 255 ) ? green : 255; unsigned int blue = b + in.b; - b = (blue < 255) ? blue : 255; + b = ( blue < 255 ) ? blue : 255; return *this; } -Rgb Rgb::operator-(const Rgb& in) const { +Rgb Rgb::operator-( const Rgb& in ) const { auto copy = *this; copy -= in; return copy; } -Rgb& Rgb::operator-=(const Rgb& in) { - r = (in.r > r) ? 0 : r - in.r; - g = (in.g > g) ? 0 : g - in.g; - b = (in.b > b) ? 0 : b - in.b; +Rgb& Rgb::operator-=( const Rgb& in ) { + r = ( in.r > r ) ? 0 : r - in.r; + g = ( in.g > g ) ? 0 : g - in.g; + b = ( in.b > b ) ? 0 : b - in.b; return *this; } -Rgb& Rgb::blend(const Rgb& in) { - unsigned int inAlpha = in.a * (255 - a); +Rgb& Rgb::blend( const Rgb& in ) { + unsigned int inAlpha = in.a * ( 255 - a ); unsigned int alpha = a + inAlpha; - r = iRgbSqrt(((r * r * a) + (in.r * in.r * inAlpha)) / alpha); - g = iRgbSqrt(((g * g * a) + (in.g * in.g * inAlpha)) / alpha); - b = iRgbSqrt(((b * b * a) + (in.b * in.b * inAlpha)) / alpha); + r = iRgbSqrt( ( ( r * r * a ) + ( in.r * in.r * inAlpha ) ) / alpha ); + g = iRgbSqrt( ( ( g * g * a ) + ( in.g * in.g * inAlpha ) ) / alpha ); + b = iRgbSqrt( ( ( b * b * a ) + ( in.b * in.b * inAlpha ) ) / alpha ); a = alpha; return *this; } -Hsv::Hsv(const Rgb& r) { - int min = std::min(r.r, std::min(r.g, r.b)); - int max = std::max(r.r, std::max(r.g, r.b)); +uint8_t Rgb::getGrb( int idx ) const { + switch ( idx ) { + case 0: return g; + case 1: return r; + case 2: return b; + } + __builtin_unreachable(); +} + +Hsv::Hsv( const Rgb& r ) { + int min = std::min( r.r, std::min( r.g, r.b ) ); + int max = std::max( r.r, std::max( r.g, r.b ) ); int chroma = max - min; v = max; - if (chroma == 0) { + if ( chroma == 0 ) { h = s = 0; return; } - s = up(chroma) / max; + s = up( chroma ) / max; int hh; - if (max == r.r) - hh = (up(int(r.g) - int(r.b))) / chroma / 6; - else if (max == r.g) - hh = 255 / 3 + (up(int(r.b) - int(r.r))) / chroma / 6; + if ( max == r.r ) + hh = ( up( int( r.g ) - int( r.b ) ) ) / chroma / 6; + else if ( max == r.g ) + hh = 255 / 3 + ( up( int( r.b ) - int( r.r ) ) ) / chroma / 6; else - hh = 2 * 255 / 3 + (up(int(r.r) - int(r.g))) / chroma / 6; + hh = 2 * 255 / 3 + ( up( int( r.r ) - int( r.g ) ) ) / chroma / 6; - if (hh < 0) + if ( hh < 0 ) hh += 255; h = hh; a = r.a; } -Hsv& Hsv::operator=(const Rgb& rgb) { - Hsv h { rgb }; - swap(h); +Hsv& Hsv::operator=( const Rgb& rgb ) { + Hsv h{ rgb }; + swap( h ); return *this; } diff --git a/src/Color.h b/src/Color.h index 2baac12..d6aaeaa 100644 --- a/src/Color.h +++ b/src/Color.h @@ -1,64 +1,51 @@ #pragma once -#include "esp_attr.h" #include +#include "esp_attr.h" union Hsv; union Rgb { - struct __attribute__((packed)) { + struct __attribute__ ((packed)) { uint8_t g, r, b, a; }; uint32_t value; - Rgb(uint8_t r = 0, uint8_t g = 0, uint8_t b = 0, uint8_t a = 255) - : g(g) - , r(r) - , b(b) - , a(a) {} - Rgb(const Hsv& c); + Rgb( uint8_t r = 0, uint8_t g = 0, uint8_t b = 0, uint8_t a = 255 ) : g( g ), r( r ), b( b ), a( a ) {} + Rgb( const Hsv& c ); Rgb(const Rgb&) = default; - Rgb& operator=(const Rgb& rgb) { - swap(rgb); - return *this; - } - Rgb& operator=(const Hsv& hsv); - Rgb operator+(const Rgb& in) const; - Rgb& operator+=(const Rgb& in); + Rgb& operator=( const Rgb& rgb ) { swap( rgb ); return *this; } + Rgb& operator=( const Hsv& hsv ); + Rgb operator+( const Rgb& in ) const; + Rgb& operator+=( const Rgb& in ); Rgb operator-(const Rgb& in) const; - Rgb& operator-=(const Rgb& in); - bool operator==(const Rgb& in) const { return in.value == value; } - Rgb& blend(const Rgb& in); - void swap(const Rgb& o) { value = o.value; } + Rgb &operator-=(const Rgb& in); + bool operator==(const Rgb& in ) const { return in.value == value; } + Rgb& blend( const Rgb& in ); + void swap( const Rgb& o ) { value = o.value; } void linearize() { r = channelGamma(r); g = channelGamma(g); b = channelGamma(b); } - inline uint8_t IRAM_ATTR getGrb(int idx) { - switch (idx) { - case 0: - return g; - case 1: - return r; - case 2: - return b; - } - __builtin_unreachable(); - } + uint8_t getGrb( int idx ) const; - void stretchChannels(uint8_t maxR, uint8_t maxG, uint8_t maxB) { - r = stretch(r, maxR); - g = stretch(g, maxG); - b = stretch(b, maxB); + void stretchChannels( uint8_t maxR, uint8_t maxG, uint8_t maxB ) { + r = stretch( r, maxR ); + g = stretch( g, maxG ); + b = stretch( b, maxB ); } - void stretchChannelsEvenly(uint8_t max) { stretchChannels(max, max, max); } + void stretchChannelsEvenly( uint8_t max ) { + stretchChannels( max, max, max ); + } private: - uint8_t stretch(int value, uint8_t max) { return (value * max) >> 8; } + uint8_t stretch( int value, uint8_t max ) { + return ( value * max ) >> 8; + } - uint8_t channelGamma(int channel) { + uint8_t channelGamma( int channel ) { /* The optimal gamma correction is x^2.8. However, this is expensive to * compute. Therefore, we use x^3 for gamma correction. Also, we add a * bias as the WS2812 LEDs do not turn on for values less than 4. */ @@ -66,27 +53,20 @@ union Rgb { return channel; channel = channel * channel * channel * 251; channel >>= 24; - return static_cast(4 + channel); + return static_cast< uint8_t >( 4 + channel ); } }; union Hsv { - struct __attribute__((packed)) { + struct __attribute__ ((packed)) { uint8_t h, s, v, a; }; uint32_t value; - Hsv(uint8_t h, uint8_t s = 0, uint8_t v = 0, uint8_t a = 255) - : h(h) - , s(s) - , v(v) - , a(a) {} - Hsv(const Rgb& r); - Hsv& operator=(const Hsv& h) { - swap(h); - return *this; - } - Hsv& operator=(const Rgb& rgb); - bool operator==(const Hsv& in) const { return in.value == value; } - void swap(const Hsv& o) { value = o.value; } + Hsv( uint8_t h, uint8_t s = 0, uint8_t v = 0, uint8_t a = 255 ) : h( h ), s( s ), v( v ), a( a ) {} + Hsv( const Rgb& r ); + Hsv& operator=( const Hsv& h ) { swap( h ); return *this; } + Hsv& operator=( const Rgb& rgb ); + bool operator==( const Hsv& in ) const { return in.value == value; } + void swap( const Hsv& o ) { value = o.value; } }; diff --git a/src/RmtDriver.h b/src/RmtDriver.h deleted file mode 100644 index 69275a9..0000000 --- a/src/RmtDriver.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include -#include - -#if (defined(ESP_IDF_VERSION) && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)) -#define SMARTLEDS_NEW_RMT_DRIVER 1 -#else -#define SMARTLEDS_NEW_RMT_DRIVER 0 -#endif - -namespace detail { - -struct TimingParams { - uint32_t T0H; - uint32_t T1H; - uint32_t T0L; - uint32_t T1L; - uint32_t TRS; -}; - -using LedType = TimingParams; - -} // namespace detail - -#if SMARTLEDS_NEW_RMT_DRIVER -#include "RmtDriver5.h" -#else -#include "RmtDriver4.h" -#endif diff --git a/src/RmtDriver4.cpp b/src/RmtDriver4.cpp deleted file mode 100644 index 41e0565..0000000 --- a/src/RmtDriver4.cpp +++ /dev/null @@ -1,111 +0,0 @@ -#include "RmtDriver4.h" - -#if !SMARTLEDS_NEW_RMT_DRIVER -#include "SmartLeds.h" - -namespace detail { - -// 8 still seems to work, but timings become marginal -static const int DIVIDER = 4; -// minimum time of a single RMT duration based on clock ns -static const double RMT_DURATION_NS = 12.5; - -RmtDriver::RmtDriver(const LedType& timing, int count, int pin, int channel_num, SemaphoreHandle_t finishedFlag) - : _timing(timing) - , _count(count) - , _pin((gpio_num_t)pin) - , _finishedFlag(finishedFlag) - , _channel((rmt_channel_t)channel_num) { - _bitToRmt[0].level0 = 1; - _bitToRmt[0].level1 = 0; - _bitToRmt[0].duration0 = _timing.T0H / (RMT_DURATION_NS * DIVIDER); - _bitToRmt[0].duration1 = _timing.T0L / (RMT_DURATION_NS * DIVIDER); - - _bitToRmt[1].level0 = 1; - _bitToRmt[1].level1 = 0; - _bitToRmt[1].duration0 = _timing.T1H / (RMT_DURATION_NS * DIVIDER); - _bitToRmt[1].duration1 = _timing.T1L / (RMT_DURATION_NS * DIVIDER); -} - -esp_err_t RmtDriver::init() { - rmt_config_t config = RMT_DEFAULT_CONFIG_TX(_pin, _channel); - config.rmt_mode = RMT_MODE_TX; - config.clk_div = DIVIDER; - config.mem_block_num = 1; - - return rmt_config(&config); -} - -esp_err_t RmtDriver::registerIsr(bool isFirstRegisteredChannel) { - auto err = rmt_driver_install(_channel, 0, ESP_INTR_FLAG_IRAM); - if (err != ESP_OK) { - return err; - } - - if (isFirstRegisteredChannel) { - rmt_register_tx_end_callback(txEndCallback, NULL); - } - - err = rmt_translator_init(_channel, translateSample); - if (err != ESP_OK) { - return err; - } - return rmt_translator_set_context(_channel, this); -} - -esp_err_t RmtDriver::unregisterIsr() { return rmt_driver_uninstall(_channel); } - -void IRAM_ATTR RmtDriver::txEndCallback(rmt_channel_t channel, void* arg) { - xSemaphoreGiveFromISR(SmartLed::ledForChannel(channel)->_finishedFlag, nullptr); -} - -void IRAM_ATTR RmtDriver::translateSample(const void* src, rmt_item32_t* dest, size_t src_size, - size_t wanted_rmt_items_num, size_t* out_consumed_src_bytes, size_t* out_used_rmt_items) { - RmtDriver* self; - ESP_ERROR_CHECK(rmt_translator_get_context(out_used_rmt_items, (void**)&self)); - - const auto& _bitToRmt = self->_bitToRmt; - const auto src_offset = self->_translatorSourceOffset; - - auto* src_components = (const uint8_t*)src; - size_t consumed_src_bytes = 0; - size_t used_rmt_items = 0; - - while (consumed_src_bytes < src_size && used_rmt_items + 7 < wanted_rmt_items_num) { - uint8_t val = *src_components; - - // each bit, from highest to lowest - for (uint8_t j = 0; j != 8; j++, val <<= 1) { - dest->val = _bitToRmt[val >> 7].val; - ++dest; - } - - used_rmt_items += 8; - ++src_components; - ++consumed_src_bytes; - - // skip alpha byte - if (((src_offset + consumed_src_bytes) % 4) == 3) { - ++src_components; - ++consumed_src_bytes; - - // TRST delay after last pixel in strip - if (consumed_src_bytes == src_size) { - (dest - 1)->duration1 = self->_timing.TRS / (detail::RMT_DURATION_NS * detail::DIVIDER); - } - } - } - - self->_translatorSourceOffset = src_offset + consumed_src_bytes; - *out_consumed_src_bytes = consumed_src_bytes; - *out_used_rmt_items = used_rmt_items; -} - -esp_err_t RmtDriver::transmit(const Rgb* buffer) { - static_assert(sizeof(Rgb) == 4); // The translator code above assumes RGB is 4 bytes - - _translatorSourceOffset = 0; - return rmt_write_sample(_channel, (const uint8_t*)buffer, _count * 4, false); -} -}; -#endif // !SMARTLEDS_NEW_RMT_DRIVER diff --git a/src/RmtDriver4.h b/src/RmtDriver4.h deleted file mode 100644 index 7a10f9a..0000000 --- a/src/RmtDriver4.h +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once - -#include "RmtDriver.h" - -#if !SMARTLEDS_NEW_RMT_DRIVER -#include "Color.h" -#include -#include -#include - -namespace detail { - -constexpr const int CHANNEL_COUNT = RMT_CHANNEL_MAX; - -class RmtDriver { -public: - RmtDriver(const LedType& timing, int count, int pin, int channel_num, SemaphoreHandle_t finishedFlag); - RmtDriver(const RmtDriver&) = delete; - - esp_err_t init(); - esp_err_t registerIsr(bool isFirstRegisteredChannel); - esp_err_t unregisterIsr(); - esp_err_t transmit(const Rgb* buffer); - -private: - static void IRAM_ATTR txEndCallback(rmt_channel_t channel, void* arg); - - static void IRAM_ATTR translateSample(const void* src, rmt_item32_t* dest, size_t src_size, - size_t wanted_rmt_items_num, size_t* out_consumed_src_bytes, size_t* out_used_rmt_items); - - const LedType& _timing; - int _count; - gpio_num_t _pin; - SemaphoreHandle_t _finishedFlag; - - rmt_channel_t _channel; - rmt_item32_t _bitToRmt[2]; - size_t _translatorSourceOffset; -}; - -}; -#endif // !SMARTLEDS_NEW_RMT_DRIVER diff --git a/src/RmtDriver5.cpp b/src/RmtDriver5.cpp deleted file mode 100644 index 9c04835..0000000 --- a/src/RmtDriver5.cpp +++ /dev/null @@ -1,173 +0,0 @@ -#include "RmtDriver5.h" - -#if SMARTLEDS_NEW_RMT_DRIVER -#include - -#include - -#include "SmartLeds.h" - -namespace detail { - -static constexpr const uint32_t RMT_RESOLUTION_HZ = 20 * 1000 * 1000; // 20 MHz -static constexpr const uint32_t RMT_NS_PER_TICK = 1000000000LU / RMT_RESOLUTION_HZ; - -static RmtEncoderWrapper* IRAM_ATTR encSelf(rmt_encoder_t* encoder) { - return (RmtEncoderWrapper*)(((intptr_t)encoder) - offsetof(RmtEncoderWrapper, base)); -} - -static size_t IRAM_ATTR encEncode(rmt_encoder_t* encoder, rmt_channel_handle_t tx_channel, const void* primary_data, - size_t data_size, rmt_encode_state_t* ret_state) { - auto* self = encSelf(encoder); - - // Delay after last pixel - if ((self->last_state & RMT_ENCODING_COMPLETE) && self->frame_idx == data_size) { - *ret_state = (rmt_encode_state_t)0; - return self->copy_encoder->encode( - self->copy_encoder, tx_channel, (const void*)&self->reset_code, sizeof(self->reset_code), ret_state); - } - - if (self->last_state & RMT_ENCODING_COMPLETE) { - Rgb* pixel = ((Rgb*)primary_data) + self->frame_idx; - self->buffer_len = sizeof(self->buffer); - for (size_t i = 0; i < sizeof(self->buffer); ++i) { - self->buffer[i] = pixel->getGrb(self->component_idx); - if (++self->component_idx == 3) { - self->component_idx = 0; - if (++self->frame_idx == data_size) { - self->buffer_len = i + 1; - break; - } - ++pixel; - } - } - } - - self->last_state = (rmt_encode_state_t)0; - auto encoded_symbols = self->bytes_encoder->encode( - self->bytes_encoder, tx_channel, (const void*)&self->buffer, self->buffer_len, &self->last_state); - if (self->last_state & RMT_ENCODING_MEM_FULL) { - *ret_state = RMT_ENCODING_MEM_FULL; - } else { - *ret_state = (rmt_encode_state_t)0; - } - - return encoded_symbols; -} - -static esp_err_t encReset(rmt_encoder_t* encoder) { - auto* self = encSelf(encoder); - rmt_encoder_reset(self->bytes_encoder); - rmt_encoder_reset(self->copy_encoder); - self->last_state = RMT_ENCODING_COMPLETE; - self->frame_idx = 0; - self->component_idx = 0; - return ESP_OK; -} - -static esp_err_t encDelete(rmt_encoder_t* encoder) { - auto* self = encSelf(encoder); - rmt_del_encoder(self->bytes_encoder); - rmt_del_encoder(self->copy_encoder); - return ESP_OK; -} - -RmtDriver::RmtDriver(const LedType& timing, int count, int pin, int channel_num, SemaphoreHandle_t finishedFlag) - : _timing(timing) - , _count(count) - , _pin(pin) - , _finishedFlag(finishedFlag) - , _channel(nullptr) - , _encoder {} {} - -esp_err_t RmtDriver::init() { - _encoder.base.encode = encEncode; - _encoder.base.reset = encReset; - _encoder.base.del = encDelete; - - _encoder.reset_code.duration0 = _timing.TRS / RMT_NS_PER_TICK; - - rmt_bytes_encoder_config_t bytes_cfg = { - .bit0 = { - .duration0 = uint32_t(_timing.T0H / RMT_NS_PER_TICK), - .level0 = 1, - .duration1 = uint32_t(_timing.T0L / RMT_NS_PER_TICK), - .level1 = 0, - }, - .bit1 = { - .duration0 = uint32_t(_timing.T1H / RMT_NS_PER_TICK), - .level0 = 1, - .duration1 = uint32_t(_timing.T1L / RMT_NS_PER_TICK), - .level1 = 0, - }, - .flags = { - .msb_first = 1, - }, - }; - - auto err = rmt_new_bytes_encoder(&bytes_cfg, &_encoder.bytes_encoder); - if (err != ESP_OK) { - return err; - } - - rmt_copy_encoder_config_t copy_cfg = {}; - err = rmt_new_copy_encoder(©_cfg, &_encoder.copy_encoder); - if (err != ESP_OK) { - return err; - } - - // The config must be in registerIsr, because rmt_new_tx_channel - // registers the ISR - return ESP_OK; -} - -esp_err_t RmtDriver::registerIsr(bool isFirstRegisteredChannel) { - rmt_tx_channel_config_t conf = { - .gpio_num = _pin, - .clk_src = RMT_CLK_SRC_APB, - .resolution_hz = RMT_RESOLUTION_HZ, - .mem_block_symbols = 64, - .trans_queue_depth = 1, - .flags = {}, - }; - - auto err = rmt_new_tx_channel(&conf, &_channel); - if (err != ESP_OK) { - return err; - } - - rmt_tx_event_callbacks_t callbacks_cfg = {}; - callbacks_cfg.on_trans_done = txDoneCallback; - - err = rmt_tx_register_event_callbacks(_channel, &callbacks_cfg, this); - if (err != ESP_OK) { - return err; - } - - return rmt_enable(_channel); -} - -esp_err_t RmtDriver::unregisterIsr() { - auto err = rmt_del_encoder(&_encoder.base); - if (err != ESP_OK) { - return err; - } - - return rmt_del_channel(_channel); -} - -bool IRAM_ATTR RmtDriver::txDoneCallback( - rmt_channel_handle_t tx_chan, const rmt_tx_done_event_data_t* edata, void* user_ctx) { - auto* self = (RmtDriver*)user_ctx; - auto taskWoken = pdTRUE; - xSemaphoreGiveFromISR(self->_finishedFlag, &taskWoken); - return taskWoken == pdTRUE; -} - -esp_err_t RmtDriver::transmit(const Rgb* buffer) { - rmt_encoder_reset(&_encoder.base); - rmt_transmit_config_t cfg = {}; - return rmt_transmit(_channel, &_encoder.base, buffer, _count, &cfg); -} -}; -#endif // !SMARTLEDS_NEW_RMT_DRIVER diff --git a/src/RmtDriver5.h b/src/RmtDriver5.h deleted file mode 100644 index c6b08b6..0000000 --- a/src/RmtDriver5.h +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once - -#include "RmtDriver.h" - -#if SMARTLEDS_NEW_RMT_DRIVER -#include -#include -#include -#include - -#include "Color.h" - -#if !defined(CONFIG_RMT_ISR_IRAM_SAFE) && !defined(SMARTLEDS_DISABLE_IRAM_WARNING) -#warning "Please enable CONFIG_RMT_ISR_IRAM_SAFE IDF option." \ - "without it, the IDF driver is not able to supply data fast enough." -#endif - -namespace detail { - -constexpr const int CHANNEL_COUNT = SOC_RMT_GROUPS * SOC_RMT_CHANNELS_PER_GROUP; - -class RmtDriver; - -// This is ridiculous -struct RmtEncoderWrapper { - struct rmt_encoder_t base; - struct rmt_encoder_t* bytes_encoder; - struct rmt_encoder_t* copy_encoder; - RmtDriver* driver; - rmt_symbol_word_t reset_code; - - uint8_t buffer[64 / 8]; // RMT peripherial has buffer for 64 bits - rmt_encode_state_t last_state; - size_t frame_idx; - uint8_t component_idx; - uint8_t buffer_len; -}; - -static_assert(std::is_standard_layout::value == true); - -class RmtDriver { -public: - RmtDriver(const LedType& timing, int count, int pin, int channel_num, SemaphoreHandle_t finishedFlag); - RmtDriver(const RmtDriver&) = delete; - - esp_err_t init(); - esp_err_t registerIsr(bool isFirstRegisteredChannel); - esp_err_t unregisterIsr(); - esp_err_t transmit(const Rgb* buffer); - -private: - static bool IRAM_ATTR txDoneCallback( - rmt_channel_handle_t tx_chan, const rmt_tx_done_event_data_t* edata, void* user_ctx); - - const LedType& _timing; - int _count; - int _pin; - SemaphoreHandle_t _finishedFlag; - - rmt_channel_handle_t _channel; - RmtEncoderWrapper _encoder; -}; - -}; -#endif // !SMARTLEDS_NEW_RMT_DRIVER diff --git a/src/SmartLeds.cpp b/src/SmartLeds.cpp index 20f2e39..5a22e93 100644 --- a/src/SmartLeds.cpp +++ b/src/SmartLeds.cpp @@ -2,8 +2,55 @@ IsrCore SmartLed::_interruptCore = CoreCurrent; -SmartLed*& IRAM_ATTR SmartLed::ledForChannel(int channel) { - static SmartLed* table[detail::CHANNEL_COUNT] = { nullptr }; - assert(channel < detail::CHANNEL_COUNT); - return table[channel]; +SmartLed*& IRAM_ATTR SmartLed::ledForChannel( int channel ) { + static SmartLed* table[8] = { nullptr }; + assert( channel < 8 ); + return table[ channel ]; +} + +void IRAM_ATTR SmartLed::txEndCallback(rmt_channel_t channel, void *arg) { + xSemaphoreGiveFromISR(ledForChannel(channel)->_finishedFlag, nullptr); +} + + +void IRAM_ATTR SmartLed::translateForRmt(const void *src, rmt_item32_t *dest, size_t src_size, + size_t wanted_rmt_items_num, size_t *out_consumed_src_bytes, size_t *out_used_rmt_items) { + SmartLed *self; + ESP_ERROR_CHECK(rmt_translator_get_context(out_used_rmt_items, (void**)&self)); + + const auto& _bitToRmt = self->_bitToRmt; + const auto src_offset = self->_translatorSourceOffset; + + auto *src_components = (const uint8_t *)src; + size_t consumed_src_bytes = 0; + size_t used_rmt_items = 0; + + while (consumed_src_bytes < src_size && used_rmt_items + 7 < wanted_rmt_items_num) { + uint8_t val = *src_components; + + // each bit, from highest to lowest + for ( uint8_t j = 0; j != 8; j++, val <<= 1 ) { + dest->val = _bitToRmt[ val >> 7 ].val; + ++dest; + } + + used_rmt_items += 8; + ++src_components; + ++consumed_src_bytes; + + // skip alpha byte + if(((src_offset + consumed_src_bytes) % 4) == 3) { + ++src_components; + ++consumed_src_bytes; + + // TRST delay after last pixel in strip + if(consumed_src_bytes == src_size) { + (dest-1)->duration1 = self->_timing.TRS / ( detail::RMT_DURATION_NS * detail::DIVIDER ); + } + } + } + + self->_translatorSourceOffset = src_offset + consumed_src_bytes; + *out_consumed_src_bytes = consumed_src_bytes; + *out_used_rmt_items = used_rmt_items; } diff --git a/src/SmartLeds.h b/src/SmartLeds.h index c1d6d03..e654aeb 100644 --- a/src/SmartLeds.h +++ b/src/SmartLeds.h @@ -28,88 +28,122 @@ * THE SOFTWARE. */ +#include #include #include -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include + +#include #include "Color.h" -#include "RmtDriver.h" +namespace detail { + +struct TimingParams { + uint32_t T0H; + uint32_t T1H; + uint32_t T0L; + uint32_t T1L; + uint32_t TRS; +}; + +static const int DIVIDER = 4; // 8 still seems to work, but timings become marginal +static const double RMT_DURATION_NS = 12.5; // minimum time of a single RMT duration based on clock ns + +} // namespace detail 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 }; +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 }; // 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 = 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 (!anyAlive() && isrCore != CoreCurrent) { + SmartLed(const LedType &type, int count, int pin, int channel = 0, BufferType doubleBuffer = DoubleBuffer, IsrCore isrCore = CoreCurrent) + : _timing(type), + _channel((rmt_channel_t)channel), + _count(count), + _firstBuffer(new Rgb[count]), + _secondBuffer( doubleBuffer ? new Rgb[ count ] : nullptr ), + _finishedFlag(xSemaphoreCreateBinary()) + { + assert( channel >= 0 && channel < RMT_CHANNEL_MAX ); + assert( ledForChannel( channel ) == nullptr ); + + xSemaphoreGive( _finishedFlag ); + + _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 ); + + rmt_config_t config = RMT_DEFAULT_CONFIG_TX((gpio_num_t)pin, _channel); + config.rmt_mode = RMT_MODE_TX; + config.clk_div = detail::DIVIDER; + config.mem_block_num = 1; + config.tx_config.idle_output_en = true; + + ESP_ERROR_CHECK(rmt_config(&config)); + + const auto anyAliveCached = anyAlive(); + if (!anyAliveCached && isrCore != CoreCurrent) { _interruptCore = isrCore; - ESP_ERROR_CHECK(esp_ipc_call_blocking(isrCore, registerInterrupt, (void*)this)); + ESP_ERROR_CHECK(esp_ipc_call_blocking(isrCore, registerInterrupt, (void*)((intptr_t)_channel))); } else { - registerInterrupt((void*)this); + registerInterrupt((void*)((intptr_t)_channel)); + } + + if(!anyAliveCached) { + rmt_register_tx_end_callback(txEndCallback, NULL); } - ledForChannel(channel) = this; + ESP_ERROR_CHECK(rmt_translator_init(_channel, translateForRmt)); + ESP_ERROR_CHECK(rmt_translator_set_context(_channel, this)); + + ledForChannel( channel ) = this; } ~SmartLed() { - ledForChannel(_channel) = nullptr; - if (!anyAlive() && _interruptCore != CoreCurrent) { - ESP_ERROR_CHECK(esp_ipc_call_blocking(_interruptCore, unregisterInterrupt, (void*)this)); + ledForChannel( _channel ) = nullptr; + if ( !anyAlive() && _interruptCore != CoreCurrent ) { + ESP_ERROR_CHECK(esp_ipc_call_blocking(_interruptCore, unregisterInterrupt, (void*)((intptr_t)_channel))); } else { - unregisterInterrupt((void*)this); + unregisterInterrupt((void*)((intptr_t)_channel)); } - 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 ]; + } esp_err_t show() { esp_err_t err = startTransmission(); @@ -117,46 +151,50 @@ class SmartLed { 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; + } - 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 IsrCore _interruptCore; - static void registerInterrupt(void* selfVoid) { - auto* self = (SmartLed*)selfVoid; - ESP_ERROR_CHECK(self->_driver.registerIsr(!anyAlive())); + static void registerInterrupt(void *channelVoid) { + ESP_ERROR_CHECK(rmt_driver_install((rmt_channel_t)((intptr_t)channelVoid), 0, ESP_INTR_FLAG_IRAM)); } - static void unregisterInterrupt(void* selfVoid) { - auto* self = (SmartLed*)selfVoid; - ESP_ERROR_CHECK(self->_driver.unregisterIsr()); + static void unregisterInterrupt(void *channelVoid) { + ESP_ERROR_CHECK(rmt_driver_uninstall((rmt_channel_t)((intptr_t)channelVoid))); } - static SmartLed*& IRAM_ATTR ledForChannel(int channel); + 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; + for ( int i = 0; i != 8; i++ ) + if ( ledForChannel( i ) != nullptr ) return true; return false; } + static void IRAM_ATTR txEndCallback(rmt_channel_t channel, void *arg); + + static void IRAM_ATTR translateForRmt(const void *src, rmt_item32_t *dest, size_t src_size, + size_t wanted_rmt_items_num, size_t *out_consumed_src_bytes, size_t *out_used_rmt_items); + void swapBuffers() { if (_secondBuffer) _firstBuffer.swap(_secondBuffer); @@ -164,23 +202,29 @@ class SmartLed { esp_err_t startTransmission() { // Invalid use of the library, you must wait() fir previous frame to get processed first - if (xSemaphoreTake(_finishedFlag, 0) != pdTRUE) + if( xSemaphoreTake( _finishedFlag, 0 ) != pdTRUE ) abort(); - auto err = _driver.transmit(_firstBuffer.get()); - if (err != ESP_OK) { + _translatorSourceOffset = 0; + + auto err = rmt_write_sample(_channel, (const uint8_t *)_firstBuffer.get(), _count * 4, false); + if(err != ESP_OK) { return err; } return ESP_OK; } - SemaphoreHandle_t _finishedFlag; - detail::RmtDriver _driver; - int _channel; + const LedType& _timing; + rmt_channel_t _channel; + rmt_item32_t _bitToRmt[ 2 ]; int _count; std::unique_ptr _firstBuffer; std::unique_ptr _secondBuffer; + + size_t _translatorSourceOffset; + + SemaphoreHandle_t _finishedFlag; }; #ifdef CONFIG_IDF_TARGET_ESP32 @@ -194,21 +238,19 @@ class SmartLed { 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; } @@ -218,13 +260,14 @@ class Apa102 { 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 ) + : _count( count ), + _firstBuffer( new ApaRgb[ count ] ), + _secondBuffer( doubleBuffer ? new ApaRgb[ count ] : nullptr ), + _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; @@ -233,29 +276,33 @@ class Apa102 { 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 = 1000000; 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, 1); - assert(ret == ESP_OK); + auto ret = spi_bus_initialize( _SMARTLEDS_SPI_HOST, &buscfg, 1 ); + assert( ret == ESP_OK ); - ret = spi_bus_add_device(_SMARTLEDS_SPI_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(); @@ -264,95 +311,93 @@ class Apa102 { } 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 _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; @@ -361,29 +406,33 @@ class LDP8806 { 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(_SMARTLEDS_SPI_HOST, &buscfg, 1); - assert(ret == ESP_OK); + auto ret = spi_bus_initialize( _SMARTLEDS_SPI_HOST, &buscfg, 1 ); + assert( ret == ESP_OK ); - ret = spi_bus_add_device(_SMARTLEDS_SPI_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(); @@ -392,50 +441,49 @@ class LDP8806 { } 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 _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 ]; }; diff --git a/test/catch.hpp b/test/catch.hpp index a1b6ed9..cc3f97c 100644 --- a/test/catch.hpp +++ b/test/catch.hpp @@ -14684,3 +14684,4 @@ using Catch::Detail::Approx; // end catch_reenable_warnings.h // end catch.hpp #endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED + diff --git a/test/colorConversion.cpp b/test/colorConversion.cpp index c390135..1fb8d08 100644 --- a/test/colorConversion.cpp +++ b/test/colorConversion.cpp @@ -1,17 +1,17 @@ #include #include #include -#include #include +#include // Oracle functions, source: https://gist.github.com/yoggy/8999625 // -#define min_f(a, b, c) (fminf(a, fminf(b, c))) -#define max_f(a, b, c) (fmaxf(a, fmaxf(b, c))) +#define min_f(a, b, c) (fminf(a, fminf(b, c))) +#define max_f(a, b, c) (fmaxf(a, fmaxf(b, c))) -void rgb2hsv(const unsigned char& src_r, const unsigned char& src_g, const unsigned char& src_b, unsigned char& dst_h, - unsigned char& dst_s, unsigned char& dst_v) { +void rgb2hsv(const unsigned char &src_r, const unsigned char &src_g, const unsigned char &src_b, unsigned char &dst_h, unsigned char &dst_s, unsigned char &dst_v) +{ float r = src_r / 255.0f; float g = src_g / 255.0f; float b = src_b / 255.0f; @@ -26,62 +26,53 @@ void rgb2hsv(const unsigned char& src_r, const unsigned char& src_g, const unsig if (max == 0.0f) { s = 0; h = 0; - } else if (max - min == 0.0f) { + } + else if (max - min == 0.0f) { s = 0; h = 0; - } else { + } + else { s = (max - min) / max; if (max == r) { h = 60 * ((g - b) / (max - min)) + 0; - } else if (max == g) { + } + else if (max == g) { h = 60 * ((b - r) / (max - min)) + 120; - } else { + } + else { h = 60 * ((r - g) / (max - min)) + 240; } } - if (h < 0) - h += 360.0f; + if (h < 0) h += 360.0f; - dst_h = (unsigned char)(h / 360.0 * 255); // dst_h : 0-180 + dst_h = (unsigned char)(h / 360.0 * 255); // dst_h : 0-180 dst_s = (unsigned char)(s * 255); // dst_s : 0-255 dst_v = (unsigned char)(v * 255); // dst_v : 0-255 } -void hsv2rgb(const unsigned char& src_h, const unsigned char& src_s, const unsigned char& src_v, unsigned char& dst_r, - unsigned char& dst_g, unsigned char& dst_b) { +void hsv2rgb(const unsigned char &src_h, const unsigned char &src_s, const unsigned char &src_v, unsigned char &dst_r, unsigned char &dst_g, unsigned char &dst_b) +{ float h = src_h / 255.0 * 360; // 0-360 float s = src_s / 255.0f; // 0.0-1.0 float v = src_v / 255.0f; // 0.0-1.0 float r, g, b; // 0.0-1.0 - int hi = (int)(h / 60.0f) % 6; - float f = (h / 60.0f) - hi; - float p = v * (1.0f - s); - float q = v * (1.0f - s * f); - float t = v * (1.0f - s * (1.0f - f)); - - switch (hi) { - case 0: - r = v, g = t, b = p; - break; - case 1: - r = q, g = v, b = p; - break; - case 2: - r = p, g = v, b = t; - break; - case 3: - r = p, g = q, b = v; - break; - case 4: - r = t, g = p, b = v; - break; - case 5: - r = v, g = p, b = q; - break; + int hi = (int)(h / 60.0f) % 6; + float f = (h / 60.0f) - hi; + float p = v * (1.0f - s); + float q = v * (1.0f - s * f); + float t = v * (1.0f - s * (1.0f - f)); + + switch(hi) { + case 0: r = v, g = t, b = p; break; + case 1: r = q, g = v, b = p; break; + case 2: r = p, g = v, b = t; break; + case 3: r = p, g = q, b = v; break; + case 4: r = t, g = p, b = v; break; + case 5: r = v, g = p, b = q; break; } dst_r = (unsigned char)(r * 255); // dst_r : 0-255 @@ -91,49 +82,52 @@ void hsv2rgb(const unsigned char& src_h, const unsigned char& src_s, const unsig // End of oracle -std::vector rgbSamples() { - std::vector ret; +std::vector< Rgb > rgbSamples() { + std::vector< Rgb > ret; static const int step = 10; - for (int r = 0; r <= 255; r += step) - for (int g = 0; g <= 255; g += step) - for (int b = 0; b <= 255; b += step) - ret.emplace_back(r, g, b); + for ( int r = 0; r <= 255; r += step ) + for ( int g = 0; g <= 255; g += step ) + for ( int b = 0; b <= 255; b += step ) + ret.emplace_back( r, g, b ); return ret; } -std::vector hsvSamples() { - std::vector ret; +std::vector< Hsv > hsvSamples() { + std::vector< Hsv > ret; static const int step = 10; - for (int h = 0; h <= 255; h += step) - for (int s = 0; s <= 255; s += step) - for (int v = 0; v <= 255; v += 255) - ret.emplace_back(h, s, v); + for ( int h = 0; h <= 255; h += step ) + for ( int s = 0; s <= 255; s += step ) + for ( int v = 0; v <= 255; v += 255 ) + ret.emplace_back( h, s, v ); return ret; } -int dist(int x, int y) { return std::abs(x - y); } +int dist( int x, int y ) { + return std::abs( x - y ); +} + TEST_CASE("RGB <-> HSV", "[rgb<->hsv]") { - for (Rgb color : rgbSamples()) { - Hsv hsv { color }; - Rgb rgb { hsv }; + for ( Rgb color : rgbSamples() ) { + Hsv hsv{ color }; + Rgb rgb{ hsv }; uint8_t h, s, v; - rgb2hsv(color.r, color.g, color.b, h, s, v); + rgb2hsv( color.r, color.g, color.b, h, s, v ); uint8_t r, g, b; - hsv2rgb(hsv.h, hsv.s, hsv.v, r, g, b); + hsv2rgb( hsv.h, hsv.s, hsv.v, r, g, b ); - CAPTURE(int(color.r), int(color.g), int(color.b)); - CAPTURE(int(rgb.r), int(rgb.g), int(rgb.b)); - CAPTURE(int(hsv.h), int(hsv.s), int(hsv.v)); - CAPTURE(int(h), int(s), int(v)); - CAPTURE(int(r), int(g), int(b)); + CAPTURE( int( color.r ), int( color.g ), int( color.b ) ); + CAPTURE( int( rgb.r ), int( rgb.g ), int( rgb.b ) ); + CAPTURE( int( hsv.h ), int( hsv.s ), int( hsv.v ) ); + CAPTURE( int( h ), int( s ), int( v ) ); + CAPTURE( int( r ), int( g ), int( b ) ); - REQUIRE(dist(rgb.r, r) <= 1); - REQUIRE(dist(rgb.g, g) <= 1); - REQUIRE(dist(rgb.b, b) <= 1); + REQUIRE( dist( rgb.r, r ) <= 1 ); + REQUIRE( dist( rgb.g, g ) <= 1 ); + REQUIRE( dist( rgb.b, b ) <= 1 ); - REQUIRE(dist(hsv.h, h) <= 1); - REQUIRE(dist(hsv.s, s) <= 1); - REQUIRE(dist(hsv.v, v) <= 1); + REQUIRE( dist(hsv.h, h ) <= 1 ); + REQUIRE( dist(hsv.s, s ) <= 1 ); + REQUIRE( dist(hsv.v, v ) <= 1 ); } -} +} \ No newline at end of file diff --git a/test/main.cpp b/test/main.cpp index b3143fb..fd9339d 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -1,2 +1,2 @@ #define CATCH_CONFIG_MAIN -#include +#include \ No newline at end of file From cf2dbb49303b789c57b21547c39b62809e9f124d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Bo=C4=8Dek?= Date: Sun, 9 Jul 2023 12:27:58 +0200 Subject: [PATCH 18/34] Refactor to use the new driver on IDF 5.0 (#45) --- .clang-format | 127 ++++++++ .github/workflows/tests.yml | 28 ++ CMakeLists.txt | 14 + README.md | 15 +- examples/basic/main.cpp | 62 ++++ examples/basicExample/basicExample.ino | 48 --- src/Color.cpp | 148 +++++---- src/Color.h | 84 ++++-- src/RmtDriver.h | 34 +++ src/RmtDriver4.cpp | 111 +++++++ src/RmtDriver4.h | 42 +++ src/RmtDriver5.cpp | 176 +++++++++++ src/RmtDriver5.h | 65 ++++ src/SmartLeds.cpp | 55 +--- src/SmartLeds.h | 400 +++++++++++-------------- test-inis/esp32-idf4-arduino.ini | 23 ++ test-inis/esp32-idf5-idf.ini | 22 ++ test-inis/esp32s3-idf4-arduino.ini | 23 ++ test-inis/esp32s3-idf5-idf.ini | 22 ++ test/catch.hpp | 1 - test/colorConversion.cpp | 132 ++++---- test/main.cpp | 2 +- 22 files changed, 1141 insertions(+), 493 deletions(-) create mode 100644 .clang-format create mode 100644 .github/workflows/tests.yml create mode 100644 CMakeLists.txt create mode 100644 examples/basic/main.cpp delete mode 100644 examples/basicExample/basicExample.ino create mode 100644 src/RmtDriver.h create mode 100644 src/RmtDriver4.cpp create mode 100644 src/RmtDriver4.h create mode 100644 src/RmtDriver5.cpp create mode 100644 src/RmtDriver5.h create mode 100644 test-inis/esp32-idf4-arduino.ini create mode 100644 test-inis/esp32-idf5-idf.ini create mode 100644 test-inis/esp32s3-idf4-arduino.ini create mode 100644 test-inis/esp32s3-idf5-idf.ini diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..bbb6cfb --- /dev/null +++ b/.clang-format @@ -0,0 +1,127 @@ +--- +Language: Cpp +# BasedOnStyle: WebKit +AccessModifierOffset: -4 +AlignAfterOpenBracket: DontAlign +AlignConsecutiveMacros: false +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Right +AlignOperands: false +AlignTrailingComments: false +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: false + AfterClass: true + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: All +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeComma +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 0 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: false +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: false +IndentPPDirectives: None +IndentWidth: 4 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 4 +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Left +ReflowComments: false +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseTab: Never +ColumnLimit: 120 +... diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..4e42927 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,28 @@ +name: PlatformIO CI + +on: [push] + +jobs: + build-test: + runs-on: ubuntu-22.04 + strategy: + matrix: + example: + - examples/basic + conf: + - esp32-idf4-arduino.ini + - esp32-idf5-idf.ini + - esp32s3-idf4-arduino.ini + - esp32s3-idf5-idf.ini + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + - name: Install dependencies + run: | + python3 -m pip install --upgrade pip + python3 -m pip install platformio + - name: Build examples + run: platformio ci --lib="." --project-conf="./test-inis/${{ matrix.conf }}" + env: + PLATFORMIO_CI_SRC: ${{ matrix.example }} diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..7caf7f5 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.0) + +set(SRCS + "src/Color.cpp" + "src/RmtDriver4.cpp" + "src/RmtDriver5.cpp" + "src/SmartLeds.cpp" +) + +idf_component_register( + SRCS ${SRCS} + INCLUDE_DIRS "./src" + REQUIRES driver +) diff --git a/README.md b/README.md index 397edf8..e2daf3a 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,16 @@ Simple & intuitive way to drive various smart LEDs on ESP32. +Requires ESP-IDF >=4.0 + ## Supported LEDs: -- WS2812 (RMT driver) +- WS2812 (RMT driver) - WS2812B (RMT driver) -- SK6812 (RMT driver) -- WS2813 (RMT driver) -- APA102 (SPI driver) -- LPD8806 (SPI driver) +- SK6812 (RMT driver) +- WS2813 (RMT driver) +- APA102 (SPI driver) +- LPD8806 (SPI driver) All the LEDs are driven by hardware peripherals in order to achieve high performance. @@ -28,4 +30,5 @@ performance. - clock at 10 MHz ## Available -[PlatformIO - library 1740 - SmartLeds](https://platformio.org/lib/show/1740/SmartLeds) \ No newline at end of file + +[PlatformIO - library 1740 - SmartLeds](https://platformio.org/lib/show/1740/SmartLeds) diff --git a/examples/basic/main.cpp b/examples/basic/main.cpp new file mode 100644 index 0000000..79e9d6a --- /dev/null +++ b/examples/basic/main.cpp @@ -0,0 +1,62 @@ +#ifdef LX16A_ARDUINO +#include +#else +#include +#include +static void delay(int ms) { vTaskDelay(pdMS_TO_TICKS(ms)); } +static uint32_t millis() { return xTaskGetTickCount(); } +#endif + +#include + +const int LED_COUNT = 15; +const int DATA_PIN = 22; +const int CHANNEL = 0; + +// SmartLed -> RMT driver (WS2812/WS2812B/SK6812/WS2813) +SmartLed leds(LED_WS2812B, LED_COUNT, DATA_PIN, CHANNEL, DoubleBuffer); + +const int CLK_PIN = 23; +// APA102 -> SPI driver +//Apa102 leds(LED_COUNT, CLK_PIN, DATA_PIN, DoubleBuffer); + +void setup() {} + +uint8_t hue; +void showGradient() { + hue++; + // Use HSV to create nice gradient + for (int i = 0; i != LED_COUNT; i++) + leds[i] = Hsv { static_cast(hue + 30 * i), 255, 255 }; + leds.show(); + // Show is asynchronous; if we need to wait for the end of transmission, + // we can use leds.wait(); however we use double buffered mode, so we + // can start drawing right after showing. +} + +void showRgb() { + leds[0] = Rgb { 255, 0, 0 }; + leds[1] = Rgb { 0, 255, 0 }; + leds[2] = Rgb { 0, 0, 255 }; + leds[3] = Rgb { 0, 0, 0 }; + leds[4] = Rgb { 255, 255, 255 }; + leds.show(); +} + +void loop() { + if (millis() % 10000 < 5000) + showGradient(); + else + showRgb(); + delay(50); +} + +#ifndef ARDUINO +extern "C" void app_main() { + setup(); + while (true) { + loop(); + vTaskDelay(0); + } +} +#endif diff --git a/examples/basicExample/basicExample.ino b/examples/basicExample/basicExample.ino deleted file mode 100644 index 9ccfbe2..0000000 --- a/examples/basicExample/basicExample.ino +++ /dev/null @@ -1,48 +0,0 @@ -#include -#include - -const int LED_COUNT = 15; -const int DATA_PIN = 22; -const int CHANNEL = 0; - -// SmartLed -> RMT driver (WS2812/WS2812B/SK6812/WS2813) -SmartLed leds( LED_WS2812, LED_COUNT, DATA_PIN, CHANNEL, DoubleBuffer ); - -const int CLK_PIN = 23; -// APA102 -> SPI driver -//Apa102 leds(LED_COUNT, CLK_PIN, DATA_PIN, DoubleBuffer); - -void setup() { - Serial.begin(9600); -} - -uint8_t hue; -void showGradient() { - hue++; - // Use HSV to create nice gradient - for ( int i = 0; i != LED_COUNT; i++ ) - leds[ i ] = Hsv{ static_cast< uint8_t >( hue + 30 * i ), 255, 255 }; - leds.show(); - // Show is asynchronous; if we need to wait for the end of transmission, - // we can use leds.wait(); however we use double buffered mode, so we - // can start drawing right after showing. -} - -void showRgb() { - leds[ 0 ] = Rgb{ 255, 0, 0 }; - leds[ 1 ] = Rgb{ 0, 255, 0 }; - leds[ 2 ] = Rgb{ 0, 0, 255 }; - leds[ 3 ] = Rgb{ 0, 0, 0 }; - leds[ 4 ] = Rgb{ 255, 255, 255 }; - leds.show(); -} - -void loop() { - Serial.println("New loop"); - - if ( millis() % 10000 < 5000 ) - showGradient(); - else - showRgb(); - delay( 50 ); -} diff --git a/src/Color.cpp b/src/Color.cpp index e1d1eda..9ab4fcc 100644 --- a/src/Color.cpp +++ b/src/Color.cpp @@ -1,27 +1,27 @@ #include "Color.h" #include -#include #include +#include namespace { // Int -> fixed point -int up( int x ) { return x * 255; } +int up(int x) { return x * 255; } } // namespace -int iRgbSqrt( int num ) { +int iRgbSqrt(int num) { // https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Binary_numeral_system_.28base_2.29 - assert( "sqrt input should be non-negative" && num >= 0 ); - assert( "sqrt input should no exceed 16 bits" && num <= 0xFFFF ); + assert("sqrt input should be non-negative" && num >= 0); + assert("sqrt input should no exceed 16 bits" && num <= 0xFFFF); int res = 0; int bit = 1 << 16; - while ( bit > num ) + while (bit > num) bit >>= 2; - while ( bit != 0 ) { - if ( num >= res + bit ) { + while (bit != 0) { + if (num >= res + bit) { num -= res + bit; - res = ( res >> 1 ) + bit; + res = (res >> 1) + bit; } else res >>= 1; bit >>= 2; @@ -29,117 +29,133 @@ int iRgbSqrt( int num ) { return res; } -Rgb::Rgb(const Hsv& y ) { +Rgb::Rgb(const Hsv& y) { // https://stackoverflow.com/questions/24152553/hsv-to-rgb-and-back-without-floating-point-math-in-python // greyscale - if( y.s == 0 ) { + if (y.s == 0) { r = g = b = y.v; return; } const int region = y.h / 43; - const int remainder = ( y.h - ( region * 43 ) ) * 6; - - const int p = ( y.v * ( 255 - y.s ) ) >> 8; - const int q = ( y.v * ( 255 - ( ( y.s * remainder ) >> 8 ) ) ) >> 8; - const int t = ( y.v * ( 255 - ( ( y.s * (255 -remainder ) ) >> 8 ) ) ) >> 8; - - switch( region ) { - case 0: r = y.v; g = t; b = p; break; - case 1: r = q; g = y.v; b = p; break; - case 2: r = p; g = y.v; b = t; break; - case 3: r = p; g = q; b = y.v; break; - case 4: r = t; g = p; b = y.v; break; - case 5: r = y.v; g = p; b = q; break; - default: __builtin_trap(); + const int remainder = (y.h - (region * 43)) * 6; + + const int p = (y.v * (255 - y.s)) >> 8; + const int q = (y.v * (255 - ((y.s * remainder) >> 8))) >> 8; + const int t = (y.v * (255 - ((y.s * (255 - remainder)) >> 8))) >> 8; + + switch (region) { + case 0: + r = y.v; + g = t; + b = p; + break; + case 1: + r = q; + g = y.v; + b = p; + break; + case 2: + r = p; + g = y.v; + b = t; + break; + case 3: + r = p; + g = q; + b = y.v; + break; + case 4: + r = t; + g = p; + b = y.v; + break; + case 5: + r = y.v; + g = p; + b = q; + break; + default: + __builtin_trap(); } a = y.a; } -Rgb& Rgb::operator=( const Hsv& hsv ) { - Rgb r{ hsv }; - swap( r ); +Rgb& Rgb::operator=(const Hsv& hsv) { + Rgb r { hsv }; + swap(r); return *this; } -Rgb Rgb::operator+( const Rgb& in ) const { +Rgb Rgb::operator+(const Rgb& in) const { auto copy = *this; copy += in; return copy; } -Rgb& Rgb::operator+=( const Rgb& in ) { +Rgb& Rgb::operator+=(const Rgb& in) { unsigned int red = r + in.r; - r = ( red < 255 ) ? red : 255; + r = (red < 255) ? red : 255; unsigned int green = g + in.g; - g = ( green < 255 ) ? green : 255; + g = (green < 255) ? green : 255; unsigned int blue = b + in.b; - b = ( blue < 255 ) ? blue : 255; + b = (blue < 255) ? blue : 255; return *this; } -Rgb Rgb::operator-( const Rgb& in ) const { +Rgb Rgb::operator-(const Rgb& in) const { auto copy = *this; copy -= in; return copy; } -Rgb& Rgb::operator-=( const Rgb& in ) { - r = ( in.r > r ) ? 0 : r - in.r; - g = ( in.g > g ) ? 0 : g - in.g; - b = ( in.b > b ) ? 0 : b - in.b; +Rgb& Rgb::operator-=(const Rgb& in) { + r = (in.r > r) ? 0 : r - in.r; + g = (in.g > g) ? 0 : g - in.g; + b = (in.b > b) ? 0 : b - in.b; return *this; } -Rgb& Rgb::blend( const Rgb& in ) { - unsigned int inAlpha = in.a * ( 255 - a ); +Rgb& Rgb::blend(const Rgb& in) { + unsigned int inAlpha = in.a * (255 - a); unsigned int alpha = a + inAlpha; - r = iRgbSqrt( ( ( r * r * a ) + ( in.r * in.r * inAlpha ) ) / alpha ); - g = iRgbSqrt( ( ( g * g * a ) + ( in.g * in.g * inAlpha ) ) / alpha ); - b = iRgbSqrt( ( ( b * b * a ) + ( in.b * in.b * inAlpha ) ) / alpha ); + r = iRgbSqrt(((r * r * a) + (in.r * in.r * inAlpha)) / alpha); + g = iRgbSqrt(((g * g * a) + (in.g * in.g * inAlpha)) / alpha); + b = iRgbSqrt(((b * b * a) + (in.b * in.b * inAlpha)) / alpha); a = alpha; return *this; } -uint8_t Rgb::getGrb( int idx ) const { - switch ( idx ) { - case 0: return g; - case 1: return r; - case 2: return b; - } - __builtin_unreachable(); -} - -Hsv::Hsv( const Rgb& r ) { - int min = std::min( r.r, std::min( r.g, r.b ) ); - int max = std::max( r.r, std::max( r.g, r.b ) ); +Hsv::Hsv(const Rgb& r) { + int min = std::min(r.r, std::min(r.g, r.b)); + int max = std::max(r.r, std::max(r.g, r.b)); int chroma = max - min; v = max; - if ( chroma == 0 ) { + if (chroma == 0) { h = s = 0; return; } - s = up( chroma ) / max; + s = up(chroma) / max; int hh; - if ( max == r.r ) - hh = ( up( int( r.g ) - int( r.b ) ) ) / chroma / 6; - else if ( max == r.g ) - hh = 255 / 3 + ( up( int( r.b ) - int( r.r ) ) ) / chroma / 6; + if (max == r.r) + hh = (up(int(r.g) - int(r.b))) / chroma / 6; + else if (max == r.g) + hh = 255 / 3 + (up(int(r.b) - int(r.r))) / chroma / 6; else - hh = 2 * 255 / 3 + ( up( int( r.r ) - int( r.g ) ) ) / chroma / 6; + hh = 2 * 255 / 3 + (up(int(r.r) - int(r.g))) / chroma / 6; - if ( hh < 0 ) + if (hh < 0) hh += 255; h = hh; a = r.a; } -Hsv& Hsv::operator=( const Rgb& rgb ) { - Hsv h{ rgb }; - swap( h ); +Hsv& Hsv::operator=(const Rgb& rgb) { + Hsv h { rgb }; + swap(h); return *this; } diff --git a/src/Color.h b/src/Color.h index d6aaeaa..2baac12 100644 --- a/src/Color.h +++ b/src/Color.h @@ -1,51 +1,64 @@ #pragma once -#include #include "esp_attr.h" +#include union Hsv; union Rgb { - struct __attribute__ ((packed)) { + struct __attribute__((packed)) { uint8_t g, r, b, a; }; uint32_t value; - Rgb( uint8_t r = 0, uint8_t g = 0, uint8_t b = 0, uint8_t a = 255 ) : g( g ), r( r ), b( b ), a( a ) {} - Rgb( const Hsv& c ); + Rgb(uint8_t r = 0, uint8_t g = 0, uint8_t b = 0, uint8_t a = 255) + : g(g) + , r(r) + , b(b) + , a(a) {} + Rgb(const Hsv& c); Rgb(const Rgb&) = default; - Rgb& operator=( const Rgb& rgb ) { swap( rgb ); return *this; } - Rgb& operator=( const Hsv& hsv ); - Rgb operator+( const Rgb& in ) const; - Rgb& operator+=( const Rgb& in ); + Rgb& operator=(const Rgb& rgb) { + swap(rgb); + return *this; + } + Rgb& operator=(const Hsv& hsv); + Rgb operator+(const Rgb& in) const; + Rgb& operator+=(const Rgb& in); Rgb operator-(const Rgb& in) const; - Rgb &operator-=(const Rgb& in); - bool operator==(const Rgb& in ) const { return in.value == value; } - Rgb& blend( const Rgb& in ); - void swap( const Rgb& o ) { value = o.value; } + Rgb& operator-=(const Rgb& in); + bool operator==(const Rgb& in) const { return in.value == value; } + Rgb& blend(const Rgb& in); + void swap(const Rgb& o) { value = o.value; } void linearize() { r = channelGamma(r); g = channelGamma(g); b = channelGamma(b); } - uint8_t getGrb( int idx ) const; - - void stretchChannels( uint8_t maxR, uint8_t maxG, uint8_t maxB ) { - r = stretch( r, maxR ); - g = stretch( g, maxG ); - b = stretch( b, maxB ); + inline uint8_t IRAM_ATTR getGrb(int idx) { + switch (idx) { + case 0: + return g; + case 1: + return r; + case 2: + return b; + } + __builtin_unreachable(); } - void stretchChannelsEvenly( uint8_t max ) { - stretchChannels( max, max, max ); + void stretchChannels(uint8_t maxR, uint8_t maxG, uint8_t maxB) { + r = stretch(r, maxR); + g = stretch(g, maxG); + b = stretch(b, maxB); } + void stretchChannelsEvenly(uint8_t max) { stretchChannels(max, max, max); } + private: - uint8_t stretch( int value, uint8_t max ) { - return ( value * max ) >> 8; - } + uint8_t stretch(int value, uint8_t max) { return (value * max) >> 8; } - uint8_t channelGamma( int channel ) { + uint8_t channelGamma(int channel) { /* The optimal gamma correction is x^2.8. However, this is expensive to * compute. Therefore, we use x^3 for gamma correction. Also, we add a * bias as the WS2812 LEDs do not turn on for values less than 4. */ @@ -53,20 +66,27 @@ union Rgb { return channel; channel = channel * channel * channel * 251; channel >>= 24; - return static_cast< uint8_t >( 4 + channel ); + return static_cast(4 + channel); } }; union Hsv { - struct __attribute__ ((packed)) { + struct __attribute__((packed)) { uint8_t h, s, v, a; }; uint32_t value; - Hsv( uint8_t h, uint8_t s = 0, uint8_t v = 0, uint8_t a = 255 ) : h( h ), s( s ), v( v ), a( a ) {} - Hsv( const Rgb& r ); - Hsv& operator=( const Hsv& h ) { swap( h ); return *this; } - Hsv& operator=( const Rgb& rgb ); - bool operator==( const Hsv& in ) const { return in.value == value; } - void swap( const Hsv& o ) { value = o.value; } + Hsv(uint8_t h, uint8_t s = 0, uint8_t v = 0, uint8_t a = 255) + : h(h) + , s(s) + , v(v) + , a(a) {} + Hsv(const Rgb& r); + Hsv& operator=(const Hsv& h) { + swap(h); + return *this; + } + Hsv& operator=(const Rgb& rgb); + bool operator==(const Hsv& in) const { return in.value == value; } + void swap(const Hsv& o) { value = o.value; } }; diff --git a/src/RmtDriver.h b/src/RmtDriver.h new file mode 100644 index 0000000..c71d909 --- /dev/null +++ b/src/RmtDriver.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +#if defined(ESP_IDF_VERSION) +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) +#define SMARTLEDS_NEW_RMT_DRIVER 1 +#else +#define SMARTLEDS_NEW_RMT_DRIVER 0 +#endif +#else +#define SMARTLEDS_NEW_RMT_DRIVER 0 +#endif + +namespace detail { + +struct TimingParams { + uint32_t T0H; + uint32_t T1H; + uint32_t T0L; + uint32_t T1L; + uint32_t TRS; +}; + +using LedType = TimingParams; + +} // namespace detail + +#if SMARTLEDS_NEW_RMT_DRIVER +#include "RmtDriver5.h" +#else +#include "RmtDriver4.h" +#endif diff --git a/src/RmtDriver4.cpp b/src/RmtDriver4.cpp new file mode 100644 index 0000000..41e0565 --- /dev/null +++ b/src/RmtDriver4.cpp @@ -0,0 +1,111 @@ +#include "RmtDriver4.h" + +#if !SMARTLEDS_NEW_RMT_DRIVER +#include "SmartLeds.h" + +namespace detail { + +// 8 still seems to work, but timings become marginal +static const int DIVIDER = 4; +// minimum time of a single RMT duration based on clock ns +static const double RMT_DURATION_NS = 12.5; + +RmtDriver::RmtDriver(const LedType& timing, int count, int pin, int channel_num, SemaphoreHandle_t finishedFlag) + : _timing(timing) + , _count(count) + , _pin((gpio_num_t)pin) + , _finishedFlag(finishedFlag) + , _channel((rmt_channel_t)channel_num) { + _bitToRmt[0].level0 = 1; + _bitToRmt[0].level1 = 0; + _bitToRmt[0].duration0 = _timing.T0H / (RMT_DURATION_NS * DIVIDER); + _bitToRmt[0].duration1 = _timing.T0L / (RMT_DURATION_NS * DIVIDER); + + _bitToRmt[1].level0 = 1; + _bitToRmt[1].level1 = 0; + _bitToRmt[1].duration0 = _timing.T1H / (RMT_DURATION_NS * DIVIDER); + _bitToRmt[1].duration1 = _timing.T1L / (RMT_DURATION_NS * DIVIDER); +} + +esp_err_t RmtDriver::init() { + rmt_config_t config = RMT_DEFAULT_CONFIG_TX(_pin, _channel); + config.rmt_mode = RMT_MODE_TX; + config.clk_div = DIVIDER; + config.mem_block_num = 1; + + return rmt_config(&config); +} + +esp_err_t RmtDriver::registerIsr(bool isFirstRegisteredChannel) { + auto err = rmt_driver_install(_channel, 0, ESP_INTR_FLAG_IRAM); + if (err != ESP_OK) { + return err; + } + + if (isFirstRegisteredChannel) { + rmt_register_tx_end_callback(txEndCallback, NULL); + } + + err = rmt_translator_init(_channel, translateSample); + if (err != ESP_OK) { + return err; + } + return rmt_translator_set_context(_channel, this); +} + +esp_err_t RmtDriver::unregisterIsr() { return rmt_driver_uninstall(_channel); } + +void IRAM_ATTR RmtDriver::txEndCallback(rmt_channel_t channel, void* arg) { + xSemaphoreGiveFromISR(SmartLed::ledForChannel(channel)->_finishedFlag, nullptr); +} + +void IRAM_ATTR RmtDriver::translateSample(const void* src, rmt_item32_t* dest, size_t src_size, + size_t wanted_rmt_items_num, size_t* out_consumed_src_bytes, size_t* out_used_rmt_items) { + RmtDriver* self; + ESP_ERROR_CHECK(rmt_translator_get_context(out_used_rmt_items, (void**)&self)); + + const auto& _bitToRmt = self->_bitToRmt; + const auto src_offset = self->_translatorSourceOffset; + + auto* src_components = (const uint8_t*)src; + size_t consumed_src_bytes = 0; + size_t used_rmt_items = 0; + + while (consumed_src_bytes < src_size && used_rmt_items + 7 < wanted_rmt_items_num) { + uint8_t val = *src_components; + + // each bit, from highest to lowest + for (uint8_t j = 0; j != 8; j++, val <<= 1) { + dest->val = _bitToRmt[val >> 7].val; + ++dest; + } + + used_rmt_items += 8; + ++src_components; + ++consumed_src_bytes; + + // skip alpha byte + if (((src_offset + consumed_src_bytes) % 4) == 3) { + ++src_components; + ++consumed_src_bytes; + + // TRST delay after last pixel in strip + if (consumed_src_bytes == src_size) { + (dest - 1)->duration1 = self->_timing.TRS / (detail::RMT_DURATION_NS * detail::DIVIDER); + } + } + } + + self->_translatorSourceOffset = src_offset + consumed_src_bytes; + *out_consumed_src_bytes = consumed_src_bytes; + *out_used_rmt_items = used_rmt_items; +} + +esp_err_t RmtDriver::transmit(const Rgb* buffer) { + static_assert(sizeof(Rgb) == 4); // The translator code above assumes RGB is 4 bytes + + _translatorSourceOffset = 0; + return rmt_write_sample(_channel, (const uint8_t*)buffer, _count * 4, false); +} +}; +#endif // !SMARTLEDS_NEW_RMT_DRIVER diff --git a/src/RmtDriver4.h b/src/RmtDriver4.h new file mode 100644 index 0000000..7a10f9a --- /dev/null +++ b/src/RmtDriver4.h @@ -0,0 +1,42 @@ +#pragma once + +#include "RmtDriver.h" + +#if !SMARTLEDS_NEW_RMT_DRIVER +#include "Color.h" +#include +#include +#include + +namespace detail { + +constexpr const int CHANNEL_COUNT = RMT_CHANNEL_MAX; + +class RmtDriver { +public: + RmtDriver(const LedType& timing, int count, int pin, int channel_num, SemaphoreHandle_t finishedFlag); + RmtDriver(const RmtDriver&) = delete; + + esp_err_t init(); + esp_err_t registerIsr(bool isFirstRegisteredChannel); + esp_err_t unregisterIsr(); + esp_err_t transmit(const Rgb* buffer); + +private: + static void IRAM_ATTR txEndCallback(rmt_channel_t channel, void* arg); + + static void IRAM_ATTR translateSample(const void* src, rmt_item32_t* dest, size_t src_size, + size_t wanted_rmt_items_num, size_t* out_consumed_src_bytes, size_t* out_used_rmt_items); + + const LedType& _timing; + int _count; + gpio_num_t _pin; + SemaphoreHandle_t _finishedFlag; + + rmt_channel_t _channel; + rmt_item32_t _bitToRmt[2]; + size_t _translatorSourceOffset; +}; + +}; +#endif // !SMARTLEDS_NEW_RMT_DRIVER diff --git a/src/RmtDriver5.cpp b/src/RmtDriver5.cpp new file mode 100644 index 0000000..93e7204 --- /dev/null +++ b/src/RmtDriver5.cpp @@ -0,0 +1,176 @@ +#include "RmtDriver5.h" + +#if SMARTLEDS_NEW_RMT_DRIVER +#include + +#include "SmartLeds.h" + +namespace detail { + +static constexpr const uint32_t RMT_RESOLUTION_HZ = 20 * 1000 * 1000; // 20 MHz +static constexpr const uint32_t RMT_NS_PER_TICK = 1000000000LLU / RMT_RESOLUTION_HZ; + +static RmtEncoderWrapper* IRAM_ATTR encSelf(rmt_encoder_t* encoder) { + return (RmtEncoderWrapper*)(((intptr_t)encoder) - offsetof(RmtEncoderWrapper, base)); +} + +static size_t IRAM_ATTR encEncode(rmt_encoder_t* encoder, rmt_channel_handle_t tx_channel, const void* primary_data, + size_t data_size, rmt_encode_state_t* ret_state) { + auto* self = encSelf(encoder); + + // Delay after last pixel + if ((self->last_state & RMT_ENCODING_COMPLETE) && self->frame_idx == data_size) { + *ret_state = (rmt_encode_state_t)0; + return self->copy_encoder->encode( + self->copy_encoder, tx_channel, (const void*)&self->reset_code, sizeof(self->reset_code), ret_state); + } + + if (self->last_state & RMT_ENCODING_COMPLETE) { + Rgb* pixel = ((Rgb*)primary_data) + self->frame_idx; + self->buffer_len = sizeof(self->buffer); + for (size_t i = 0; i < sizeof(self->buffer); ++i) { + self->buffer[i] = pixel->getGrb(self->component_idx); + if (++self->component_idx == 3) { + self->component_idx = 0; + if (++self->frame_idx == data_size) { + self->buffer_len = i + 1; + break; + } + ++pixel; + } + } + } + + self->last_state = (rmt_encode_state_t)0; + auto encoded_symbols = self->bytes_encoder->encode( + self->bytes_encoder, tx_channel, (const void*)&self->buffer, self->buffer_len, &self->last_state); + if (self->last_state & RMT_ENCODING_MEM_FULL) { + *ret_state = RMT_ENCODING_MEM_FULL; + } else { + *ret_state = (rmt_encode_state_t)0; + } + + return encoded_symbols; +} + +static esp_err_t encReset(rmt_encoder_t* encoder) { + auto* self = encSelf(encoder); + rmt_encoder_reset(self->bytes_encoder); + rmt_encoder_reset(self->copy_encoder); + self->last_state = RMT_ENCODING_COMPLETE; + self->frame_idx = 0; + self->component_idx = 0; + return ESP_OK; +} + +static esp_err_t encDelete(rmt_encoder_t* encoder) { + auto* self = encSelf(encoder); + rmt_del_encoder(self->bytes_encoder); + rmt_del_encoder(self->copy_encoder); + return ESP_OK; +} + +RmtDriver::RmtDriver(const LedType& timing, int count, int pin, int channel_num, SemaphoreHandle_t finishedFlag) + : _timing(timing) + , _count(count) + , _pin(pin) + , _finishedFlag(finishedFlag) + , _channel(nullptr) + , _encoder {} {} + +esp_err_t RmtDriver::init() { + _encoder.base.encode = encEncode; + _encoder.base.reset = encReset; + _encoder.base.del = encDelete; + + _encoder.reset_code.duration0 = _timing.TRS / RMT_NS_PER_TICK; + + rmt_bytes_encoder_config_t bytes_cfg = { + .bit0 = { + .duration0 = uint32_t(_timing.T0H / RMT_NS_PER_TICK), + .level0 = 1, + .duration1 = uint32_t(_timing.T0L / RMT_NS_PER_TICK), + .level1 = 0, + }, + .bit1 = { + .duration0 = uint32_t(_timing.T1H / RMT_NS_PER_TICK), + .level0 = 1, + .duration1 = uint32_t(_timing.T1L / RMT_NS_PER_TICK), + .level1 = 0, + }, + .flags = { + .msb_first = 1, + }, + }; + + auto err = rmt_new_bytes_encoder(&bytes_cfg, &_encoder.bytes_encoder); + if (err != ESP_OK) { + return err; + } + + rmt_copy_encoder_config_t copy_cfg = {}; + err = rmt_new_copy_encoder(©_cfg, &_encoder.copy_encoder); + if (err != ESP_OK) { + return err; + } + + // The config must be in registerIsr, because rmt_new_tx_channel + // registers the ISR + return ESP_OK; +} + +esp_err_t RmtDriver::registerIsr(bool isFirstRegisteredChannel) { + rmt_tx_channel_config_t conf = { + .gpio_num = _pin, + .clk_src = RMT_CLK_SRC_APB, + .resolution_hz = RMT_RESOLUTION_HZ, + .mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL, + .trans_queue_depth = 1, + .flags = {}, + }; + + auto err = rmt_new_tx_channel(&conf, &_channel); + if (err != ESP_OK) { + return err; + } + + rmt_tx_event_callbacks_t callbacks_cfg = {}; + callbacks_cfg.on_trans_done = txDoneCallback; + + err = rmt_tx_register_event_callbacks(_channel, &callbacks_cfg, this); + if (err != ESP_OK) { + return err; + } + + return rmt_enable(_channel); +} + +esp_err_t RmtDriver::unregisterIsr() { + auto err = rmt_del_encoder(&_encoder.base); + if (err != ESP_OK) { + return err; + } + + err = rmt_disable(_channel); + if (err != ESP_OK) { + return err; + } + + return rmt_del_channel(_channel); +} + +bool IRAM_ATTR RmtDriver::txDoneCallback( + rmt_channel_handle_t tx_chan, const rmt_tx_done_event_data_t* edata, void* user_ctx) { + auto* self = (RmtDriver*)user_ctx; + auto taskWoken = pdTRUE; + xSemaphoreGiveFromISR(self->_finishedFlag, &taskWoken); + return taskWoken == pdTRUE; +} + +esp_err_t RmtDriver::transmit(const Rgb* buffer) { + rmt_encoder_reset(&_encoder.base); + rmt_transmit_config_t cfg = {}; + return rmt_transmit(_channel, &_encoder.base, buffer, _count, &cfg); +} +}; +#endif // !SMARTLEDS_NEW_RMT_DRIVER diff --git a/src/RmtDriver5.h b/src/RmtDriver5.h new file mode 100644 index 0000000..19a177b --- /dev/null +++ b/src/RmtDriver5.h @@ -0,0 +1,65 @@ +#pragma once + +#include "RmtDriver.h" + +#if SMARTLEDS_NEW_RMT_DRIVER +#include +#include +#include +#include + +#include "Color.h" + +#if !defined(CONFIG_RMT_ISR_IRAM_SAFE) && !defined(SMARTLEDS_DISABLE_IRAM_WARNING) +#warning "Please enable CONFIG_RMT_ISR_IRAM_SAFE IDF option." \ + "without it, the IDF driver is not able to supply data fast enough." +#endif + +namespace detail { + +constexpr const int CHANNEL_COUNT = SOC_RMT_GROUPS * SOC_RMT_CHANNELS_PER_GROUP; + +class RmtDriver; + +// This is ridiculous +struct RmtEncoderWrapper { + struct rmt_encoder_t base; + struct rmt_encoder_t* bytes_encoder; + struct rmt_encoder_t* copy_encoder; + RmtDriver* driver; + rmt_symbol_word_t reset_code; + + uint8_t buffer[SOC_RMT_MEM_WORDS_PER_CHANNEL / 8]; + rmt_encode_state_t last_state; + size_t frame_idx; + uint8_t component_idx; + uint8_t buffer_len; +}; + +static_assert(std::is_standard_layout::value == true); + +class RmtDriver { +public: + RmtDriver(const LedType& timing, int count, int pin, int channel_num, SemaphoreHandle_t finishedFlag); + RmtDriver(const RmtDriver&) = delete; + + esp_err_t init(); + esp_err_t registerIsr(bool isFirstRegisteredChannel); + esp_err_t unregisterIsr(); + esp_err_t transmit(const Rgb* buffer); + +private: + static bool IRAM_ATTR txDoneCallback( + rmt_channel_handle_t tx_chan, const rmt_tx_done_event_data_t* edata, void* user_ctx); + + const LedType& _timing; + int _count; + int _pin; + SemaphoreHandle_t _finishedFlag; + + rmt_channel_handle_t _channel; + RmtEncoderWrapper _encoder; +}; + +}; +#endif // !SMARTLEDS_NEW_RMT_DRIVER diff --git a/src/SmartLeds.cpp b/src/SmartLeds.cpp index 5a22e93..601f427 100644 --- a/src/SmartLeds.cpp +++ b/src/SmartLeds.cpp @@ -2,55 +2,8 @@ IsrCore SmartLed::_interruptCore = CoreCurrent; -SmartLed*& IRAM_ATTR SmartLed::ledForChannel( int channel ) { - static SmartLed* table[8] = { nullptr }; - assert( channel < 8 ); - return table[ channel ]; -} - -void IRAM_ATTR SmartLed::txEndCallback(rmt_channel_t channel, void *arg) { - xSemaphoreGiveFromISR(ledForChannel(channel)->_finishedFlag, nullptr); -} - - -void IRAM_ATTR SmartLed::translateForRmt(const void *src, rmt_item32_t *dest, size_t src_size, - size_t wanted_rmt_items_num, size_t *out_consumed_src_bytes, size_t *out_used_rmt_items) { - SmartLed *self; - ESP_ERROR_CHECK(rmt_translator_get_context(out_used_rmt_items, (void**)&self)); - - const auto& _bitToRmt = self->_bitToRmt; - const auto src_offset = self->_translatorSourceOffset; - - auto *src_components = (const uint8_t *)src; - size_t consumed_src_bytes = 0; - size_t used_rmt_items = 0; - - while (consumed_src_bytes < src_size && used_rmt_items + 7 < wanted_rmt_items_num) { - uint8_t val = *src_components; - - // each bit, from highest to lowest - for ( uint8_t j = 0; j != 8; j++, val <<= 1 ) { - dest->val = _bitToRmt[ val >> 7 ].val; - ++dest; - } - - used_rmt_items += 8; - ++src_components; - ++consumed_src_bytes; - - // skip alpha byte - if(((src_offset + consumed_src_bytes) % 4) == 3) { - ++src_components; - ++consumed_src_bytes; - - // TRST delay after last pixel in strip - if(consumed_src_bytes == src_size) { - (dest-1)->duration1 = self->_timing.TRS / ( detail::RMT_DURATION_NS * detail::DIVIDER ); - } - } - } - - self->_translatorSourceOffset = src_offset + consumed_src_bytes; - *out_consumed_src_bytes = consumed_src_bytes; - *out_used_rmt_items = used_rmt_items; +SmartLed*& IRAM_ATTR SmartLed::ledForChannel(int channel) { + static SmartLed* table[detail::CHANNEL_COUNT] = {}; + assert(channel < detail::CHANNEL_COUNT); + return table[channel]; } diff --git a/src/SmartLeds.h b/src/SmartLeds.h index e654aeb..935f59b 100644 --- a/src/SmartLeds.h +++ b/src/SmartLeds.h @@ -28,122 +28,88 @@ * THE SOFTWARE. */ -#include #include #include +#include -#include -#include -#include -#include #include #include - -#include +#include +#include +#include +#include #include "Color.h" -namespace detail { - -struct TimingParams { - uint32_t T0H; - uint32_t T1H; - uint32_t T0L; - uint32_t T1L; - uint32_t TRS; -}; - -static const int DIVIDER = 4; // 8 still seems to work, but timings become marginal -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 = DoubleBuffer, IsrCore isrCore = CoreCurrent) - : _timing(type), - _channel((rmt_channel_t)channel), - _count(count), - _firstBuffer(new Rgb[count]), - _secondBuffer( doubleBuffer ? new Rgb[ count ] : nullptr ), - _finishedFlag(xSemaphoreCreateBinary()) - { - assert( channel >= 0 && channel < RMT_CHANNEL_MAX ); - assert( ledForChannel( channel ) == nullptr ); - - xSemaphoreGive( _finishedFlag ); - - _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 ); - - rmt_config_t config = RMT_DEFAULT_CONFIG_TX((gpio_num_t)pin, _channel); - config.rmt_mode = RMT_MODE_TX; - config.clk_div = detail::DIVIDER; - config.mem_block_num = 1; - config.tx_config.idle_output_en = true; - - ESP_ERROR_CHECK(rmt_config(&config)); - - const auto anyAliveCached = anyAlive(); - if (!anyAliveCached && isrCore != CoreCurrent) { + 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 (!anyAlive() && isrCore != CoreCurrent) { _interruptCore = isrCore; - ESP_ERROR_CHECK(esp_ipc_call_blocking(isrCore, registerInterrupt, (void*)((intptr_t)_channel))); + ESP_ERROR_CHECK(esp_ipc_call_blocking(isrCore, registerInterrupt, (void*)this)); } else { - registerInterrupt((void*)((intptr_t)_channel)); + registerInterrupt((void*)this); } - if(!anyAliveCached) { - rmt_register_tx_end_callback(txEndCallback, NULL); - } - - ESP_ERROR_CHECK(rmt_translator_init(_channel, translateForRmt)); - ESP_ERROR_CHECK(rmt_translator_set_context(_channel, this)); - - ledForChannel( channel ) = this; + ledForChannel(channel) = this; } ~SmartLed() { - ledForChannel( _channel ) = nullptr; - if ( !anyAlive() && _interruptCore != CoreCurrent ) { - ESP_ERROR_CHECK(esp_ipc_call_blocking(_interruptCore, unregisterInterrupt, (void*)((intptr_t)_channel))); + ledForChannel(_channel) = nullptr; + if (!anyAlive() && _interruptCore != CoreCurrent) { + ESP_ERROR_CHECK(esp_ipc_call_blocking(_interruptCore, unregisterInterrupt, (void*)this)); } else { - unregisterInterrupt((void*)((intptr_t)_channel)); + 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]; } esp_err_t show() { esp_err_t err = startTransmission(); @@ -151,50 +117,46 @@ class SmartLed { 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; } - 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 IsrCore _interruptCore; - static void registerInterrupt(void *channelVoid) { - ESP_ERROR_CHECK(rmt_driver_install((rmt_channel_t)((intptr_t)channelVoid), 0, ESP_INTR_FLAG_IRAM)); + static void registerInterrupt(void* selfVoid) { + auto* self = (SmartLed*)selfVoid; + ESP_ERROR_CHECK(self->_driver.registerIsr(!anyAlive())); } - static void unregisterInterrupt(void *channelVoid) { - ESP_ERROR_CHECK(rmt_driver_uninstall((rmt_channel_t)((intptr_t)channelVoid))); + static void unregisterInterrupt(void* selfVoid) { + auto* self = (SmartLed*)selfVoid; + ESP_ERROR_CHECK(self->_driver.unregisterIsr()); } - static SmartLed*& IRAM_ATTR ledForChannel( int channel ); + static SmartLed*& IRAM_ATTR ledForChannel(int channel); static bool anyAlive() { - for ( int i = 0; i != 8; i++ ) - if ( ledForChannel( i ) != nullptr ) return true; + for (int i = 0; i != detail::CHANNEL_COUNT; i++) + if (ledForChannel(i) != nullptr) + return true; return false; } - static void IRAM_ATTR txEndCallback(rmt_channel_t channel, void *arg); - - static void IRAM_ATTR translateForRmt(const void *src, rmt_item32_t *dest, size_t src_size, - size_t wanted_rmt_items_num, size_t *out_consumed_src_bytes, size_t *out_used_rmt_items); - void swapBuffers() { if (_secondBuffer) _firstBuffer.swap(_secondBuffer); @@ -202,55 +164,49 @@ class SmartLed { esp_err_t startTransmission() { // Invalid use of the library, you must wait() fir previous frame to get processed first - if( xSemaphoreTake( _finishedFlag, 0 ) != pdTRUE ) + if (xSemaphoreTake(_finishedFlag, 0) != pdTRUE) abort(); - _translatorSourceOffset = 0; - - auto err = rmt_write_sample(_channel, (const uint8_t *)_firstBuffer.get(), _count * 4, false); - if(err != ESP_OK) { + auto err = _driver.transmit(_firstBuffer.get()); + if (err != ESP_OK) { return err; } return ESP_OK; } - const LedType& _timing; - rmt_channel_t _channel; - rmt_item32_t _bitToRmt[ 2 ]; + SemaphoreHandle_t _finishedFlag; + detail::RmtDriver _driver; + int _channel; int _count; std::unique_ptr _firstBuffer; std::unique_ptr _secondBuffer; - - size_t _translatorSourceOffset; - - SemaphoreHandle_t _finishedFlag; }; -#ifdef CONFIG_IDF_TARGET_ESP32 -#define _SMARTLEDS_SPI_HOST HSPI_HOST -#elif defined(CONFIG_IDF_TARGET_ESP32S3) +#if defined(CONFIG_IDF_TARGET_ESP32S3) #define _SMARTLEDS_SPI_HOST SPI2_HOST #else -#error "SmartLeds SPI host not defined for this chip/esp-idf version." +#define _SMARTLEDS_SPI_HOST HSPI_HOST #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; } @@ -260,14 +216,13 @@ class Apa102 { 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) + : _count(count) + , _firstBuffer(new ApaRgb[count]) + , _secondBuffer(doubleBuffer ? new ApaRgb[count] : nullptr) + , _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; @@ -276,33 +231,29 @@ class Apa102 { 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 = 1000000; 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, 1 ); - assert( ret == ESP_OK ); + auto ret = spi_bus_initialize(_SMARTLEDS_SPI_HOST, &buscfg, 1); + assert(ret == ESP_OK); - ret = spi_bus_add_device( _SMARTLEDS_SPI_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(); @@ -311,93 +262,95 @@ class Apa102 { } 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 _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; @@ -406,33 +359,29 @@ class LDP8806 { 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( _SMARTLEDS_SPI_HOST, &buscfg, 1 ); - assert( ret == ESP_OK ); + auto ret = spi_bus_initialize(_SMARTLEDS_SPI_HOST, &buscfg, 1); + assert(ret == ESP_OK); - ret = spi_bus_add_device( _SMARTLEDS_SPI_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(); @@ -441,49 +390,50 @@ class LDP8806 { } 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 _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]; }; diff --git a/test-inis/esp32-idf4-arduino.ini b/test-inis/esp32-idf4-arduino.ini new file mode 100644 index 0000000..daaf145 --- /dev/null +++ b/test-inis/esp32-idf4-arduino.ini @@ -0,0 +1,23 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; http://docs.platformio.org/page/projectconf.html + +[env:esp32dev] +platform = espressif32@5.3.0 +board = esp32dev +framework = arduino + +upload_speed = 921600 +monitor_speed = 115200 + +build_unflags = -std=gnu++11 +build_flags = + -std=gnu++14 + -fmax-errors=5 + -DLX16A_ARDUINO=1 diff --git a/test-inis/esp32-idf5-idf.ini b/test-inis/esp32-idf5-idf.ini new file mode 100644 index 0000000..1c6f859 --- /dev/null +++ b/test-inis/esp32-idf5-idf.ini @@ -0,0 +1,22 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; http://docs.platformio.org/page/projectconf.html + +[env:esp32dev] +platform = espressif32@6.3.2 +board = esp32dev +framework = espidf + +upload_speed = 921600 +monitor_speed = 115200 + +build_unflags = -std=gnu++11 +build_flags = + -std=gnu++14 + -fmax-errors=5 diff --git a/test-inis/esp32s3-idf4-arduino.ini b/test-inis/esp32s3-idf4-arduino.ini new file mode 100644 index 0000000..7b0da2f --- /dev/null +++ b/test-inis/esp32s3-idf4-arduino.ini @@ -0,0 +1,23 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; http://docs.platformio.org/page/projectconf.html + +[env:esp32dev] +platform = espressif32@6.3.2 +board = esp32-s3-devkitc-1 +framework = arduino + +upload_speed = 921600 +monitor_speed = 115200 + +build_unflags = -std=gnu++11 +build_flags = + -std=gnu++14 + -fmax-errors=5 + -DLX16A_ARDUINO=1 diff --git a/test-inis/esp32s3-idf5-idf.ini b/test-inis/esp32s3-idf5-idf.ini new file mode 100644 index 0000000..6b81e68 --- /dev/null +++ b/test-inis/esp32s3-idf5-idf.ini @@ -0,0 +1,22 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; http://docs.platformio.org/page/projectconf.html + +[env:esp32dev] +platform = espressif32@6.3.2 +board = esp32-s3-devkitc-1 +framework = espidf + +upload_speed = 921600 +monitor_speed = 115200 + +build_unflags = -std=gnu++11 +build_flags = + -std=gnu++14 + -fmax-errors=5 diff --git a/test/catch.hpp b/test/catch.hpp index cc3f97c..a1b6ed9 100644 --- a/test/catch.hpp +++ b/test/catch.hpp @@ -14684,4 +14684,3 @@ using Catch::Detail::Approx; // end catch_reenable_warnings.h // end catch.hpp #endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED - diff --git a/test/colorConversion.cpp b/test/colorConversion.cpp index 1fb8d08..c390135 100644 --- a/test/colorConversion.cpp +++ b/test/colorConversion.cpp @@ -1,17 +1,17 @@ #include #include #include -#include #include +#include // Oracle functions, source: https://gist.github.com/yoggy/8999625 // -#define min_f(a, b, c) (fminf(a, fminf(b, c))) -#define max_f(a, b, c) (fmaxf(a, fmaxf(b, c))) +#define min_f(a, b, c) (fminf(a, fminf(b, c))) +#define max_f(a, b, c) (fmaxf(a, fmaxf(b, c))) -void rgb2hsv(const unsigned char &src_r, const unsigned char &src_g, const unsigned char &src_b, unsigned char &dst_h, unsigned char &dst_s, unsigned char &dst_v) -{ +void rgb2hsv(const unsigned char& src_r, const unsigned char& src_g, const unsigned char& src_b, unsigned char& dst_h, + unsigned char& dst_s, unsigned char& dst_v) { float r = src_r / 255.0f; float g = src_g / 255.0f; float b = src_b / 255.0f; @@ -26,53 +26,62 @@ void rgb2hsv(const unsigned char &src_r, const unsigned char &src_g, const unsig if (max == 0.0f) { s = 0; h = 0; - } - else if (max - min == 0.0f) { + } else if (max - min == 0.0f) { s = 0; h = 0; - } - else { + } else { s = (max - min) / max; if (max == r) { h = 60 * ((g - b) / (max - min)) + 0; - } - else if (max == g) { + } else if (max == g) { h = 60 * ((b - r) / (max - min)) + 120; - } - else { + } else { h = 60 * ((r - g) / (max - min)) + 240; } } - if (h < 0) h += 360.0f; + if (h < 0) + h += 360.0f; - dst_h = (unsigned char)(h / 360.0 * 255); // dst_h : 0-180 + dst_h = (unsigned char)(h / 360.0 * 255); // dst_h : 0-180 dst_s = (unsigned char)(s * 255); // dst_s : 0-255 dst_v = (unsigned char)(v * 255); // dst_v : 0-255 } -void hsv2rgb(const unsigned char &src_h, const unsigned char &src_s, const unsigned char &src_v, unsigned char &dst_r, unsigned char &dst_g, unsigned char &dst_b) -{ +void hsv2rgb(const unsigned char& src_h, const unsigned char& src_s, const unsigned char& src_v, unsigned char& dst_r, + unsigned char& dst_g, unsigned char& dst_b) { float h = src_h / 255.0 * 360; // 0-360 float s = src_s / 255.0f; // 0.0-1.0 float v = src_v / 255.0f; // 0.0-1.0 float r, g, b; // 0.0-1.0 - int hi = (int)(h / 60.0f) % 6; - float f = (h / 60.0f) - hi; - float p = v * (1.0f - s); - float q = v * (1.0f - s * f); - float t = v * (1.0f - s * (1.0f - f)); - - switch(hi) { - case 0: r = v, g = t, b = p; break; - case 1: r = q, g = v, b = p; break; - case 2: r = p, g = v, b = t; break; - case 3: r = p, g = q, b = v; break; - case 4: r = t, g = p, b = v; break; - case 5: r = v, g = p, b = q; break; + int hi = (int)(h / 60.0f) % 6; + float f = (h / 60.0f) - hi; + float p = v * (1.0f - s); + float q = v * (1.0f - s * f); + float t = v * (1.0f - s * (1.0f - f)); + + switch (hi) { + case 0: + r = v, g = t, b = p; + break; + case 1: + r = q, g = v, b = p; + break; + case 2: + r = p, g = v, b = t; + break; + case 3: + r = p, g = q, b = v; + break; + case 4: + r = t, g = p, b = v; + break; + case 5: + r = v, g = p, b = q; + break; } dst_r = (unsigned char)(r * 255); // dst_r : 0-255 @@ -82,52 +91,49 @@ void hsv2rgb(const unsigned char &src_h, const unsigned char &src_s, const unsig // End of oracle -std::vector< Rgb > rgbSamples() { - std::vector< Rgb > ret; +std::vector rgbSamples() { + std::vector ret; static const int step = 10; - for ( int r = 0; r <= 255; r += step ) - for ( int g = 0; g <= 255; g += step ) - for ( int b = 0; b <= 255; b += step ) - ret.emplace_back( r, g, b ); + for (int r = 0; r <= 255; r += step) + for (int g = 0; g <= 255; g += step) + for (int b = 0; b <= 255; b += step) + ret.emplace_back(r, g, b); return ret; } -std::vector< Hsv > hsvSamples() { - std::vector< Hsv > ret; +std::vector hsvSamples() { + std::vector ret; static const int step = 10; - for ( int h = 0; h <= 255; h += step ) - for ( int s = 0; s <= 255; s += step ) - for ( int v = 0; v <= 255; v += 255 ) - ret.emplace_back( h, s, v ); + for (int h = 0; h <= 255; h += step) + for (int s = 0; s <= 255; s += step) + for (int v = 0; v <= 255; v += 255) + ret.emplace_back(h, s, v); return ret; } -int dist( int x, int y ) { - return std::abs( x - y ); -} - +int dist(int x, int y) { return std::abs(x - y); } TEST_CASE("RGB <-> HSV", "[rgb<->hsv]") { - for ( Rgb color : rgbSamples() ) { - Hsv hsv{ color }; - Rgb rgb{ hsv }; + for (Rgb color : rgbSamples()) { + Hsv hsv { color }; + Rgb rgb { hsv }; uint8_t h, s, v; - rgb2hsv( color.r, color.g, color.b, h, s, v ); + rgb2hsv(color.r, color.g, color.b, h, s, v); uint8_t r, g, b; - hsv2rgb( hsv.h, hsv.s, hsv.v, r, g, b ); + hsv2rgb(hsv.h, hsv.s, hsv.v, r, g, b); - CAPTURE( int( color.r ), int( color.g ), int( color.b ) ); - CAPTURE( int( rgb.r ), int( rgb.g ), int( rgb.b ) ); - CAPTURE( int( hsv.h ), int( hsv.s ), int( hsv.v ) ); - CAPTURE( int( h ), int( s ), int( v ) ); - CAPTURE( int( r ), int( g ), int( b ) ); + CAPTURE(int(color.r), int(color.g), int(color.b)); + CAPTURE(int(rgb.r), int(rgb.g), int(rgb.b)); + CAPTURE(int(hsv.h), int(hsv.s), int(hsv.v)); + CAPTURE(int(h), int(s), int(v)); + CAPTURE(int(r), int(g), int(b)); - REQUIRE( dist( rgb.r, r ) <= 1 ); - REQUIRE( dist( rgb.g, g ) <= 1 ); - REQUIRE( dist( rgb.b, b ) <= 1 ); + REQUIRE(dist(rgb.r, r) <= 1); + REQUIRE(dist(rgb.g, g) <= 1); + REQUIRE(dist(rgb.b, b) <= 1); - REQUIRE( dist(hsv.h, h ) <= 1 ); - REQUIRE( dist(hsv.s, s ) <= 1 ); - REQUIRE( dist(hsv.v, v ) <= 1 ); + REQUIRE(dist(hsv.h, h) <= 1); + REQUIRE(dist(hsv.s, s) <= 1); + REQUIRE(dist(hsv.v, v) <= 1); } -} \ No newline at end of file +} diff --git a/test/main.cpp b/test/main.cpp index fd9339d..b3143fb 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -1,2 +1,2 @@ #define CATCH_CONFIG_MAIN -#include \ No newline at end of file +#include From 3330090587b1e3302a1c5f7a60b3bb01d0636093 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Bo=C4=8Dek?= Date: Sun, 9 Jul 2023 12:28:22 +0200 Subject: [PATCH 19/34] Release 3.0.0 --- library.json | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/library.json b/library.json index 6a6a01c..1bbc44d 100644 --- a/library.json +++ b/library.json @@ -1,21 +1,20 @@ { - "name": "SmartLeds", - "keywords": "LED, esp32, NeoPixel, WS2812, WS2812B, APA102", - "description": "Simple & intuitive way to drive various smart LEDs on ESP32.", - "repository": + "name": "SmartLeds", + "keywords": "LED, esp32, NeoPixel, WS2812, WS2812B, APA102", + "description": "Simple & intuitive way to drive various smart LEDs on ESP32.", + "repository": { + "type": "git", + "url": "https://github.com/RoboticsBrno/SmartLeds.git" + }, + "authors": [ { - "type": "git", - "url": "https://github.com/RoboticsBrno/SmartLeds.git" - }, - "authors": [ - { - "name": "Jan Mrázek", - "email": "email@honzamrazek.cz", - "url": "http://www.robotikabrno.cz", - "maintainer": true - } - ], - "version": "2.0.0", - "frameworks": ["arduino","espidf"], - "platforms": "espressif32" + "name": "Jan Mrázek", + "email": "email@honzamrazek.cz", + "url": "http://www.robotikabrno.cz", + "maintainer": true + } + ], + "version": "3.0.0", + "frameworks": ["arduino", "espidf"], + "platforms": "espressif32" } From c054909cdc5dfe7d43b40bfd57c85d416b31cfc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Bo=C4=8Dek?= Date: Sun, 9 Jul 2023 15:31:18 +0200 Subject: [PATCH 20/34] feat: add channel() getter to SmartLeds --- src/SmartLeds.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/SmartLeds.h b/src/SmartLeds.h index 935f59b..6901be2 100644 --- a/src/SmartLeds.h +++ b/src/SmartLeds.h @@ -126,6 +126,7 @@ class SmartLed { } int size() const { return _count; } + int channel() const { return _channel; } Rgb* begin() { return _firstBuffer.get(); } const Rgb* begin() const { return _firstBuffer.get(); } From 4f30ba6eb3d7a6b1b0d3440fddf3dd97c4156bdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Bo=C4=8Dek?= Date: Sun, 9 Jul 2023 15:31:33 +0200 Subject: [PATCH 21/34] fix: compatiblity with esp32c3 --- .github/workflows/tests.yml | 3 +++ src/SmartLeds.h | 14 +++++++++++--- test-inis/esp32c3-idf4-arduino.ini | 23 +++++++++++++++++++++++ test-inis/esp32c3-idf5-idf.ini | 22 ++++++++++++++++++++++ 4 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 test-inis/esp32c3-idf4-arduino.ini create mode 100644 test-inis/esp32c3-idf5-idf.ini diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4e42927..96199f3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -6,6 +6,7 @@ jobs: build-test: runs-on: ubuntu-22.04 strategy: + fail-fast: false matrix: example: - examples/basic @@ -14,6 +15,8 @@ jobs: - esp32-idf5-idf.ini - esp32s3-idf4-arduino.ini - esp32s3-idf5-idf.ini + - esp32c3-idf4-arduino.ini + - esp32c3-idf5-idf.ini steps: - uses: actions/checkout@v3 - name: Set up Python diff --git a/src/SmartLeds.h b/src/SmartLeds.h index 6901be2..262b071 100644 --- a/src/SmartLeds.h +++ b/src/SmartLeds.h @@ -72,6 +72,8 @@ class SmartLed { // // 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()) @@ -87,10 +89,13 @@ class SmartLed { _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 { + } else +#endif + { registerInterrupt((void*)this); } @@ -99,9 +104,12 @@ class SmartLed { ~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 { + } else +#endif + { unregisterInterrupt((void*)this); } vSemaphoreDelete(_finishedFlag); @@ -184,7 +192,7 @@ class SmartLed { std::unique_ptr _secondBuffer; }; -#if defined(CONFIG_IDF_TARGET_ESP32S3) +#if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3) #define _SMARTLEDS_SPI_HOST SPI2_HOST #else #define _SMARTLEDS_SPI_HOST HSPI_HOST diff --git a/test-inis/esp32c3-idf4-arduino.ini b/test-inis/esp32c3-idf4-arduino.ini new file mode 100644 index 0000000..4755550 --- /dev/null +++ b/test-inis/esp32c3-idf4-arduino.ini @@ -0,0 +1,23 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; http://docs.platformio.org/page/projectconf.html + +[env:esp32dev] +platform = espressif32@6.3.2 +board = esp32-c3-devkitm-1 +framework = arduino + +upload_speed = 921600 +monitor_speed = 115200 + +build_unflags = -std=gnu++11 +build_flags = + -std=gnu++14 + -fmax-errors=5 + -DLX16A_ARDUINO=1 diff --git a/test-inis/esp32c3-idf5-idf.ini b/test-inis/esp32c3-idf5-idf.ini new file mode 100644 index 0000000..5ff7403 --- /dev/null +++ b/test-inis/esp32c3-idf5-idf.ini @@ -0,0 +1,22 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; http://docs.platformio.org/page/projectconf.html + +[env:esp32dev] +platform = espressif32@6.3.2 +board = esp32-c3-devkitm-1 +framework = espidf + +upload_speed = 921600 +monitor_speed = 115200 + +build_unflags = -std=gnu++11 +build_flags = + -std=gnu++14 + -fmax-errors=5 From 95e286c28a776afd3bf44a8835ce84f9b2ed7b24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Bo=C4=8Dek?= Date: Sun, 9 Jul 2023 15:37:49 +0200 Subject: [PATCH 22/34] Release 3.1.0 --- library.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library.json b/library.json index 1bbc44d..d6e52a9 100644 --- a/library.json +++ b/library.json @@ -14,7 +14,7 @@ "maintainer": true } ], - "version": "3.0.0", + "version": "3.1.0", "frameworks": ["arduino", "espidf"], "platforms": "espressif32" } From 713065b47515b5ba0cc2a57d6497516ac943bd39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Bo=C4=8Dek?= Date: Mon, 10 Jul 2023 10:48:32 +0200 Subject: [PATCH 23/34] fix: warning about narrowing conversion --- library.json | 2 +- src/RmtDriver5.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/library.json b/library.json index d6e52a9..f36e8f7 100644 --- a/library.json +++ b/library.json @@ -14,7 +14,7 @@ "maintainer": true } ], - "version": "3.1.0", + "version": "3.1.1", "frameworks": ["arduino", "espidf"], "platforms": "espressif32" } diff --git a/src/RmtDriver5.cpp b/src/RmtDriver5.cpp index 93e7204..b25b028 100644 --- a/src/RmtDriver5.cpp +++ b/src/RmtDriver5.cpp @@ -87,15 +87,15 @@ esp_err_t RmtDriver::init() { rmt_bytes_encoder_config_t bytes_cfg = { .bit0 = { - .duration0 = uint32_t(_timing.T0H / RMT_NS_PER_TICK), + .duration0 = uint16_t(_timing.T0H / RMT_NS_PER_TICK), .level0 = 1, - .duration1 = uint32_t(_timing.T0L / RMT_NS_PER_TICK), + .duration1 = uint16_t(_timing.T0L / RMT_NS_PER_TICK), .level1 = 0, }, .bit1 = { - .duration0 = uint32_t(_timing.T1H / RMT_NS_PER_TICK), + .duration0 = uint16_t(_timing.T1H / RMT_NS_PER_TICK), .level0 = 1, - .duration1 = uint32_t(_timing.T1L / RMT_NS_PER_TICK), + .duration1 = uint16_t(_timing.T1L / RMT_NS_PER_TICK), .level1 = 0, }, .flags = { From ede05a64142a75e71d7dcd4a321e045ea53e0c5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Bo=C4=8Dek?= Date: Mon, 10 Jul 2023 10:59:32 +0200 Subject: [PATCH 24/34] fix: pin type conversion --- library.json | 2 +- src/RmtDriver5.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library.json b/library.json index f36e8f7..d0d7ac2 100644 --- a/library.json +++ b/library.json @@ -14,7 +14,7 @@ "maintainer": true } ], - "version": "3.1.1", + "version": "3.1.2", "frameworks": ["arduino", "espidf"], "platforms": "espressif32" } diff --git a/src/RmtDriver5.cpp b/src/RmtDriver5.cpp index b25b028..52ec2c1 100644 --- a/src/RmtDriver5.cpp +++ b/src/RmtDriver5.cpp @@ -121,7 +121,7 @@ esp_err_t RmtDriver::init() { esp_err_t RmtDriver::registerIsr(bool isFirstRegisteredChannel) { rmt_tx_channel_config_t conf = { - .gpio_num = _pin, + .gpio_num = (gpio_num_t)_pin, .clk_src = RMT_CLK_SRC_APB, .resolution_hz = RMT_RESOLUTION_HZ, .mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL, From de331ca43affedfd73ed27a2a7355ff796f39348 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Bo=C4=8Dek?= Date: Thu, 27 Jun 2024 15:46:00 +0200 Subject: [PATCH 25/34] fix: do not set IRAM ISR flag if the config is not set (#48) * fix: do not set IRAM ISR flag if the config is not set --- library.json | 2 +- src/RmtDriver4.cpp | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/library.json b/library.json index d0d7ac2..32973b5 100644 --- a/library.json +++ b/library.json @@ -14,7 +14,7 @@ "maintainer": true } ], - "version": "3.1.2", + "version": "3.1.3", "frameworks": ["arduino", "espidf"], "platforms": "espressif32" } diff --git a/src/RmtDriver4.cpp b/src/RmtDriver4.cpp index 41e0565..9827d98 100644 --- a/src/RmtDriver4.cpp +++ b/src/RmtDriver4.cpp @@ -37,7 +37,13 @@ esp_err_t RmtDriver::init() { } esp_err_t RmtDriver::registerIsr(bool isFirstRegisteredChannel) { - auto err = rmt_driver_install(_channel, 0, ESP_INTR_FLAG_IRAM); + auto err = rmt_driver_install(_channel, 0, +#if defined(CONFIG_RMT_ISR_IRAM_SAFE) + ESP_INTR_FLAG_IRAM +#else + 0 +#endif + ); if (err != ESP_OK) { return err; } From cb3cbc198562b62207980a422bf9fef150a04822 Mon Sep 17 00:00:00 2001 From: Vojtech Bocek Date: Mon, 25 Nov 2024 19:37:17 +0100 Subject: [PATCH 26/34] fix: DMA channel picking on S3 --- src/SmartLeds.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/SmartLeds.h b/src/SmartLeds.h index 262b071..d7294fd 100644 --- a/src/SmartLeds.h +++ b/src/SmartLeds.h @@ -194,8 +194,10 @@ class SmartLed { #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 { @@ -247,7 +249,7 @@ class Apa102 { devcfg.queue_size = TRANS_COUNT; devcfg.pre_cb = nullptr; - auto ret = spi_bus_initialize(_SMARTLEDS_SPI_HOST, &buscfg, 1); + 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); @@ -375,7 +377,7 @@ class LDP8806 { devcfg.queue_size = TRANS_COUNT_MAX; devcfg.pre_cb = nullptr; - auto ret = spi_bus_initialize(_SMARTLEDS_SPI_HOST, &buscfg, 1); + 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); From 6516d2b0f42125ffb00236f7a2ecc93a9bffe4da Mon Sep 17 00:00:00 2001 From: Vojtech Bocek Date: Mon, 25 Nov 2024 20:37:22 +0100 Subject: [PATCH 27/34] fix: make Apa102 clock speed configurable --- src/SmartLeds.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SmartLeds.h b/src/SmartLeds.h index d7294fd..f8cf3ce 100644 --- a/src/SmartLeds.h +++ b/src/SmartLeds.h @@ -227,7 +227,7 @@ class Apa102 { static const int FINAL_FRAME_SIZE = 4; static const int TRANS_COUNT = 2 + 8; - Apa102(int count, int clkpin, int datapin, BufferType doubleBuffer = SingleBuffer) + 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) @@ -243,7 +243,7 @@ class Apa102 { spi_device_interface_config_t devcfg; memset(&devcfg, 0, sizeof(devcfg)); - devcfg.clock_speed_hz = 1000000; + devcfg.clock_speed_hz = clock_speed_hz; devcfg.mode = 0; devcfg.spics_io_num = -1; devcfg.queue_size = TRANS_COUNT; From bbeb895fe2d54a62f1dc20e89189aad0f8d91f4f Mon Sep 17 00:00:00 2001 From: Vojtech Bocek Date: Mon, 25 Nov 2024 22:08:02 +0100 Subject: [PATCH 28/34] fix: init _transCount in Apa102 --- src/SmartLeds.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/SmartLeds.h b/src/SmartLeds.h index f8cf3ce..d4c9571 100644 --- a/src/SmartLeds.h +++ b/src/SmartLeds.h @@ -231,6 +231,7 @@ class Apa102 { : _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)); From dd49716aa0116ab157059581c2a81252992835ec Mon Sep 17 00:00:00 2001 From: Vojtech Bocek Date: Mon, 2 Dec 2024 18:57:11 +0100 Subject: [PATCH 29/34] chore: bump to 3.1.4 --- library.json | 2 +- library.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library.json b/library.json index 32973b5..1fbdb24 100644 --- a/library.json +++ b/library.json @@ -14,7 +14,7 @@ "maintainer": true } ], - "version": "3.1.3", + "version": "3.1.4", "frameworks": ["arduino", "espidf"], "platforms": "espressif32" } diff --git a/library.properties b/library.properties index 2c43807..c150e60 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=SmartLeds -version=2.0.0 +version=3.1.4 author=Jan Mrázek maintainer=Jan Mrázek sentence=Simple & intuitive way to drive various smart LEDs on ESP32. From 3f1e230c3b1bbab513446bf940607930cb4addc4 Mon Sep 17 00:00:00 2001 From: Vojtech Bocek Date: Fri, 27 Dec 2024 16:19:12 +0100 Subject: [PATCH 30/34] fix: end frame for SK9822 LEDs --- library.json | 2 +- library.properties | 2 +- src/SmartLeds.h | 9 ++++++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/library.json b/library.json index 1fbdb24..3bb486c 100644 --- a/library.json +++ b/library.json @@ -14,7 +14,7 @@ "maintainer": true } ], - "version": "3.1.4", + "version": "3.1.5", "frameworks": ["arduino", "espidf"], "platforms": "espressif32" } diff --git a/library.properties b/library.properties index c150e60..9d6f707 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=SmartLeds -version=3.1.4 +version=3.1.5 author=Jan Mrázek maintainer=Jan Mrázek sentence=Simple & intuitive way to drive various smart LEDs on ESP32. diff --git a/src/SmartLeds.h b/src/SmartLeds.h index d4c9571..cfef25b 100644 --- a/src/SmartLeds.h +++ b/src/SmartLeds.h @@ -200,6 +200,7 @@ class SmartLed { #define _SMARTLEDS_SPI_DMA_CHAN 1 #endif +// Can also handle SK9822 class Apa102 { public: struct ApaRgb { @@ -256,7 +257,7 @@ class Apa102 { 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, 0); } ~Apa102() { @@ -304,8 +305,10 @@ class Apa102 { 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; + // https://cpldcpu.com/2016/12/13/sk9822-a-clone-of-the-apa102/ - "Unified protocol" + const int end_bits = 32 + (_count/2 + 1); + for (int i = 0; i < end_bits; i += 32 * FINAL_FRAME_SIZE) { + _transactions[2 + i].length = std::min(32 * FINAL_FRAME_SIZE, end_bits - i); _transactions[2 + i].tx_buffer = _finalFrame; spi_device_queue_trans(_spi, _transactions + 2 + i, portMAX_DELAY); _transCount++; From cee7537b918b575e8045a5e21b527fc09fb35c35 Mon Sep 17 00:00:00 2001 From: Jakub Andrysek Date: Mon, 23 Jun 2025 16:04:48 +0200 Subject: [PATCH 31/34] fix: update minimum CMake version to 3.10 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7caf7f5..c25563e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.10) set(SRCS "src/Color.cpp" From 3f745cdeb42c7408a93f04e04faa18a66025b7e1 Mon Sep 17 00:00:00 2001 From: Petr Kubica Date: Fri, 4 Jul 2025 03:30:17 +0200 Subject: [PATCH 32/34] force data used by rmt to sram --- src/SmartLeds.h | 61 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 50 insertions(+), 11 deletions(-) diff --git a/src/SmartLeds.h b/src/SmartLeds.h index cfef25b..d562ce5 100644 --- a/src/SmartLeds.h +++ b/src/SmartLeds.h @@ -62,6 +62,26 @@ enum BufferType { SingleBuffer = 0, DoubleBuffer }; enum IsrCore { CoreFirst = 0, CoreSecond = 1, CoreCurrent = 2 }; +struct RmtDriverDeleter { + void operator()(detail::RmtDriver* ptr) const { + if (ptr) { + std::destroy_at(ptr); + heap_caps_free(ptr); + } + } +}; + +struct RgbDeleter { + int count = 0; + + void operator()(Rgb* ptr) const { + if (ptr) { + std::destroy(ptr, ptr + count); + heap_caps_free(ptr); + } + } +}; + class SmartLed { public: friend class detail::RmtDriver; @@ -77,17 +97,36 @@ class SmartLed { 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) { + , _count(count) { assert(channel >= 0 && channel < detail::CHANNEL_COUNT); assert(ledForChannel(channel) == nullptr); xSemaphoreGive(_finishedFlag); - _driver.init(); + auto mem = heap_caps_malloc(sizeof(detail::RmtDriver), MALLOC_CAP_INTERNAL); + if (!mem) { + throw std::bad_alloc();; + } + _driver.reset(new (reinterpret_cast(mem)) detail::RmtDriver(type, count, pin, channel, _finishedFlag)); + + constexpr auto allocateRgb = [](size_t count, auto memoryCaps) { + auto mem = reinterpret_cast(heap_caps_malloc(sizeof(Rgb) * count, memoryCaps)); + if (!mem) { + throw std::bad_alloc();; + } + return new (mem) Rgb[count]; + }; + + _firstBuffer.reset(allocateRgb(count, MALLOC_CAP_INTERNAL)); + _firstBuffer.get_deleter().count = count; + + if (doubleBuffer) { + _secondBuffer.reset(allocateRgb(count, MALLOC_CAP_INTERNAL)); + _secondBuffer.get_deleter().count = count; + } + + _driver->init(); #if !defined(SOC_CPU_CORES_NUM) || SOC_CPU_CORES_NUM > 1 if (!anyAlive() && isrCore != CoreCurrent) { @@ -149,12 +188,12 @@ class SmartLed { static void registerInterrupt(void* selfVoid) { auto* self = (SmartLed*)selfVoid; - ESP_ERROR_CHECK(self->_driver.registerIsr(!anyAlive())); + ESP_ERROR_CHECK(self->_driver->registerIsr(!anyAlive())); } static void unregisterInterrupt(void* selfVoid) { auto* self = (SmartLed*)selfVoid; - ESP_ERROR_CHECK(self->_driver.unregisterIsr()); + ESP_ERROR_CHECK(self->_driver->unregisterIsr()); } static SmartLed*& IRAM_ATTR ledForChannel(int channel); @@ -176,7 +215,7 @@ class SmartLed { if (xSemaphoreTake(_finishedFlag, 0) != pdTRUE) abort(); - auto err = _driver.transmit(_firstBuffer.get()); + auto err = _driver->transmit(_firstBuffer.get()); if (err != ESP_OK) { return err; } @@ -185,11 +224,11 @@ class SmartLed { } SemaphoreHandle_t _finishedFlag; - detail::RmtDriver _driver; + std::unique_ptr _driver; int _channel; int _count; - std::unique_ptr _firstBuffer; - std::unique_ptr _secondBuffer; + std::unique_ptr _firstBuffer; + std::unique_ptr _secondBuffer; }; #if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3) From 4cea3c68b1724146755cedb8d8f634f05a67a1ed Mon Sep 17 00:00:00 2001 From: Petr Kubica Date: Fri, 4 Jul 2025 03:37:08 +0200 Subject: [PATCH 33/34] update std to c++17 --- test-inis/esp32-idf4-arduino.ini | 2 +- test-inis/esp32-idf5-idf.ini | 2 +- test-inis/esp32c3-idf4-arduino.ini | 2 +- test-inis/esp32c3-idf5-idf.ini | 2 +- test-inis/esp32s3-idf4-arduino.ini | 2 +- test-inis/esp32s3-idf5-idf.ini | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test-inis/esp32-idf4-arduino.ini b/test-inis/esp32-idf4-arduino.ini index daaf145..ff4d229 100644 --- a/test-inis/esp32-idf4-arduino.ini +++ b/test-inis/esp32-idf4-arduino.ini @@ -18,6 +18,6 @@ monitor_speed = 115200 build_unflags = -std=gnu++11 build_flags = - -std=gnu++14 + -std=gnu++17 -fmax-errors=5 -DLX16A_ARDUINO=1 diff --git a/test-inis/esp32-idf5-idf.ini b/test-inis/esp32-idf5-idf.ini index 1c6f859..87caa29 100644 --- a/test-inis/esp32-idf5-idf.ini +++ b/test-inis/esp32-idf5-idf.ini @@ -18,5 +18,5 @@ monitor_speed = 115200 build_unflags = -std=gnu++11 build_flags = - -std=gnu++14 + -std=gnu++17 -fmax-errors=5 diff --git a/test-inis/esp32c3-idf4-arduino.ini b/test-inis/esp32c3-idf4-arduino.ini index 4755550..13a5785 100644 --- a/test-inis/esp32c3-idf4-arduino.ini +++ b/test-inis/esp32c3-idf4-arduino.ini @@ -18,6 +18,6 @@ monitor_speed = 115200 build_unflags = -std=gnu++11 build_flags = - -std=gnu++14 + -std=gnu++17 -fmax-errors=5 -DLX16A_ARDUINO=1 diff --git a/test-inis/esp32c3-idf5-idf.ini b/test-inis/esp32c3-idf5-idf.ini index 5ff7403..2ce9c42 100644 --- a/test-inis/esp32c3-idf5-idf.ini +++ b/test-inis/esp32c3-idf5-idf.ini @@ -18,5 +18,5 @@ monitor_speed = 115200 build_unflags = -std=gnu++11 build_flags = - -std=gnu++14 + -std=gnu++17 -fmax-errors=5 diff --git a/test-inis/esp32s3-idf4-arduino.ini b/test-inis/esp32s3-idf4-arduino.ini index 7b0da2f..4aacd8f 100644 --- a/test-inis/esp32s3-idf4-arduino.ini +++ b/test-inis/esp32s3-idf4-arduino.ini @@ -18,6 +18,6 @@ monitor_speed = 115200 build_unflags = -std=gnu++11 build_flags = - -std=gnu++14 + -std=gnu++17 -fmax-errors=5 -DLX16A_ARDUINO=1 diff --git a/test-inis/esp32s3-idf5-idf.ini b/test-inis/esp32s3-idf5-idf.ini index 6b81e68..321eea6 100644 --- a/test-inis/esp32s3-idf5-idf.ini +++ b/test-inis/esp32s3-idf5-idf.ini @@ -18,5 +18,5 @@ monitor_speed = 115200 build_unflags = -std=gnu++11 build_flags = - -std=gnu++14 + -std=gnu++17 -fmax-errors=5 From 4a87c94dd893d0e0eac98eda088ee1be09622537 Mon Sep 17 00:00:00 2001 From: Petr Kubica Date: Fri, 4 Jul 2025 03:52:46 +0200 Subject: [PATCH 34/34] dont use exceptions when disabled --- src/SmartLeds.h | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/SmartLeds.h b/src/SmartLeds.h index d562ce5..5ae26e1 100644 --- a/src/SmartLeds.h +++ b/src/SmartLeds.h @@ -82,6 +82,13 @@ struct RgbDeleter { } }; +#if __cpp_exceptions + #define SMARTLEDS_ALLOC_FAIL() throw std::bad_alloc(); +#else + #define SMARTLEDS_ALLOC_FAIL() abort(); +#endif + + class SmartLed { public: friend class detail::RmtDriver; @@ -106,14 +113,14 @@ class SmartLed { auto mem = heap_caps_malloc(sizeof(detail::RmtDriver), MALLOC_CAP_INTERNAL); if (!mem) { - throw std::bad_alloc();; + SMARTLEDS_ALLOC_FAIL(); } _driver.reset(new (reinterpret_cast(mem)) detail::RmtDriver(type, count, pin, channel, _finishedFlag)); constexpr auto allocateRgb = [](size_t count, auto memoryCaps) { auto mem = reinterpret_cast(heap_caps_malloc(sizeof(Rgb) * count, memoryCaps)); if (!mem) { - throw std::bad_alloc();; + SMARTLEDS_ALLOC_FAIL(); } return new (mem) Rgb[count]; };