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..96199f3 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,31 @@ +name: PlatformIO CI + +on: [push] + +jobs: + build-test: + runs-on: ubuntu-22.04 + strategy: + fail-fast: false + matrix: + example: + - examples/basic + conf: + - esp32-idf4-arduino.ini + - 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 + 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 1e1104a..e2daf3a 100644 --- a/README.md +++ b/README.md @@ -2,13 +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) +- 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. @@ -27,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/library.json b/library.json index e8bbadd..3bb486c 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": "1.2.1", - "frameworks": ["arduino","espidf"], - "platforms": "espressif32" + "name": "Jan Mrázek", + "email": "email@honzamrazek.cz", + "url": "http://www.robotikabrno.cz", + "maintainer": true + } + ], + "version": "3.1.5", + "frameworks": ["arduino", "espidf"], + "platforms": "espressif32" } diff --git a/library.properties b/library.properties index cb36524..9d6f707 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=SmartLeds -version=1.2.1 +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/Color.cpp b/src/Color.cpp index 94d61bd..9ab4fcc 100644 --- a/src/Color.cpp +++ b/src/Color.cpp @@ -1,18 +1,38 @@ #include "Color.h" #include +#include #include namespace { // Int -> fixed point -int up( int x ) { return x * 255; } +int up(int x) { return x * 255; } } // namespace -Rgb::Rgb( Hsv y ) { +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(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; } @@ -22,77 +42,120 @@ Rgb::Rgb( Hsv y ) { 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 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; } -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::operator-(const Rgb& in) const { + auto copy = *this; + copy -= in; + return copy; } -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 ) ); +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); + 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(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/src/Color.h b/src/Color.h index 368d2ea..2baac12 100644 --- a/src/Color.h +++ b/src/Color.h @@ -1,55 +1,92 @@ #pragma once -#include #include "esp_attr.h" +#include union Hsv; union Rgb { - struct __attribute__ ((packed)) { - uint8_t r, g, b, _extra; + 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 ) : r( r ), g( g ), b( b ), _extra( 0 ) {} - Rgb( Hsv c ); - Rgb& operator=( Rgb rgb ) { swap( rgb ); return *this; } - Rgb& operator=( Hsv hsv ); - Rgb operator+( Rgb in ) const; - Rgb& operator+=( 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 = ( 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 ); - - 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) { + /* 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(4 + channel); } }; union Hsv { - struct __attribute__ ((packed)) { - uint8_t h, s, v, _extra; + struct __attribute__((packed)) { + 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( Rgb r ); - Hsv& operator=( Hsv h ) { swap( h ); return *this; } - Hsv& operator=( Rgb rgb ); - 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; } }; 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..9827d98 --- /dev/null +++ b/src/RmtDriver4.cpp @@ -0,0 +1,117 @@ +#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/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..52ec2c1 --- /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 = 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_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 6a4bc1c..601f427 100644 --- a/src/SmartLeds.cpp +++ b/src/SmartLeds.cpp @@ -1,60 +1,9 @@ #include "SmartLeds.h" +IsrCore SmartLed::_interruptCore = CoreCurrent; + SmartLed*& IRAM_ATTR SmartLed::ledForChannel(int channel) { - static SmartLed* table[8] = { nullptr }; - assert(channel < 8); + static SmartLed* table[detail::CHANNEL_COUNT] = {}; + assert(channel < detail::CHANNEL_COUNT); 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; - } -} diff --git a/src/SmartLeds.h b/src/SmartLeds.h index 7404a37..cfef25b 100644 --- a/src/SmartLeds.h +++ b/src/SmartLeds.h @@ -28,234 +28,197 @@ * THE SOFTWARE. */ -#include #include #include +#include -#if defined ( ARDUINO ) - extern "C" { // ...someone forgot to put in the includes... - #include "esp32-hal.h" - #include "esp_intr_alloc.h" - #include "driver/gpio.h" - #include "driver/periph_ctrl.h" - #include "freertos/semphr.h" - #include "soc/rmt_struct.h" - #include - } -#elif defined ( ESP_PLATFORM ) - extern "C" { // ...someone forgot to put in the includes... - #include - #include - #include - #include - #include - #include - #include - #include - } - #include -#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 }; + class SmartLed { public: - SmartLed( const LedType& type, int count, int pin, int channel = 0, BufferType doubleBuffer = SingleBuffer ) - : _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 ); + friend class detail::RmtDriver; + + // The RMT interrupt must not run on the same core as WiFi interrupts, otherwise SmartLeds + // can't fill the RMT buffer fast enough, resulting in rendering artifacts. + // Usually, that means you have to set isrCore == CoreSecond. + // + // If you use anything other than CoreCurrent, the FreeRTOS scheduler MUST be already running, + // so you can't use it if you define SmartLed as global variable. + // + // Does nothing on chips that only have one core. + SmartLed(const LedType& type, int count, int pin, int channel = 0, BufferType doubleBuffer = DoubleBuffer, + IsrCore isrCore = CoreCurrent) + : _finishedFlag(xSemaphoreCreateBinary()) + , _driver(type, count, pin, channel, _finishedFlag) + , _channel(channel) + , _count(count) + , _firstBuffer(new Rgb[count]) + , _secondBuffer(doubleBuffer ? new Rgb[count] : nullptr) { + assert(channel >= 0 && channel < detail::CHANNEL_COUNT); + assert(ledForChannel(channel) == nullptr); xSemaphoreGive(_finishedFlag); - 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 ); + _driver.init(); - _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() ) - esp_intr_alloc( ETS_RMT_INTR_SOURCE, 0, interruptHandler, nullptr, &_interruptHandle ); +#if !defined(SOC_CPU_CORES_NUM) || SOC_CPU_CORES_NUM > 1 + if (!anyAlive() && isrCore != CoreCurrent) { + _interruptCore = isrCore; + ESP_ERROR_CHECK(esp_ipc_call_blocking(isrCore, registerInterrupt, (void*)this)); + } else +#endif + { + registerInterrupt((void*)this); + } - ledForChannel( channel ) = this; + ledForChannel(channel) = this; } ~SmartLed() { - ledForChannel( _channel ) = nullptr; - if ( !anyAlive() ) - esp_intr_free( _interruptHandle ); - vSemaphoreDelete( _finishedFlag ); + ledForChannel(_channel) = nullptr; +#if !defined(SOC_CPU_CORES_NUM) || SOC_CPU_CORES_NUM > 1 + if (!anyAlive() && _interruptCore != CoreCurrent) { + ESP_ERROR_CHECK(esp_ipc_call_blocking(_interruptCore, unregisterInterrupt, (void*)this)); + } else +#endif + { + unregisterInterrupt((void*)this); + } + vSemaphoreDelete(_finishedFlag); } - Rgb& operator[]( int idx ) { - return _firstBuffer[ idx ]; - } + 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 ); + 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 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 IsrCore _interruptCore; + + static void registerInterrupt(void* selfVoid) { + auto* self = (SmartLed*)selfVoid; + ESP_ERROR_CHECK(self->_driver.registerIsr(!anyAlive())); + } + + static void unregisterInterrupt(void* selfVoid) { + auto* self = (SmartLed*)selfVoid; + ESP_ERROR_CHECK(self->_driver.unregisterIsr()); } static SmartLed*& IRAM_ATTR ledForChannel(int channel); - static void IRAM_ATTR interruptHandler(void*); - 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; - intr_handle_t _interruptHandle; - - 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 + +// Can also handle SK9822 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; } @@ -265,14 +228,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; @@ -281,33 +244,29 @@ class Apa102 { buscfg.max_transfer_sz = 65535; spi_device_interface_config_t devcfg; - memset( &devcfg, 0, sizeof( devcfg ) ); - devcfg.clock_speed_hz = 1000000; - devcfg.mode=0; + 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, 0); } ~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(); @@ -316,51 +275,180 @@ 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 ); + // 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++; } } 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& operator=(const Rgb& o) { + //Convert 8->7bit colour + r = (o.r * 127 / 256) | 0x80; + g = (o.g * 127 / 256) | 0x80; + b = (o.b * 127 / 256) | 0x80; + return *this; + } + + LDP8806_GRB& operator=(const Hsv& o) { + *this = Rgb { o }; + return *this; + } + + uint8_t g, r, b; + }; + + static const int LED_FRAME_SIZE_BYTES = sizeof(LDP8806_GRB); + static const int LATCH_FRAME_SIZE_BYTES = 3; + static const int TRANS_COUNT_MAX = 20; //Arbitrary, supports up to 600 LED + + LDP8806( + int count, int clkpin, int datapin, BufferType doubleBuffer = SingleBuffer, uint32_t clock_speed_hz = 2000000) + : _count(count) + , _firstBuffer(new LDP8806_GRB[count]) + , _secondBuffer(doubleBuffer ? new LDP8806_GRB[count] : nullptr) + , + // one 'latch'/start-of-data mark frame for every 32 leds + _latchFrames((count + 31) / 32) { + spi_bus_config_t buscfg; + memset(&buscfg, 0, sizeof(buscfg)); + buscfg.mosi_io_num = datapin; + buscfg.miso_io_num = -1; + buscfg.sclk_io_num = clkpin; + buscfg.quadwp_io_num = -1; + buscfg.quadhd_io_num = -1; + buscfg.max_transfer_sz = 65535; + + spi_device_interface_config_t devcfg; + memset(&devcfg, 0, sizeof(devcfg)); + devcfg.clock_speed_hz = clock_speed_hz; + devcfg.mode = 0; + devcfg.spics_io_num = -1; + devcfg.queue_size = TRANS_COUNT_MAX; + devcfg.pre_cb = nullptr; + + auto ret = spi_bus_initialize(_SMARTLEDS_SPI_HOST, &buscfg, _SMARTLEDS_SPI_DMA_CHAN); + assert(ret == ESP_OK); + + ret = spi_bus_add_device(_SMARTLEDS_SPI_HOST, &devcfg, &_spi); + assert(ret == ESP_OK); + + std::fill_n(_latchBuffer, LATCH_FRAME_SIZE_BYTES, 0x0); + } + + ~LDP8806() { + // noop + } + + LDP8806_GRB& operator[](int idx) { return _firstBuffer[idx]; } + + const LDP8806_GRB& operator[](int idx) const { return _firstBuffer[idx]; } + + void show() { + _buffer = _firstBuffer.get(); + startTransmission(); + swapBuffers(); + } + + void wait() { + while (_transCount--) { + spi_transaction_t* t; + spi_device_get_trans_result(_spi, &t, portMAX_DELAY); + } + } + +private: + void swapBuffers() { + if (_secondBuffer) + _firstBuffer.swap(_secondBuffer); + } + + void startTransmission() { + _transCount = 0; + for (int i = 0; i != TRANS_COUNT_MAX; i++) { + _transactions[i].cmd = 0; + _transactions[i].addr = 0; + _transactions[i].flags = 0; + _transactions[i].rxlength = 0; + _transactions[i].rx_buffer = nullptr; + } + // LED Data + _transactions[0].length = (LED_FRAME_SIZE_BYTES * 8) * _count; + _transactions[0].tx_buffer = _buffer; + spi_device_queue_trans(_spi, _transactions + _transCount, portMAX_DELAY); + _transCount++; + + // 'latch'/start-of-data marker frames + for (int i = 0; i < _latchFrames; i++) { + _transactions[_transCount].length = (LATCH_FRAME_SIZE_BYTES * 8); + _transactions[_transCount].tx_buffer = _latchBuffer; + spi_device_queue_trans(_spi, _transactions + _transCount, portMAX_DELAY); + _transCount++; + } + } + + spi_device_handle_t _spi; + int _count; + std::unique_ptr _firstBuffer, _secondBuffer; + LDP8806_GRB* _buffer; + + spi_transaction_t _transactions[TRANS_COUNT_MAX]; + int _transCount; + + int _latchFrames; + 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/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 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