diff --git a/code/components/jomjol_controlGPIO/Color.cpp b/code/components/jomjol_controlGPIO/Color.cpp index b2beb9acf..cdefcc0de 100644 --- a/code/components/jomjol_controlGPIO/Color.cpp +++ b/code/components/jomjol_controlGPIO/Color.cpp @@ -1,27 +1,53 @@ +/******************************************************************************** + * https://github.com/RoboticsBrno/SmartLeds + * + * MIT License + * + * Copyright (c) 2017 RoboticsBrno (RobotikaBrno) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *******************************************************************************/ + #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,104 +55,133 @@ 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 ) { + 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=( Hsv hsv ) { - Rgb r{ hsv }; - swap( r ); +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; + 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::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; +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; return *this; } -uint8_t IRAM_ATTR Rgb::getGrb( int idx ) { - switch ( idx ) { - case 0: return g; - case 1: return r; - case 2: return b; - } - __builtin_unreachable(); +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); + a = alpha; + return *this; } -Hsv::Hsv( 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=( Rgb rgb ) { - Hsv h{ rgb }; - swap( h ); +Hsv& Hsv::operator=(const Rgb& rgb) { + Hsv h { rgb }; + swap(h); return *this; } diff --git a/code/components/jomjol_controlGPIO/Color.h b/code/components/jomjol_controlGPIO/Color.h index aecd69c97..38d3e18ec 100644 --- a/code/components/jomjol_controlGPIO/Color.h +++ b/code/components/jomjol_controlGPIO/Color.h @@ -1,51 +1,90 @@ -#pragma once +/******************************************************************************** + * https://github.com/RoboticsBrno/SmartLeds + * + * MIT License + * + * Copyright (c) 2017 RoboticsBrno (RobotikaBrno) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *******************************************************************************/ -#ifndef COLOR_H -#define COLOR_H +#pragma once -#include #include "esp_attr.h" +#include union Hsv; union Rgb { - struct __attribute__ ((packed)) { - uint8_t r, g, b, a; + 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 ) : 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 ); - 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); b = channelGamma(b); } - uint8_t IRAM_ATTR getGrb( int idx ); - - 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,22 +92,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( 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(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; } }; - -#endif //COLOR_H diff --git a/code/components/jomjol_controlGPIO/RmtDriver.h b/code/components/jomjol_controlGPIO/RmtDriver.h new file mode 100644 index 000000000..a35176446 --- /dev/null +++ b/code/components/jomjol_controlGPIO/RmtDriver.h @@ -0,0 +1,60 @@ +/******************************************************************************** + * https://github.com/RoboticsBrno/SmartLeds + * + * MIT License + * + * Copyright (c) 2017 RoboticsBrno (RobotikaBrno) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *******************************************************************************/ + +#pragma once + +#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/code/components/jomjol_controlGPIO/RmtDriver4.cpp b/code/components/jomjol_controlGPIO/RmtDriver4.cpp new file mode 100644 index 000000000..f71c29cf1 --- /dev/null +++ b/code/components/jomjol_controlGPIO/RmtDriver4.cpp @@ -0,0 +1,143 @@ +/******************************************************************************** + * https://github.com/RoboticsBrno/SmartLeds + * + * MIT License + * + * Copyright (c) 2017 RoboticsBrno (RobotikaBrno) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *******************************************************************************/ + +#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, +#if defined(CONFIG_RMT_ISR_IRAM_SAFE) + ESP_INTR_FLAG_IRAM +#else + 0 +#endif + ); + 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/code/components/jomjol_controlGPIO/RmtDriver4.h b/code/components/jomjol_controlGPIO/RmtDriver4.h new file mode 100644 index 000000000..54236318b --- /dev/null +++ b/code/components/jomjol_controlGPIO/RmtDriver4.h @@ -0,0 +1,68 @@ +/******************************************************************************** + * https://github.com/RoboticsBrno/SmartLeds + * + * MIT License + * + * Copyright (c) 2017 RoboticsBrno (RobotikaBrno) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *******************************************************************************/ + +#pragma once + +#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/code/components/jomjol_controlGPIO/RmtDriver5.cpp b/code/components/jomjol_controlGPIO/RmtDriver5.cpp new file mode 100644 index 000000000..d995ee8e8 --- /dev/null +++ b/code/components/jomjol_controlGPIO/RmtDriver5.cpp @@ -0,0 +1,202 @@ +/******************************************************************************** + * https://github.com/RoboticsBrno/SmartLeds + * + * MIT License + * + * Copyright (c) 2017 RoboticsBrno (RobotikaBrno) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *******************************************************************************/ + +#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 = uint16_t(_timing.T0H / RMT_NS_PER_TICK), + .level0 = 1, + .duration1 = uint16_t(_timing.T0L / RMT_NS_PER_TICK), + .level1 = 0, + }, + .bit1 = { + .duration0 = uint16_t(_timing.T1H / RMT_NS_PER_TICK), + .level0 = 1, + .duration1 = uint16_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 = (gpio_num_t)_pin, + .clk_src = RMT_CLK_SRC_DEFAULT, //.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/code/components/jomjol_controlGPIO/RmtDriver5.h b/code/components/jomjol_controlGPIO/RmtDriver5.h new file mode 100644 index 000000000..0a059fc09 --- /dev/null +++ b/code/components/jomjol_controlGPIO/RmtDriver5.h @@ -0,0 +1,91 @@ +/******************************************************************************** + * https://github.com/RoboticsBrno/SmartLeds + * + * MIT License + * + * Copyright (c) 2017 RoboticsBrno (RobotikaBrno) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *******************************************************************************/ + +#pragma once + +#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/code/components/jomjol_controlGPIO/SmartLeds.cpp b/code/components/jomjol_controlGPIO/SmartLeds.cpp index 58fb74c66..c2fc29c7c 100644 --- a/code/components/jomjol_controlGPIO/SmartLeds.cpp +++ b/code/components/jomjol_controlGPIO/SmartLeds.cpp @@ -1,90 +1,35 @@ -#include "SmartLeds.h" - - -/* PlatformIO 6 (ESP IDF 5) does no longer allow access to RMTMEM, - see https://docs.espressif.com/projects/esp-idf/en/latest/esp32/migration-guides/release-5.x/5.0/peripherals.html?highlight=rmtmem#id5 - As a dirty workaround, we copy the needed structures from rmt_struct.h - In the long run, this should be replaced! */ -typedef struct rmt_item32_s { - union { - struct { - uint32_t duration0 :15; - uint32_t level0 :1; - uint32_t duration1 :15; - uint32_t level1 :1; - }; - uint32_t val; - }; -} rmt_item32_t; - -//Allow access to RMT memory using RMTMEM.chan[0].data32[8] -typedef volatile struct rmt_mem_s { - struct { - rmt_item32_t data32[64]; - } chan[8]; -} rmt_mem_t; -extern rmt_mem_t RMTMEM; - +/******************************************************************************** + * https://github.com/RoboticsBrno/SmartLeds + * + * MIT License + * + * Copyright (c) 2017 RoboticsBrno (RobotikaBrno) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *******************************************************************************/ +#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 ); - 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::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; - } +SmartLed*& IRAM_ATTR SmartLed::ledForChannel(int channel) { + static SmartLed* table[detail::CHANNEL_COUNT] = {}; + assert(channel < detail::CHANNEL_COUNT); + return table[channel]; } diff --git a/code/components/jomjol_controlGPIO/SmartLeds.h b/code/components/jomjol_controlGPIO/SmartLeds.h index b779ac683..0ba97c4ed 100644 --- a/code/components/jomjol_controlGPIO/SmartLeds.h +++ b/code/components/jomjol_controlGPIO/SmartLeds.h @@ -1,7 +1,30 @@ -#pragma once +/******************************************************************************** + * https://github.com/RoboticsBrno/SmartLeds + * + * MIT License + * + * Copyright (c) 2017 RoboticsBrno (RobotikaBrno) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *******************************************************************************/ -#ifndef SMARTLEDS_H -#define SMARTLEDS_H +#pragma once /* * A C++ driver for the WS2812 LEDs using the RMT peripheral on the ESP32. @@ -31,305 +54,196 @@ * THE SOFTWARE. */ -#include #include #include +#include -#include "esp_idf_version.h" -#if (ESP_IDF_VERSION_MAJOR >= 5) -#include "soc/periph_defs.h" -#include "esp_private/periph_ctrl.h" -#include "soc/gpio_sig_map.h" -#include "soc/gpio_periph.h" -#include "soc/io_mux_reg.h" -#include "esp_rom_gpio.h" -#define gpio_pad_select_gpio esp_rom_gpio_pad_select_gpio -#define gpio_matrix_in(a,b,c) esp_rom_gpio_connect_in_signal(a,b,c) -#define gpio_matrix_out(a,b,c,d) esp_rom_gpio_connect_out_signal(a,b,c,d) -#define ets_delay_us(a) esp_rom_delay_us(a) -#endif - -#if defined ( ARDUINO ) - extern "C" { // ...someone forgot to put in the includes... - #include "esp32-hal.h" - #include "esp_intr_alloc.h" - #include "esp_ipc.h" - #include "driver/gpio.h" - #include "driver/periph_ctrl.h" - #include "freertos/semphr.h" - #include "soc/rmt_struct.h" - #include - #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 - -#if (ESP_IDF_VERSION_MAJOR >= 4) && (ESP_IDF_VERSION_MINOR > 1) -#include "hal/gpio_ll.h" -#else -#include "soc/gpio_periph.h" -#define esp_rom_delay_us ets_delay_us -static inline int gpio_ll_get_level(gpio_dev_t *hw, int gpio_num) -{ - if (gpio_num < 32) { - return (hw->in >> gpio_num) & 0x1; - } else { - return (hw->in1.data >> (gpio_num - 32)) & 0x1; - } -} -#endif - -#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)) -#if !(configENABLE_BACKWARD_COMPATIBILITY == 1) -#define xSemaphoreHandle SemaphoreHandle_t -#endif -#endif +#include +#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; -}; - -union RmtPulsePair { - struct { - int duration0:15; - int level0:1; - int duration1:15; - int level1:1; - }; - uint32_t value; -}; - -static const int DIVIDER = 4; // 8 still seems to work, but timings become marginal -static const int MAX_PULSES = 32; // A channel has a 64 "pulse" buffer - we use half per pass -static const double RMT_DURATION_NS = 12.5; // minimum time of a single RMT duration based on clock ns - -} // namespace detail +#include "RmtDriver.h" using LedType = detail::TimingParams; -static const LedType LED_WS2812 = { 350, 700, 800, 600, 50000 }; -static const LedType LED_WS2812B = { 400, 850, 850, 400, 50100 }; -static const LedType LED_SK6812 = { 300, 600, 900, 600, 80000 }; -static const LedType LED_WS2813 = { 350, 800, 350, 350, 300000 }; - +// Times are in nanoseconds, +// The RMT driver runs at 20MHz, so minimal representable time is 50 nanoseconds +static const LedType LED_WS2812 = { 350, 700, 800, 600, 50000 }; +// longer reset time because https://blog.adafruit.com/2017/05/03/psa-the-ws2812b-rgb-led-has-been-revised-will-require-code-tweak/ +static const LedType LED_WS2812B = { 400, 800, 850, 450, 300000 }; // universal +static const LedType LED_WS2812B_NEWVARIANT = { 200, 750, 750, 200, 300000 }; +static const LedType LED_WS2812B_OLDVARIANT = { 400, 800, 850, 450, 50000 }; +// This is timing from datasheet, but does not seem to actually work - try LED_WS2812B +static const LedType LED_WS2812C = { 250, 550, 550, 250, 280000 }; +static const LedType LED_SK6812 = { 300, 600, 900, 600, 80000 }; +static const LedType LED_WS2813 = { 350, 800, 350, 350, 300000 }; + +// Single buffer == can't touch the Rgbs between show() and wait() enum BufferType { SingleBuffer = 0, DoubleBuffer }; -enum IsrCore { CoreFirst = 0, CoreSecond = 1, CoreCurrent = 2}; +enum IsrCore { CoreFirst = 0, CoreSecond = 1, CoreCurrent = 2 }; class SmartLed { public: + friend class detail::RmtDriver; + // The RMT interrupt must not run on the same core as WiFi interrupts, otherwise SmartLeds // can't fill the RMT buffer fast enough, resulting in rendering artifacts. // Usually, that means you have to set isrCore == CoreSecond. // // If you use anything other than CoreCurrent, the FreeRTOS scheduler MUST be already running, // so you can't use it if you define SmartLed as global variable. - SmartLed( const LedType& type, int count, int pin, int channel = 0, BufferType doubleBuffer = SingleBuffer, IsrCore isrCore = CoreCurrent) - : _timing( type ), - _channel( channel ), - _count( count ), - _firstBuffer( new Rgb[ count ] ), - _secondBuffer( doubleBuffer ? new Rgb[ count ] : nullptr ), - _finishedFlag( xSemaphoreCreateBinary() ) - { - assert( channel >= 0 && channel < 8 ); - assert( ledForChannel( channel ) == nullptr ); - - xSemaphoreGive( _finishedFlag ); - - DPORT_SET_PERI_REG_MASK( DPORT_PERIP_CLK_EN_REG, DPORT_RMT_CLK_EN ); - DPORT_CLEAR_PERI_REG_MASK( DPORT_PERIP_RST_EN_REG, DPORT_RMT_RST ); - - PIN_FUNC_SELECT( GPIO_PIN_MUX_REG[ pin ], 2 ); - gpio_set_direction( static_cast< gpio_num_t >( pin ), GPIO_MODE_OUTPUT ); - gpio_matrix_out( static_cast< gpio_num_t >( pin ), RMT_SIG_OUT0_IDX + _channel, 0, 0 ); - initChannel( _channel ); - - RMT.tx_lim_ch[ _channel ].limit = detail::MAX_PULSES; - RMT.int_ena.val |= 1 << ( 24 + _channel ); - RMT.int_ena.val |= 1 << ( 3 * _channel ); - - _bitToRmt[ 0 ].level0 = 1; - _bitToRmt[ 0 ].level1 = 0; - _bitToRmt[ 0 ].duration0 = _timing.T0H / ( detail::RMT_DURATION_NS * detail::DIVIDER ); - _bitToRmt[ 0 ].duration1 = _timing.T0L / ( detail::RMT_DURATION_NS * detail::DIVIDER ); - - _bitToRmt[ 1 ].level0 = 1; - _bitToRmt[ 1 ].level1 = 0; - _bitToRmt[ 1 ].duration0 = _timing.T1H / ( detail::RMT_DURATION_NS * detail::DIVIDER ); - _bitToRmt[ 1 ].duration1 = _timing.T1L / ( detail::RMT_DURATION_NS * detail::DIVIDER ); - - if ( !anyAlive() ) { + // + // Does nothing on chips that only have one core. + SmartLed(const LedType& type, int count, int pin, int channel = 0, BufferType doubleBuffer = DoubleBuffer, + IsrCore isrCore = CoreCurrent) + : _finishedFlag(xSemaphoreCreateBinary()) + , _driver(type, count, pin, channel, _finishedFlag) + , _channel(channel) + , _count(count) + , _firstBuffer(new Rgb[count]) + , _secondBuffer(doubleBuffer ? new Rgb[count] : nullptr) { + assert(channel >= 0 && channel < detail::CHANNEL_COUNT); + assert(ledForChannel(channel) == nullptr); + + xSemaphoreGive(_finishedFlag); + + _driver.init(); + +#if !defined(SOC_CPU_CORES_NUM) || SOC_CPU_CORES_NUM > 1 + if (!anyAlive() && isrCore != CoreCurrent) { _interruptCore = isrCore; - if(isrCore != CoreCurrent) { - ESP_ERROR_CHECK(esp_ipc_call_blocking(isrCore, registerInterrupt, NULL)); - } else { - registerInterrupt(NULL); - } + ESP_ERROR_CHECK(esp_ipc_call_blocking(isrCore, registerInterrupt, (void*)this)); + } else +#endif + { + registerInterrupt((void*)this); } - ledForChannel( channel ) = this; + ledForChannel(channel) = this; } ~SmartLed() { - ledForChannel( _channel ) = nullptr; - if ( !anyAlive() ) { - if(_interruptCore != CoreCurrent) { - ESP_ERROR_CHECK(esp_ipc_call_blocking(_interruptCore, unregisterInterrupt, NULL)); - } else { - unregisterInterrupt(NULL); - } + ledForChannel(_channel) = nullptr; +#if !defined(SOC_CPU_CORES_NUM) || SOC_CPU_CORES_NUM > 1 + if (!anyAlive() && _interruptCore != CoreCurrent) { + ESP_ERROR_CHECK(esp_ipc_call_blocking(_interruptCore, unregisterInterrupt, (void*)this)); + } else +#endif + { + unregisterInterrupt((void*)this); } - vSemaphoreDelete( _finishedFlag ); + vSemaphoreDelete(_finishedFlag); } - Rgb& operator[]( int idx ) { - return _firstBuffer[ idx ]; - } + Rgb& operator[](int idx) { return _firstBuffer[idx]; } - const Rgb& operator[]( int idx ) const { - return _firstBuffer[ idx ]; - } + const Rgb& operator[](int idx) const { return _firstBuffer[idx]; } - void show() { - _buffer = _firstBuffer.get(); - startTransmission(); + esp_err_t show() { + esp_err_t err = startTransmission(); swapBuffers(); + return err; } - bool wait( TickType_t timeout = portMAX_DELAY ) { - if( xSemaphoreTake( _finishedFlag, timeout ) == pdTRUE ) { - xSemaphoreGive( _finishedFlag ); + bool wait(TickType_t timeout = portMAX_DELAY) { + if (xSemaphoreTake(_finishedFlag, timeout) == pdTRUE) { + xSemaphoreGive(_finishedFlag); return true; } return false; } - int size() const { - return _count; - } + int size() const { return _count; } + int channel() const { return _channel; } - Rgb *begin() { return _firstBuffer.get(); } - const Rgb *begin() const { return _firstBuffer.get(); } - const Rgb *cbegin() const { return _firstBuffer.get(); } + Rgb* begin() { return _firstBuffer.get(); } + const Rgb* begin() const { return _firstBuffer.get(); } + const Rgb* cbegin() const { return _firstBuffer.get(); } - Rgb *end() { return _firstBuffer.get() + _count; } - const Rgb *end() const { return _firstBuffer.get() + _count; } - const Rgb *cend() const { return _firstBuffer.get() + _count; } + Rgb* end() { return _firstBuffer.get() + _count; } + const Rgb* end() const { return _firstBuffer.get() + _count; } + const Rgb* cend() const { return _firstBuffer.get() + _count; } private: - static intr_handle_t _interruptHandle; static IsrCore _interruptCore; - static void initChannel( int channel ) { - RMT.apb_conf.fifo_mask = 1; //enable memory access, instead of FIFO mode. - RMT.apb_conf.mem_tx_wrap_en = 1; //wrap around when hitting end of buffer - RMT.conf_ch[ channel ].conf0.div_cnt = detail::DIVIDER; - RMT.conf_ch[ channel ].conf0.mem_size = 1; - RMT.conf_ch[ channel ].conf0.carrier_en = 0; - RMT.conf_ch[ channel ].conf0.carrier_out_lv = 1; - RMT.conf_ch[ channel ].conf0.mem_pd = 0; - - RMT.conf_ch[ channel ].conf1.rx_en = 0; - RMT.conf_ch[ channel ].conf1.mem_owner = 0; - RMT.conf_ch[ channel ].conf1.tx_conti_mode = 0; //loop back mode. - RMT.conf_ch[ channel ].conf1.ref_always_on = 1; // use apb clock: 80M - RMT.conf_ch[ channel ].conf1.idle_out_en = 1; - RMT.conf_ch[ channel ].conf1.idle_out_lv = 0; - } - - static void registerInterrupt(void *) { - ESP_ERROR_CHECK(esp_intr_alloc( ETS_RMT_INTR_SOURCE, 0, interruptHandler, nullptr, &_interruptHandle)); + static void registerInterrupt(void* selfVoid) { + auto* self = (SmartLed*)selfVoid; + ESP_ERROR_CHECK(self->_driver.registerIsr(!anyAlive())); } - static void unregisterInterrupt(void*) { - esp_intr_free( _interruptHandle ); + static void unregisterInterrupt(void* selfVoid) { + auto* self = (SmartLed*)selfVoid; + ESP_ERROR_CHECK(self->_driver.unregisterIsr()); } - static SmartLed*& IRAM_ATTR ledForChannel( int channel ); - static void IRAM_ATTR interruptHandler( void* ); + static SmartLed*& IRAM_ATTR ledForChannel(int channel); - void IRAM_ATTR copyRmtHalfBlock(); + static bool anyAlive() { + for (int i = 0; i != detail::CHANNEL_COUNT; i++) + if (ledForChannel(i) != nullptr) + return true; + return false; + } void swapBuffers() { - if ( _secondBuffer ) - _firstBuffer.swap( _secondBuffer ); + if (_secondBuffer) + _firstBuffer.swap(_secondBuffer); } - void startTransmission() { - // Invalid use of the library - if( xSemaphoreTake( _finishedFlag, 0 ) != pdTRUE ) + esp_err_t startTransmission() { + // Invalid use of the library, you must wait() fir previous frame to get processed first + if (xSemaphoreTake(_finishedFlag, 0) != pdTRUE) abort(); - _pixelPosition = _componentPosition = _halfIdx = 0; - copyRmtHalfBlock(); - if ( _pixelPosition < _count ) - copyRmtHalfBlock(); - - RMT.conf_ch[ _channel ].conf1.mem_rd_rst = 1; - RMT.conf_ch[ _channel ].conf1.tx_start = 1; - } + auto err = _driver.transmit(_firstBuffer.get()); + if (err != ESP_OK) { + return err; + } - static bool anyAlive() { - for ( int i = 0; i != 8; i++ ) - if ( ledForChannel( i ) != nullptr ) return true; - return false; + return ESP_OK; } - const LedType& _timing; + SemaphoreHandle_t _finishedFlag; + detail::RmtDriver _driver; int _channel; - detail::RmtPulsePair _bitToRmt[ 2 ]; int _count; - std::unique_ptr< Rgb[] > _firstBuffer; - std::unique_ptr< Rgb[] > _secondBuffer; - Rgb *_buffer; - - xSemaphoreHandle _finishedFlag; - - int _pixelPosition; - int _componentPosition; - int _halfIdx; + std::unique_ptr _firstBuffer; + std::unique_ptr _secondBuffer; }; +#if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3) +#define _SMARTLEDS_SPI_HOST SPI2_HOST +#define _SMARTLEDS_SPI_DMA_CHAN SPI_DMA_CH_AUTO +#else +#define _SMARTLEDS_SPI_HOST HSPI_HOST +#define _SMARTLEDS_SPI_DMA_CHAN 1 +#endif + class Apa102 { public: struct ApaRgb { - ApaRgb( uint8_t r = 0, uint8_t g = 0, uint32_t b = 0, uint32_t v = 0xFF ) - : v( 0xE0 | v ), b( b ), g( g ), r( r ) - {} + ApaRgb(uint8_t r = 0, uint8_t g = 0, uint32_t b = 0, uint32_t v = 0xFF) + : v(0xE0 | v) + , b(b) + , g(g) + , r(r) {} - ApaRgb& operator=( const Rgb& o ) { + ApaRgb& operator=(const Rgb& o) { r = o.r; g = o.g; b = o.b; return *this; } - ApaRgb& operator=( const Hsv& o ) { - *this = Rgb{ o }; + ApaRgb& operator=(const Hsv& o) { + *this = Rgb { o }; return *this; } @@ -339,14 +253,14 @@ 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, int clock_speed_hz = 1000000) + : _count(count) + , _firstBuffer(new ApaRgb[count]) + , _secondBuffer(doubleBuffer ? new ApaRgb[count] : nullptr) + , _transCount(0) + , _initFrame(0) { spi_bus_config_t buscfg; - memset( &buscfg, 0, sizeof( buscfg ) ); + memset(&buscfg, 0, sizeof(buscfg)); buscfg.mosi_io_num = datapin; buscfg.miso_io_num = -1; buscfg.sclk_io_num = clkpin; @@ -355,33 +269,29 @@ class Apa102 { buscfg.max_transfer_sz = 65535; spi_device_interface_config_t devcfg; - memset( &devcfg, 0, sizeof( devcfg ) ); - devcfg.clock_speed_hz = 1000000; + memset(&devcfg, 0, sizeof(devcfg)); + devcfg.clock_speed_hz = clock_speed_hz; devcfg.mode = 0; devcfg.spics_io_num = -1; devcfg.queue_size = TRANS_COUNT; devcfg.pre_cb = nullptr; - auto ret = spi_bus_initialize( HSPI_HOST, &buscfg, 1 ); - assert( ret == ESP_OK ); + auto ret = spi_bus_initialize(_SMARTLEDS_SPI_HOST, &buscfg, _SMARTLEDS_SPI_DMA_CHAN); + assert(ret == ESP_OK); - ret = spi_bus_add_device( HSPI_HOST, &devcfg, &_spi ); - assert( ret == ESP_OK ); + ret = spi_bus_add_device(_SMARTLEDS_SPI_HOST, &devcfg, &_spi); + assert(ret == ESP_OK); - std::fill_n( _finalFrame, FINAL_FRAME_SIZE, 0xFFFFFFFF ); + std::fill_n(_finalFrame, FINAL_FRAME_SIZE, 0xFFFFFFFF); } ~Apa102() { // ToDo } - ApaRgb& operator[]( int idx ) { - return _firstBuffer[ idx ]; - } + ApaRgb& operator[](int idx) { return _firstBuffer[idx]; } - const ApaRgb& operator[]( int idx ) const { - return _firstBuffer[ idx ]; - } + const ApaRgb& operator[](int idx) const { return _firstBuffer[idx]; } void show() { _buffer = _firstBuffer.get(); @@ -390,93 +300,95 @@ 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; @@ -485,33 +397,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( HSPI_HOST, &buscfg, 1 ); - assert( ret == ESP_OK ); + auto ret = spi_bus_initialize(_SMARTLEDS_SPI_HOST, &buscfg, _SMARTLEDS_SPI_DMA_CHAN); + assert(ret == ESP_OK); - ret = spi_bus_add_device( HSPI_HOST, &devcfg, &_spi ); - assert( ret == ESP_OK ); + ret = spi_bus_add_device(_SMARTLEDS_SPI_HOST, &devcfg, &_spi); + assert(ret == ESP_OK); - std::fill_n( _latchBuffer, LATCH_FRAME_SIZE_BYTES, 0x0 ); + std::fill_n(_latchBuffer, LATCH_FRAME_SIZE_BYTES, 0x0); } ~LDP8806() { // noop } - LDP8806_GRB& operator[]( int idx ) { - return _firstBuffer[ idx ]; - } + LDP8806_GRB& operator[](int idx) { return _firstBuffer[idx]; } - const LDP8806_GRB& operator[]( int idx ) const { - return _firstBuffer[ idx ]; - } + const LDP8806_GRB& operator[](int idx) const { return _firstBuffer[idx]; } void show() { _buffer = _firstBuffer.get(); @@ -520,51 +428,50 @@ 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]; }; - -#endif //SMARTLEDS_H