From 4b96e0d66687a47928b886181ee063e4b1125b6e Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Sun, 18 Aug 2024 11:44:48 -0700 Subject: [PATCH] Esp32 s3 LCD Parallel (#824) Co-authored-by: Colin Iuliano Co-authored-by: Colin --- .../NeoPixel_ESP32_I2sParallel.ino | 7 +- .../NeoPixel_ESP32_LcdParallel.ino | 55 ++ src/internal/NeoMethods.h | 1 + src/internal/methods/Esp32_i2s.c | 127 +-- src/internal/methods/Esp32_i2s.h | 14 +- src/internal/methods/FractionClk.c | 157 ++++ src/internal/methods/FractionClk.h | 23 + src/internal/methods/NeoEsp32I2sMethod.h | 4 + src/internal/methods/NeoEsp32I2sXMethod.h | 28 +- src/internal/methods/NeoEsp32LcdXMethod.h | 837 ++++++++++++++++++ 10 files changed, 1106 insertions(+), 147 deletions(-) create mode 100644 examples/ESP32/NeoPixel_ESP32_LcdParallel/NeoPixel_ESP32_LcdParallel.ino create mode 100644 src/internal/methods/FractionClk.c create mode 100644 src/internal/methods/FractionClk.h create mode 100644 src/internal/methods/NeoEsp32LcdXMethod.h diff --git a/examples/ESP32/NeoPixel_ESP32_I2sParallel/NeoPixel_ESP32_I2sParallel.ino b/examples/ESP32/NeoPixel_ESP32_I2sParallel/NeoPixel_ESP32_I2sParallel.ino index 72d5582..218032a 100644 --- a/examples/ESP32/NeoPixel_ESP32_I2sParallel/NeoPixel_ESP32_I2sParallel.ino +++ b/examples/ESP32/NeoPixel_ESP32_I2sParallel/NeoPixel_ESP32_I2sParallel.ino @@ -1,14 +1,15 @@ // // NeoPixel_ESP32_I2sParallel - // This sketch demonstrates the use of the I2S Parallel method allowing upto 8 hardware updated channels -// This example only works on the ESP32 +// This example only works on the ESP32 (C2,S2) // // The key part of the method name is Esp32I2s1X8, // E2p32 (platform specific method), -// I2s Channel 1 (most commonly available), -// X8 (8 parallel channel mode) +// I2s peripheral channel 1 (most commonly available), +// X8 (8 parallel channel mode, x16 also available) // // In this example, it demonstrates different ColorFeatures, Method specification, and count per strip +// Note, the first instance of a NeoPixelBus will set the overall timing of all other instances // #include diff --git a/examples/ESP32/NeoPixel_ESP32_LcdParallel/NeoPixel_ESP32_LcdParallel.ino b/examples/ESP32/NeoPixel_ESP32_LcdParallel/NeoPixel_ESP32_LcdParallel.ino new file mode 100644 index 0000000..eb9654b --- /dev/null +++ b/examples/ESP32/NeoPixel_ESP32_LcdParallel/NeoPixel_ESP32_LcdParallel.ino @@ -0,0 +1,55 @@ +// +// NeoPixel_ESP32_LcdParallel - +// This sketch demonstrates the use of the LCD Parallel method allowing upto 8 or 16 hardware updated channels +// This example only works on the ESP32S3 +// +// The key part of the method name is Esp32LcdX8, +// E2p32 (platform specific method), +// Lcd peripheral, +// X8 (8 parallel channel mode, x16 is also supported) +// +// In this example, it demonstrates different ColorFeatures, Method specification, and count per strip +// Note, the first instance of a NeoPixelBus will set the overall timing of all other instances +// +#include + +// Demonstrating the use of the first four channels, but the method used allows for eight +NeoPixelBus strip1(120, 15); // note: older WS2811 and longer strip +NeoPixelBus strip2(100, 2); // note: modern WS2812 with letter like WS2812b +NeoPixelBus strip3(100, 4); // note: inverted +NeoPixelBus strip4(50, 16); // note: RGBW and Sk6812 and smaller strip + +void setup() { + Serial.begin(115200); + while (!Serial); // wait for serial attach + + Serial.println(); + Serial.println("Initializing..."); + Serial.flush(); + + // must call begin on all the strips + strip1.Begin(); + strip2.Begin(); + strip3.Begin(); + strip4.Begin(); + + Serial.println(); + Serial.println("Running..."); +} + +void loop() { + delay(1000); + + // draw on the strips + strip1.SetPixelColor(0, RgbColor(255, 0, 0)); // red + strip2.SetPixelColor(0, RgbColor(0, 127, 0)); // green + strip3.SetPixelColor(0, RgbColor(0, 0, 53)); // blue + strip4.SetPixelColor(0, RgbwColor(0, 0, 128, 255)); // white channel with a little blue + + // show them, + // only on the last show, no matter the order, will the data be sent + strip1.Show(); + strip2.Show(); + strip3.Show(); + strip4.Show(); +} \ No newline at end of file diff --git a/src/internal/NeoMethods.h b/src/internal/NeoMethods.h index 9c53586..1be92d0 100644 --- a/src/internal/NeoMethods.h +++ b/src/internal/NeoMethods.h @@ -59,6 +59,7 @@ License along with NeoPixel. If not, see #include "methods/NeoEsp32RmtMethod.h" #include "methods/DotStarEsp32DmaSpiMethod.h" #include "methods/NeoEsp32I2sXMethod.h" +#include "methods/NeoEsp32LcdXMethod.h" #endif diff --git a/src/internal/methods/Esp32_i2s.c b/src/internal/methods/Esp32_i2s.c index d132385..ade1077 100644 --- a/src/internal/methods/Esp32_i2s.c +++ b/src/internal/methods/Esp32_i2s.c @@ -18,18 +18,18 @@ #include "sdkconfig.h" // this sets useful config symbols, like CONFIG_IDF_TARGET_ESP32C3 -// ESP32 C3, S3, C6, and H2 I2S is not supported yet due to significant changes to interface -#if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) && !defined(CONFIG_IDF_TARGET_ESP32C6) && !defined(CONFIG_IDF_TARGET_ESP32H2) - #include #include #include "stdlib.h" +// ESP32 C3, S3, C6, and H2 I2S is not supported yet due to significant changes to interface +#if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) && !defined(CONFIG_IDF_TARGET_ESP32C6) && !defined(CONFIG_IDF_TARGET_ESP32H2) + #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" #include "freertos/queue.h" - +#include "FractionClk.h" #if ESP_IDF_VERSION_MAJOR>=4 #include "esp_intr_alloc.h" @@ -601,123 +601,6 @@ void i2sDeinit(uint8_t bus_num) i2sDeinitDmaItems(bus_num); } -void i2sUnitDecimalToFractionClks(uint8_t* resultN, - uint8_t* resultD, - double unitDecimal, - double accuracy) -{ - if (unitDecimal <= accuracy) - { - // no fractional - *resultN = 0; - *resultD = 1; - return; - } - else if (unitDecimal <= (1.0 / 63.0)) - { - // lowest fractional - *resultN = 0; - *resultD = 2; - return; - } - else if (unitDecimal >= (62.0 / 63.0)) - { - // highest fractional - *resultN = 2; - *resultD = 2; - return; - } - -// printf("\nSearching for %f\n", unitDecimal); - - // The lower fraction is 0 / 1 - uint16_t lowerN = 0; - uint16_t lowerD = 1; - double lowerDelta = unitDecimal; - - // The upper fraction is 1 / 1 - uint16_t upperN = 1; - uint16_t upperD = 1; - double upperDelta = 1.0 - unitDecimal; - - uint16_t closestN = 0; - uint16_t closestD = 1; - double closestDelta = lowerDelta; - - for (;;) - { - // The middle fraction is - // (lowerN + upperN) / (lowerD + upperD) - uint16_t middleN = lowerN + upperN; - uint16_t middleD = lowerD + upperD; - double middleUnit = (double)middleN / middleD; - - if (middleD > 63) - { - // exceeded our clock bits so break out - // and use closest we found so far - break; - } - - if (middleD * (unitDecimal + accuracy) < middleN) - { - // middle is our new upper - upperN = middleN; - upperD = middleD; - upperDelta = middleUnit - unitDecimal; - } - else if (middleN < (unitDecimal - accuracy) * middleD) - { - // middle is our new lower - lowerN = middleN; - lowerD = middleD; - lowerDelta = unitDecimal - middleUnit; - } - else - { - // middle is our best fraction - *resultN = middleN; - *resultD = middleD; - -// printf(" Match %d/%d = %f (%f)\n", middleN, middleD, middleUnit, unitDecimal - middleUnit); - return; - } - - // track the closest fraction so far (ONLY THE UPPER, so allow only slower Kbps) - // - //if (upperDelta < lowerDelta) - { - if (upperDelta < closestDelta) - { - closestN = upperN; - closestD = upperD; - closestDelta = upperDelta; - -// printf(" Upper %d/%d = %f (%f)\n", closestN, closestD, middleUnit, closestDelta); - } - } - /* - else - { - if (lowerDelta < closestDelta) - { - closestN = lowerN; - closestD = lowerD; - closestDelta = lowerDelta; - - printf(" Lower %d/%d = %f (%f)\n", closestN, closestD, middleUnit, closestDelta); - } - } - */ - } - -// printf(" Closest %d/%d = %f (%f)\n\n", closestN, closestD, (double)closestN / closestD, closestDelta); - // no perfect match, use the closest we found - // - *resultN = closestN; - *resultD = closestD; -} - esp_err_t i2sSetSampleRate(uint8_t bus_num, uint32_t rate, bool parallel_mode, @@ -764,7 +647,7 @@ esp_err_t i2sSetSampleRate(uint8_t bus_num, uint8_t divB = 0; uint8_t divA = 0; - i2sUnitDecimalToFractionClks(&divB, &divA, clkmFraction, 0.000001); + UnitDecimalToFractionClks(&divB, &divA, clkmFraction, 0.000001); i2sSetClock(bus_num, clkmInteger, diff --git a/src/internal/methods/Esp32_i2s.h b/src/internal/methods/Esp32_i2s.h index 8de14ec..23d1ac4 100644 --- a/src/internal/methods/Esp32_i2s.h +++ b/src/internal/methods/Esp32_i2s.h @@ -1,12 +1,17 @@ #pragma once +#if defined(ARDUINO_ARCH_ESP32) + + + // ESP32C3/S3 I2S is not supported yet due to significant changes to interface -#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) +#if !defined(CONFIG_IDF_TARGET_ESP32S3) && !defined(CONFIG_IDF_TARGET_ESP32C3) #ifdef __cplusplus extern "C" { #endif + #include "esp_err.h" #define I2S_DMA_MAX_DATA_LEN 4092// maximum bytes in one dma item @@ -52,10 +57,7 @@ bool i2sWriteDone(uint8_t bus_num); bool i2sDump(uint8_t bus_num); bool i2sGetClks(uint8_t bus_num, uint8_t* clkm_div_num, uint8_t* clkm_div_b, uint8_t* clkm_div_a ); -void i2sUnitDecimalToFractionClks(uint8_t* resultN, - uint8_t* resultD, - double unitDecimal, - double accuracy); + #endif #ifdef __cplusplus @@ -63,3 +65,5 @@ void i2sUnitDecimalToFractionClks(uint8_t* resultN, #endif #endif + +#endif diff --git a/src/internal/methods/FractionClk.c b/src/internal/methods/FractionClk.c new file mode 100644 index 0000000..622f660 --- /dev/null +++ b/src/internal/methods/FractionClk.c @@ -0,0 +1,157 @@ +/*------------------------------------------------------------------------- +NeoPixel library helper functions. + +Written by Michael C. Miller. + +I invest time and resources providing this open source code, +please support me by dontating (see https://github.com/Makuna/NeoPixelBus) + +------------------------------------------------------------------------- +This file is part of the Makuna/NeoPixelBus library. + +NeoPixelBus is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 3 of +the License, or (at your option) any later version. + +NeoPixelBus is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with NeoPixel. If not, see +. +-------------------------------------------------------------------------*/ + +#include +#include +#include "stdlib.h" + +// UnitDecimalToFractionClks +// Given unit decimal (floating point value less than 1.0), +// return a fraction numerator and denomenator that closesly matches that +// +// resultN - the address of the variable to place the numerator result +// resultD - the address of the variable to place the denomenator result +// unitDecimal - the decimal value that is less than 1.0 and greater than/eaual 0.0 +// accuracy - the accuracy needed to match for a fractionals (0.000001 is a good start) +// +void UnitDecimalToFractionClks(uint8_t* resultN, + uint8_t* resultD, + double unitDecimal, + double accuracy) +{ + if (unitDecimal <= accuracy) + { + // no fractional + *resultN = 0; + *resultD = 1; + return; + } + else if (unitDecimal <= (1.0 / 63.0)) + { + // lowest fractional + *resultN = 0; + *resultD = 2; + return; + } + else if (unitDecimal >= (62.0 / 63.0)) + { + // highest fractional + *resultN = 2; + *resultD = 2; + return; + } + + // printf("\nSearching for %f\n", unitDecimal); + + // The lower fraction is 0 / 1 + uint16_t lowerN = 0; + uint16_t lowerD = 1; + double lowerDelta = unitDecimal; + + // The upper fraction is 1 / 1 + uint16_t upperN = 1; + uint16_t upperD = 1; + double upperDelta = 1.0 - unitDecimal; + + uint16_t closestN = 0; + uint16_t closestD = 1; + double closestDelta = lowerDelta; + + for (;;) + { + // The middle fraction is + // (lowerN + upperN) / (lowerD + upperD) + uint16_t middleN = lowerN + upperN; + uint16_t middleD = lowerD + upperD; + double middleUnit = (double)middleN / middleD; + + if (middleD > 63) + { + // exceeded our clock bits so break out + // and use closest we found so far + break; + } + + if (middleD * (unitDecimal + accuracy) < middleN) + { + // middle is our new upper + upperN = middleN; + upperD = middleD; + upperDelta = middleUnit - unitDecimal; + } + else if (middleN < (unitDecimal - accuracy) * middleD) + { + // middle is our new lower + lowerN = middleN; + lowerD = middleD; + lowerDelta = unitDecimal - middleUnit; + } + else + { + // middle is our best fraction + *resultN = middleN; + *resultD = middleD; + + // printf(" Match %d/%d = %f (%f)\n", middleN, middleD, middleUnit, unitDecimal - middleUnit); + return; + } + + // track the closest fraction so far (ONLY THE UPPER, so allow only slower Kbps) + // + //if (upperDelta < lowerDelta) + { + if (upperDelta < closestDelta) + { + closestN = upperN; + closestD = upperD; + closestDelta = upperDelta; + + // printf(" Upper %d/%d = %f (%f)\n", closestN, closestD, middleUnit, closestDelta); + } + } + /* + else + { + if (lowerDelta < closestDelta) + { + closestN = lowerN; + closestD = lowerD; + closestDelta = lowerDelta; + + printf(" Lower %d/%d = %f (%f)\n", closestN, closestD, middleUnit, closestDelta); + } + } + */ + } + + + // printf(" Closest %d/%d = %f (%f)\n\n", closestN, closestD, (double)closestN / closestD, closestDelta); + // no perfect match, use the closest we found + // + *resultN = closestN; + *resultD = closestD; +} + diff --git a/src/internal/methods/FractionClk.h b/src/internal/methods/FractionClk.h new file mode 100644 index 0000000..a83709c --- /dev/null +++ b/src/internal/methods/FractionClk.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include +#include "stdlib.h" + +// UnitDecimalToFractionClks is used inside both c and c++ files, +// so to make sure there are no duplicate definitions in c and c++ calling conventions +// we wrap it up +// +#ifdef __cplusplus +extern "C" { +#endif + +void UnitDecimalToFractionClks(uint8_t* resultN, + uint8_t* resultD, + double unitDecimal, + double accuracy); + +#ifdef __cplusplus +} +#endif + diff --git a/src/internal/methods/NeoEsp32I2sMethod.h b/src/internal/methods/NeoEsp32I2sMethod.h index 9db245c..01c5141 100644 --- a/src/internal/methods/NeoEsp32I2sMethod.h +++ b/src/internal/methods/NeoEsp32I2sMethod.h @@ -38,6 +38,7 @@ extern "C" const uint16_t c_dmaBytesPerPixelBytes = 4; +// -------------------------------------------------------- class NeoEsp32I2sSpeedWs2812x { public: @@ -110,6 +111,7 @@ public: const static uint16_t ResetTimeUs = 50; }; +// -------------------------------------------------------- class NeoEsp32I2sBusZero { public: @@ -139,6 +141,7 @@ public: const uint8_t I2sBusNumber; }; +// -------------------------------------------------------- class NeoEsp32I2sNotInverted { public: @@ -151,6 +154,7 @@ public: const static bool Inverted = true; }; +// -------------------------------------------------------- template class NeoEsp32I2sMethodBase { public: diff --git a/src/internal/methods/NeoEsp32I2sXMethod.h b/src/internal/methods/NeoEsp32I2sXMethod.h index e6960b2..458a3e1 100644 --- a/src/internal/methods/NeoEsp32I2sXMethod.h +++ b/src/internal/methods/NeoEsp32I2sXMethod.h @@ -57,6 +57,7 @@ public: NeoEspI2sMuxBusSize8Bit() {}; const static size_t MuxBusDataSize = 1; + const static size_t DmaBitsPerPixelBit = 4; // 4 step cadence, matches endcoding static void EncodeIntoDma(uint8_t* dmaBuffer, const uint8_t* data, size_t sizeData, uint8_t muxId) { @@ -86,6 +87,8 @@ public: uint32_t* pDma = reinterpret_cast(dmaBuffer); const uint8_t* pEnd = data + sizeData; + const uint32_t OneBit = EncodedOneBit << muxId; + const uint32_t ZeroBit = EncodedZeroBit << muxId; for (const uint8_t* pPixel = data; pPixel < pEnd; pPixel++) { @@ -95,7 +98,7 @@ public: { uint32_t dma = *(pDma); - dma |= (((value & 0x80) ? EncodedOneBit : EncodedZeroBit) << (muxId)); + dma |= (value & 0x80) ? OneBit : ZeroBit; *(pDma++) = dma; value <<= 1; } @@ -112,6 +115,7 @@ public: NeoEspI2sMuxBusSize16Bit() {}; const static size_t MuxBusDataSize = 2; + const static size_t DmaBitsPerPixelBit = 4; // 4 step cadence, matches endcoding static void EncodeIntoDma(uint8_t* dmaBuffer, const uint8_t* data, size_t sizeData, uint8_t muxId) { @@ -186,6 +190,8 @@ protected: { uint64_t* pDma64 = reinterpret_cast(dmaBuffer); const uint8_t* pEnd = data + sizeData; + const uint64_t OneBit = EncodedOneBit64 << muxShift; + const uint64_t ZeroBit = EncodedZeroBit64 << muxShift; for (const uint8_t* pPixel = data; pPixel < pEnd; pPixel++) { @@ -195,7 +201,7 @@ protected: { uint64_t dma64 = *(pDma64); - dma64 |= (((value & 0x80) ? EncodedOneBit64 : EncodedZeroBit64) << (muxShift)); + dma64 |= (value & 0x80) ? OneBit : ZeroBit; *(pDma64++) = dma64; value <<= 1; } @@ -226,11 +232,7 @@ public: // so its not useful to have or rely on, // but without it presence they get zeroed far too late NeoEspI2sMuxMap() - // //: - // //MaxBusDataSize(0), - // //UpdateMap(0), - // //UpdateMapMask(0), - // //BusCount(0) + //: { } @@ -319,8 +321,6 @@ template class NeoEspI2sMonoBuffContext { public: - const static size_t DmaBitsPerPixelBit = 4; - size_t I2sBufferSize; // total size of I2sBuffer uint8_t* I2sBuffer; // holds the DMA buffer that is referenced by I2sBufDesc T_MUXMAP MuxMap; @@ -331,10 +331,6 @@ public: // but without it presence they get zeroed far too late NeoEspI2sMonoBuffContext() //: - //I2sBufferSize(0), - //I2sBuffer(nullptr), - //I2sEditBuffer(nullptr), - //MuxMap() { } @@ -346,7 +342,7 @@ public: // MuxMap.MaxBusDataSize = max size in bytes of a single channel // DmaBitsPerPixelBit = how many dma bits/byte are needed for each source (pixel) bit/byte // T_MUXMAP::MuxBusDataSize = the true size of data for selected mux mode (not exposed size as i2s0 only supports 16bit mode) - I2sBufferSize = MuxMap.MaxBusDataSize * 8 * DmaBitsPerPixelBit * T_MUXMAP::MuxBusDataSize; + I2sBufferSize = MuxMap.MaxBusDataSize * 8 * T_MUXMAP::DmaBitsPerPixelBit * T_MUXMAP::MuxBusDataSize; // must have a 4 byte aligned buffer for i2s uint32_t alignment = I2sBufferSize % 4; @@ -455,8 +451,6 @@ template class NeoEspI2sDblBuffContext { public: - const static size_t DmaBitsPerPixelBit = 4; - size_t I2sBufferSize; // total size of I2sBuffer uint8_t* I2sBuffer; // holds the DMA buffer that is referenced by I2sBufDesc uint8_t* I2sEditBuffer; // hold a editable buffer that is copied to I2sBuffer @@ -483,7 +477,7 @@ public: // MuxMap.MaxBusDataSize = max size in bytes of a single channel // DmaBitsPerPixelBit = how many dma bits/byte are needed for each source (pixel) bit/byte // T_MUXMAP::MuxBusDataSize = the true size of data for selected mux mode (not exposed size as i2s0 only supports 16bit mode) - I2sBufferSize = MuxMap.MaxBusDataSize * 8 * DmaBitsPerPixelBit * T_MUXMAP::MuxBusDataSize; + I2sBufferSize = MuxMap.MaxBusDataSize * 8 * T_MUXMAP::DmaBitsPerPixelBit * T_MUXMAP::MuxBusDataSize; // must have a 4 byte aligned buffer for i2s uint32_t alignment = I2sBufferSize % 4; diff --git a/src/internal/methods/NeoEsp32LcdXMethod.h b/src/internal/methods/NeoEsp32LcdXMethod.h new file mode 100644 index 0000000..21e3815 --- /dev/null +++ b/src/internal/methods/NeoEsp32LcdXMethod.h @@ -0,0 +1,837 @@ +#pragma once + +/*------------------------------------------------------------------------- +NeoPixel library helper functions for Esp32. + +Written by Michael C. Miller. + +I invest time and resources providing this open source code, +please support me by dontating (see https://github.com/Makuna/NeoPixelBus) + +------------------------------------------------------------------------- +This file is part of the Makuna/NeoPixelBus library. + +NeoPixelBus is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 3 of +the License, or (at your option) any later version. + +NeoPixelBus is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with NeoPixel. If not, see +. +-------------------------------------------------------------------------*/ + +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_IDF_TARGET_ESP32S3) + +extern "C" +{ +#include +#include +#include +//#include +#include +#include +#include +#include +#include "FractionClk.h" +} + +// +// true size of mux channel, 8 bit +// +class NeoEspLcdMuxBusSize8Bit +{ +public: + NeoEspLcdMuxBusSize8Bit() {}; + + const static size_t MuxBusDataSize = 1; + const static size_t DmaBitsPerPixelBit = 3; // 3 step cadence, matches endcoding + + + // by using a 3 step cadence, the dma data can't be updated with a single OR operation as + // its value resides across a non-uint16_t aligned 3 element type, so it requires two seperate OR + // operations to update a single pixel bit, the last element can be skipped as its always 0 + static void EncodeIntoDma(uint8_t* dmaBuffer, const uint8_t* data, size_t sizeData, uint8_t muxId) + { + uint8_t* pDma = dmaBuffer; + const uint8_t* pValue = data; + const uint8_t* pEnd = pValue + sizeData; + const uint8_t muxBit = 0x1 << muxId; + + while (pValue < pEnd) + { + uint8_t value = *(pValue++); + + for (uint8_t bit = 0; bit < 8; bit++) + { + // first cadence step init to 1 + *(pDma++) |= muxBit; + + // second candence step set based on bit + if (value & 0x80) + { + *(pDma) |= muxBit; + } + pDma++; + + // last candence step already init to 0, skip it + pDma++; + + // Next + value <<= 1; + } + } + } +}; + +// +// true size of mux channel, 16 bit +// +class NeoEspLcdMuxBusSize16Bit +{ +public: + NeoEspLcdMuxBusSize16Bit() {}; + + const static size_t MuxBusDataSize = 2; + const static size_t DmaBitsPerPixelBit = 3; // 3 step cadence, matches endcoding + + // by using a 3 step cadence, the dma data can't be updated with a single OR operation as + // its value resides across a non-uint32_t aligned 3 element type, so it requires two seperate OR + // operations to update a single pixel bit, the last element can be skipped as its always 0 + static void EncodeIntoDma(uint8_t* dmaBuffer, const uint8_t* data, size_t sizeData, uint8_t muxId) + { + uint16_t* pDma = reinterpret_cast(dmaBuffer); + const uint8_t* pValue = data; + const uint8_t* pEnd = pValue + sizeData; + const uint16_t muxBit = 0x1 << muxId; + + while (pValue < pEnd) + { + uint8_t value = *(pValue++); + + for (uint8_t bit = 0; bit < 8; bit++) + { + // first cadence step init to 1 + *(pDma++) |= muxBit; + + // second candence step set based on bit + if (value & 0x80) + { + *(pDma) |= muxBit; + } + pDma++; + + // last candence step already init to 0, skip it + pDma++; + + // Next + value <<= 1; + } + } + } +}; + +// +// tracks mux channels used and if updated +// +// T_FLAG - type used to store bit flags, UINT8_t for 8 channels, UINT16_t for 16 +// T_MUXSIZE - true size of mux channel = NeoEspLcdMuxBusSize8Bit or NeoEspLcdMuxBusSize16Bit +// +template +class NeoEspLcdMuxMap : public T_MUXSIZE +{ +public: + const static uint8_t InvalidMuxId = -1; + const static size_t BusMaxCount = sizeof(T_FLAG) * 8; + + size_t MaxBusDataSize; // max size of stream data from any single mux bus + T_FLAG UpdateMap; // bitmap flags of mux buses to track update state + T_FLAG UpdateMapMask; // mask to used bits in s_UpdateMap + T_FLAG BusCount; // count of mux buses + + // as a static instance, all members get initialized to zero + // and the constructor is called at inconsistent time to other globals + // so its not useful to have or rely on, + // but without it presence they get zeroed far too late + NeoEspLcdMuxMap() + //: + { + } + + uint8_t RegisterNewMuxBus(const size_t dataSize) + { + // find first available bus id + uint8_t muxId = 0; + while (muxId < BusMaxCount) + { + T_FLAG muxIdField = (1 << muxId); + if ((UpdateMapMask & muxIdField) == 0) + { + // complete registration + BusCount++; + UpdateMapMask |= muxIdField; + if (dataSize > MaxBusDataSize) + { + MaxBusDataSize = dataSize; + } + break; + } + muxId++; + } + if (muxId == BusMaxCount) + { + log_e("exceded channel limit of %u on bus", BusMaxCount); + } + return muxId; + } + + + bool DeregisterMuxBus(uint8_t muxId) + { + T_FLAG muxIdField = (1 << muxId); + if (UpdateMapMask & muxIdField) + { + // complete deregistration + BusCount--; + UpdateMapMask &= ~muxIdField; + if (UpdateMapMask == 0) + { + return true; + } + } + return false; + } + + bool IsAllMuxBusesUpdated() + { + return (UpdateMap == UpdateMapMask); + } + + bool IsNoMuxBusesUpdate() + { + return (UpdateMap == 0); + } + + void MarkMuxBusUpdated(uint8_t muxId) + { + UpdateMap |= (1 << muxId); + } + + void ResetMuxBusesUpdated() + { + UpdateMap = 0; + } + + void Reset() + { + MaxBusDataSize = 0; + UpdateMap = 0; + UpdateMapMask = 0; + BusCount = 0; + } +}; + +// REVIEW: Is this actually in IRAM, old compiler bug ignored function attributes in header files +static IRAM_ATTR bool dma_callback(gdma_channel_handle_t dma_chan, + gdma_event_data_t *event_data, + void *user_data) +{ +// esp_rom_delay_us(5); This was to handle the that the IRQ is fired on the last DMA block starting to transfer rather than when its done + LCD_CAM.lcd_user.lcd_start = 0; + return true; +} + +// +// Implementation of a Single Buffered version of a LcdContext +// Manages the underlying I2S details including the buffer +// This creates only a actively sending back buffer, +// Note that the back buffer must be DMA memory, a limited resource +// Assumes a 3 step candence, so pulses are 1/3 and 2/3 of pulse width +// +// T_MUXMAP - NeoEspLcdMuxMap - tracking class for mux state +// +template +class NeoEspLcdMonoBuffContext +{ +private: + gdma_channel_handle_t _dmaChannel; + dma_descriptor_t* _dmaItems; // holds the DMA description table + +public: + size_t LcdBufferSize; // total size of LcdBuffer + uint8_t* LcdBuffer; // holds the DMA buffer that is referenced by _dmaItems + T_MUXMAP MuxMap; + + // as a static instance, all members get initialized to zero + // and the constructor is called at inconsistent time to other globals + // so its not useful to have or rely on, + // but without it presence they get zeroed far too late + NeoEspLcdMonoBuffContext() + //: + { + } + + void Construct(uint16_t nsBitSendTime) + { + // construct only once on first time called + if (_dmaItems == nullptr) + { + // MuxMap.MaxBusDataSize = max size in bytes of a single channel + // DmaBitsPerPixelBit = how many dma bits/byte are needed for each source (pixel) bit/byte + // T_MUXMAP::MuxBusDataSize = the true size of data for selected mux mode (not exposed size as i2s0 only supports 16bit mode) + const size_t DmaBytesPerPixelByte = (8 * T_MUXMAP::DmaBitsPerPixelBit * T_MUXMAP::MuxBusDataSize); + LcdBufferSize = DmaBytesPerPixelByte * MuxMap.MaxBusDataSize; + + // must have a 4 byte aligned buffer for DMA + uint32_t alignment = LcdBufferSize % 4; + if (alignment) + { + LcdBufferSize += 4 - alignment; + } + + size_t dmaBlockCount = (LcdBufferSize + DMA_DESCRIPTOR_BUFFER_MAX_SIZE - 1) / DMA_DESCRIPTOR_BUFFER_MAX_SIZE; + dmaBlockCount++; // extra one at the end for trigger IRQ only + size_t dmaBlockSize = dmaBlockCount * sizeof(dma_descriptor_t); + + _dmaItems = static_cast(heap_caps_malloc(dmaBlockSize, MALLOC_CAP_DMA)); + if (_dmaItems == nullptr) + { + log_e("LCD Dma Table memory allocation failure (size %u)", + dmaBlockSize); + } + // required to init to zero as settings these below only resets some fields + memset(_dmaItems, 0x00, dmaBlockSize); + + LcdBuffer = static_cast(heap_caps_malloc(LcdBufferSize, MALLOC_CAP_DMA)); + if (LcdBuffer == nullptr) + { + log_e("LCD Dma Buffer memory allocation failure (size %u)", + LcdBufferSize); + } + memset(LcdBuffer, 0x00, LcdBufferSize); + + // init dma descriptor blocks + // + dma_descriptor_t* itemFirst = _dmaItems; + dma_descriptor_t* item = itemFirst; + dma_descriptor_t* itemNext = item + 1; + + size_t dataLeft = LcdBufferSize - DmaBytesPerPixelByte; // last one for IRQ use + uint8_t* pos = LcdBuffer; + + // init blocks with available data + // + while (dataLeft) + { + // track how much of data goes into this descriptor block + size_t blockSize = dataLeft; + if (blockSize >= DMA_DESCRIPTOR_BUFFER_MAX_SIZE) + { + blockSize = DMA_DESCRIPTOR_BUFFER_MAX_SIZE - 1; + } + dataLeft -= blockSize; + + // init a DMA descriptor item + item->dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA; + item->dw0.suc_eof = 0; + item->next = itemNext; + item->dw0.size = blockSize; + item->dw0.length = blockSize; + item->buffer = pos; + + pos += blockSize; + + item = itemNext; + itemNext++; + } + + // last dma descriptor item is EOF to manage send state using EOF ISR + item->dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA; + item->dw0.suc_eof = 1; + item->next = NULL; + item->dw0.size = 0; + item->dw0.length = 0; + item->buffer = NULL; + + // Configure LCD Peripheral + // + + // LCD_CAM isn't enabled by default -- MUST begin with this: + periph_module_enable(PERIPH_LCD_CAM_MODULE); + periph_module_reset(PERIPH_LCD_CAM_MODULE); + + // Reset LCD bus + LCD_CAM.lcd_user.lcd_reset = 1; + esp_rom_delay_us(100); + + // calc needed clock scaler values from bit send time + // + double clkm_div = (double)nsBitSendTime / T_MUXMAP::DmaBitsPerPixelBit / 1000.0 * 240.0; // PLL 240Mhz + if (clkm_div > LCD_LL_CLK_FRAC_DIV_N_MAX) + { + log_e("rate is too low"); + return; + } + else if (clkm_div < 2.0) + { + log_e("rate is too fast, clkmdiv = %f (%u)", + clkm_div, + nsBitSendTime); + return; + } + + // calc integer and franctional for more precise timing + uint8_t clkm_div_Integer = clkm_div; + double clkm_Fraction = (clkm_div - clkm_div_Integer); + uint8_t divB = 0; + uint8_t divA = 0; + + UnitDecimalToFractionClks(&divB, &divA, clkm_Fraction, 0.000001); + + //Serial.print("Clk Div "); + //Serial.print(clkm_div); + //Serial.print(" = "); + + //Serial.print(clkm_div_Integer); + //Serial.print(" "); + //Serial.print(divB); + //Serial.print("/"); + //Serial.println(divA); + + // Configure LCD clock + LCD_CAM.lcd_clock.clk_en = 1; // Enable clock + LCD_CAM.lcd_clock.lcd_clk_sel = 2; // PLL240M source + LCD_CAM.lcd_clock.lcd_clkm_div_a = divA; // scale fractional denomenator, + LCD_CAM.lcd_clock.lcd_clkm_div_b = divB; // scale fractional numerator + LCD_CAM.lcd_clock.lcd_clkm_div_num = clkm_div_Integer; // scale integer (240Mhz clock) + LCD_CAM.lcd_clock.lcd_ck_out_edge = 0; // PCLK low in 1st half cycle + LCD_CAM.lcd_clock.lcd_ck_idle_edge = 0; // PCLK low idle + LCD_CAM.lcd_clock.lcd_clk_equ_sysclk = 1; // PCLK = CLK (ignore CLKCNT_N) + + // Configure frame format + LCD_CAM.lcd_ctrl.lcd_rgb_mode_en = 0; // i8080 mode (not RGB) + LCD_CAM.lcd_rgb_yuv.lcd_conv_bypass = 0; // Disable RGB/YUV converter + LCD_CAM.lcd_misc.lcd_next_frame_en = 0; // Do NOT auto-frame + LCD_CAM.lcd_data_dout_mode.val = 0; // No data delays + LCD_CAM.lcd_user.lcd_always_out_en = 1; // Enable 'always out' mode + LCD_CAM.lcd_user.lcd_8bits_order = 0; // Do not swap bytes + LCD_CAM.lcd_user.lcd_bit_order = 0; // Do not reverse bit order + LCD_CAM.lcd_user.lcd_2byte_en = (T_MUXMAP::MuxBusDataSize == 2); + LCD_CAM.lcd_user.lcd_dummy = 1; // Dummy phase(s) @ LCD start + LCD_CAM.lcd_user.lcd_dummy_cyclelen = 0; // 1 dummy phase + LCD_CAM.lcd_user.lcd_cmd = 0; // No command at LCD start + // Dummy phase(s) MUST be enabled for DMA to trigger reliably. + + // Alloc DMA channel & connect it to LCD periph + gdma_channel_alloc_config_t dma_chan_config = { + .sibling_chan = NULL, + .direction = GDMA_CHANNEL_DIRECTION_TX, + .flags = {.reserve_sibling = 0}}; + gdma_new_channel(&dma_chan_config, &_dmaChannel); + gdma_connect(_dmaChannel, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_LCD, 0)); + gdma_strategy_config_t strategy_config = {.owner_check = false, + .auto_update_desc = false}; + gdma_apply_strategy(_dmaChannel, &strategy_config); + + // Enable DMA transfer callback + gdma_tx_event_callbacks_t tx_cbs = {.on_trans_eof = dma_callback}; + gdma_register_tx_event_callbacks(_dmaChannel, &tx_cbs, NULL); + } + } + + void Destruct() + { + if (_dmaItems == nullptr) + { + return; + } + + periph_module_disable(PERIPH_LCD_CAM_MODULE); + periph_module_reset(PERIPH_LCD_CAM_MODULE); + + gdma_reset(_dmaChannel); + + heap_caps_free(LcdBuffer); + heap_caps_free(_dmaItems); + + LcdBufferSize = 0; + _dmaItems = nullptr; + LcdBuffer = nullptr; + + MuxMap.Reset(); + } + + void StartWrite() + { + if (MuxMap.IsAllMuxBusesUpdated()) + { + MuxMap.ResetMuxBusesUpdated(); + + gdma_reset(_dmaChannel); + LCD_CAM.lcd_user.lcd_dout = 1; + LCD_CAM.lcd_user.lcd_update = 1; + LCD_CAM.lcd_misc.lcd_afifo_reset = 1; + + gdma_start(_dmaChannel, (intptr_t)&_dmaItems[0]); + esp_rom_delay_us(1); + LCD_CAM.lcd_user.lcd_start = 1; + } + } + + void FillBuffers(const uint8_t* data, + size_t sizeData, + uint8_t muxId) + { + // wait for not actively sending data + while (LCD_CAM.lcd_user.lcd_start) + { + yield(); + } + + + // to keep the inner loops for EncodeIntoDma smaller + // they will just OR in their values + // so the buffer must be cleared first + if (MuxMap.IsNoMuxBusesUpdate()) + { + // clear all the data in preperation for each mux channel to update their bit + memset(LcdBuffer, 0x00, LcdBufferSize); + } + + MuxMap.EncodeIntoDma(LcdBuffer, + data, + sizeData, + muxId); + + MuxMap.MarkMuxBusUpdated(muxId); + } +}; + +// +// Implementation of the low level interface into lcd mux bus +// +// T_BUSCONTEXT - the context to use, currently only NeoEspLcdDblBuffContext but there is +// a plan to provide one that doesn't implement the front buffer but would be less +// async as it would have to wait until the last frame was completely sent before +// updating and new data +// T_BUS - the bus id, NeoEsp32LcdBusZero, NeoEsp32LcdBusOne +// +template +class NeoEsp32LcdMuxBus +{ +public: + NeoEsp32LcdMuxBus() : + _muxId(s_context.MuxMap.InvalidMuxId) + { + } + + void RegisterNewMuxBus(size_t dataSize) + { + _muxId = s_context.MuxMap.RegisterNewMuxBus(dataSize); + } + + void Initialize(uint8_t pin, uint16_t nsBitSendTime, bool invert) + { + s_context.Construct(nsBitSendTime); + + uint8_t muxIdx = LCD_DATA_OUT0_IDX + _muxId; + esp_rom_gpio_connect_out_signal(pin, muxIdx, invert, false); + gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[pin], PIN_FUNC_GPIO); + gpio_set_drive_capability((gpio_num_t)pin, (gpio_drive_cap_t)3); + } + + void DeregisterMuxBus(uint8_t pin) + { + if (s_context.MuxMap.DeregisterMuxBus(_muxId)) + { + s_context.Destruct(); + } + + // disconnect muxed pin + gpio_matrix_out(pin, SIG_GPIO_OUT_IDX, false, false); + pinMode(pin, INPUT); + + _muxId = s_context.MuxMap.InvalidMuxId; + } + + void StartWrite() + { + s_context.StartWrite(); + } + + bool IsWriteDone() const + { + bool busy = LCD_CAM.lcd_user.lcd_start; + return !busy; + } + + void FillBuffers(const uint8_t* data, size_t sizeData) + { + s_context.FillBuffers(data, sizeData, _muxId); + } + + void MarkUpdated() + { + s_context.MuxMap.MarkMuxBusUpdated(_muxId); + } + +private: + static T_BUSCONTEXT s_context; + uint8_t _muxId; +}; + +template T_BUSCONTEXT NeoEsp32LcdMuxBus::s_context = T_BUSCONTEXT(); + +// +// wrapping layer of the lcd mux bus as a NeoMethod +// +// T_SPEED - NeoEsp32LcdSpeed* (ex NeoEsp32LcdSpeedWs2812x) used to define output signal form +// T_BUS - NeoEsp32LcdMuxBus, the bus to use +// T_INVERT - NeoEsp32LcdNotInverted or NeoEsp32LcdInverted, will invert output signal +// +template +class NeoEsp32LcdXMethodBase +{ +public: + typedef NeoNoSettings SettingsObject; + + NeoEsp32LcdXMethodBase(uint8_t pin, uint16_t pixelCount, size_t elementSize, size_t settingsSize) : + _sizeData(pixelCount * elementSize + settingsSize), + _pin(pin), + _bus() + { + size_t numResetBytes = T_SPEED::ResetTimeUs / (T_SPEED::BitSendTimeNs*8/1000); + _bus.RegisterNewMuxBus(_sizeData + numResetBytes); + } + + ~NeoEsp32LcdXMethodBase() + { + while (!_bus.IsWriteDone()) + { + yield(); + } + + _bus.DeregisterMuxBus(_pin); + + free(_data); + } + + bool IsReadyToUpdate() const + { + return _bus.IsWriteDone(); + } + + void Initialize() + { + _bus.Initialize(_pin, T_SPEED::BitSendTimeNs, T_INVERT::Inverted); + + _data = static_cast(malloc(_sizeData)); + if (_data == nullptr) + { + log_e("front buffer memory allocation failure"); + } + // data cleared later in Begin() + } + + void Update(bool) + { + _bus.FillBuffers(_data, _sizeData); + + _bus.StartWrite(); // only triggers actual write after all mux busses have updated + } + + bool AlwaysUpdate() + { + // this method requires update to be called even if no changes to method buffer + // as edit buffer is always cleared and then copied to send buffer and all + // mux bus needs to included + return true; + } + + uint8_t* getData() const + { + return _data; + }; + + size_t getDataSize() const + { + return _sizeData; + } + + void applySettings([[maybe_unused]] const SettingsObject& settings) + { + } + +private: + const size_t _sizeData; // Size of '_data' buffer + const uint8_t _pin; // output pin number + + T_BUS _bus; // holds instance for mux bus support + uint8_t* _data; // Holds LED color values +}; + + + +typedef NeoEsp32LcdMuxBus>> NeoEsp32LcdMux8Bus; +typedef NeoEsp32LcdMuxBus>> NeoEsp32LcdMux16Bus; + +// -------------------------------------------------------- +class NeoEsp32LcdSpeedWs2812x +{ +public: + const static uint16_t BitSendTimeNs = 1250; + const static uint16_t ResetTimeUs = 300; +}; + +class NeoEsp32LcdSpeeWs2805 +{ +public: + const static uint16_t BitSendTimeNs = 1125; + const static uint16_t ResetTimeUs = 300; // spec is 280, intentionally longer for compatiblity use +}; + +class NeoEsp32LcdSpeeSk6812 +{ +public: + const static uint16_t BitSendTimeNs = 1250; + const static uint16_t ResetTimeUs = 80; +}; + +class NeoEsp32LcdSpeeTm1814 +{ +public: + const static uint16_t BitSendTimeNs = 1250; + const static uint16_t ResetTimeUs = 200; +}; + +class NeoEsp32LcdSpeeTm1914 +{ +public: + const static uint16_t BitSendTimeNs = 1250; + const static uint16_t ResetTimeUs = 200; +}; + +class NeoEsp32LcdSpeeTm1829 +{ +public: + const static uint16_t BitSendTimeNs = 1250; + const static uint16_t ResetTimeUs = 200; +}; + +class NeoEsp32LcdSpee800Kbps +{ +public: + const static uint16_t BitSendTimeNs = 1250; + const static uint16_t ResetTimeUs = 50; +}; + +class NeoEsp32LcdSpee400Kbps +{ +public: + const static uint16_t BitSendTimeNs = 2500; + const static uint16_t ResetTimeUs = 50; +}; + +class NeoEsp32LcdSpeeApa106 +{ +public: + const static uint16_t BitSendTimeNs = 1710; + const static uint16_t ResetTimeUs = 50; +}; + +//--------------------------------------------------------- +class NeoEsp32LcdNotInverted +{ +public: + const static bool Inverted = false; +}; + +class NeoEsp32LcdInverted +{ +public: + const static bool Inverted = true; +}; + + +//-------------------------------------------------------- + +typedef NeoEsp32LcdXMethodBase NeoEsp32LcdX8Ws2812xMethod; +typedef NeoEsp32LcdXMethodBase NeoEsp32LcdX8Ws2805Method; +typedef NeoEsp32LcdXMethodBase NeoEsp32LcdX8Sk6812Method; +typedef NeoEsp32LcdXMethodBase NeoEsp32LcdX8Tm1814Method; +typedef NeoEsp32LcdXMethodBase NeoEsp32LcdX8Tm1829Method; +typedef NeoEsp32LcdXMethodBase NeoEsp32LcdX8Tm1914Method; +typedef NeoEsp32LcdXMethodBase NeoEsp32LcdX8800KbpsMethod; +typedef NeoEsp32LcdXMethodBase NeoEsp32LcdX8400KbpsMethod; +typedef NeoEsp32LcdXMethodBase NeoEsp32LcdX8Apa106Method; + +typedef NeoEsp32LcdX8Ws2805Method NeoEsp32LcdX8Ws2814Method; +typedef NeoEsp32LcdX8Ws2812xMethod NeoEsp32LcdX8Ws2813Method; +typedef NeoEsp32LcdX8Ws2812xMethod NeoEsp32LcdX8Ws2812dMethod; +typedef NeoEsp32LcdX8Ws2812xMethod NeoEsp32LcdX8Ws2811Method; +typedef NeoEsp32LcdX8Ws2812xMethod NeoEsp32LcdX8Ws2816Method; +typedef NeoEsp32LcdX8800KbpsMethod NeoEsp32LcdX8Ws2812Method; +typedef NeoEsp32LcdX8Sk6812Method NeoEsp32LcdX8Lc8812Method; + +typedef NeoEsp32LcdXMethodBase NeoEsp32LcdX16Ws2812xMethod; +typedef NeoEsp32LcdXMethodBase NeoEsp32LcdX16Ws2805Method; +typedef NeoEsp32LcdXMethodBase NeoEsp32LcdX16Sk6812Method; +typedef NeoEsp32LcdXMethodBase NeoEsp32LcdX16Tm1814Method; +typedef NeoEsp32LcdXMethodBase NeoEsp32LcdX16Tm1829Method; +typedef NeoEsp32LcdXMethodBase NeoEsp32LcdX16Tm1914Method; +typedef NeoEsp32LcdXMethodBase NeoEsp32LcdX16800KbpsMethod; +typedef NeoEsp32LcdXMethodBase NeoEsp32LcdX16400KbpsMethod; +typedef NeoEsp32LcdXMethodBase NeoEsp32LcdX16Apa106Method; + +typedef NeoEsp32LcdX16Ws2805Method NeoEsp32LcdX16Ws2814Method; +typedef NeoEsp32LcdX16Ws2812xMethod NeoEsp32LcdX16Ws2813Method; +typedef NeoEsp32LcdX16Ws2812xMethod NeoEsp32LcdX16Ws2812dMethod; +typedef NeoEsp32LcdX16Ws2812xMethod NeoEsp32LcdX16Ws2811Method; +typedef NeoEsp32LcdX16Ws2812xMethod NeoEsp32LcdX16Ws2816Method; +typedef NeoEsp32LcdX16800KbpsMethod NeoEsp32LcdX16Ws2812Method; +typedef NeoEsp32LcdX16Sk6812Method NeoEsp32LcdX16Lc8812Method; + + +//-------------------------------------------------------- +typedef NeoEsp32LcdXMethodBase NeoEsp32LcdX8Ws2812xInvertedMethod; +typedef NeoEsp32LcdXMethodBase NeoEsp32LcdX8Ws2805InvertedMethod; +typedef NeoEsp32LcdXMethodBase NeoEsp32LcdX8Sk6812InvertedMethod; +typedef NeoEsp32LcdXMethodBase NeoEsp32LcdX8Tm1814InvertedMethod; +typedef NeoEsp32LcdXMethodBase NeoEsp32LcdX8Tm1829InvertedMethod; +typedef NeoEsp32LcdXMethodBase NeoEsp32LcdX8Tm1914InvertedMethod; +typedef NeoEsp32LcdXMethodBase NeoEsp32LcdX8800KbpsInvertedMethod; +typedef NeoEsp32LcdXMethodBase NeoEsp32LcdX8400KbpsInvertedMethod; +typedef NeoEsp32LcdXMethodBase NeoEsp32LcdX8Apa106InvertedMethod; + +typedef NeoEsp32LcdX8Ws2805InvertedMethod NeoEsp32LcdX8Ws2814InvertedMethod; +typedef NeoEsp32LcdX8Ws2812xInvertedMethod NeoEsp32LcdX8Ws2813InvertedMethod; +typedef NeoEsp32LcdX8Ws2812xInvertedMethod NeoEsp32LcdX8Ws2812dInvertedMethod; +typedef NeoEsp32LcdX8Ws2812xInvertedMethod NeoEsp32LcdX8Ws2811InvertedMethod; +typedef NeoEsp32LcdX8Ws2812xInvertedMethod NeoEsp32LcdX8Ws2816InvertedMethod; +typedef NeoEsp32LcdX8800KbpsInvertedMethod NeoEsp32LcdX8Ws2812InvertedMethod; +typedef NeoEsp32LcdX8Sk6812InvertedMethod NeoEsp32LcdX8Lc8812InvertedMethod; + +typedef NeoEsp32LcdXMethodBase NeoEsp32LcdX16Ws2812xInvertedMethod; +typedef NeoEsp32LcdXMethodBase NeoEsp32LcdX16Ws2805InvertedMethod; +typedef NeoEsp32LcdXMethodBase NeoEsp32LcdX16Sk6812InvertedMethod; +typedef NeoEsp32LcdXMethodBase NeoEsp32LcdX16Tm1814InvertedMethod; +typedef NeoEsp32LcdXMethodBase NeoEsp32LcdX16Tm1829InvertedMethod; +typedef NeoEsp32LcdXMethodBase NeoEsp32LcdX16Tm1914InvertedMethod; +typedef NeoEsp32LcdXMethodBase NeoEsp32LcdX16800KbpsInvertedMethod; +typedef NeoEsp32LcdXMethodBase NeoEsp32LcdX16400KbpsInvertedMethod; +typedef NeoEsp32LcdXMethodBase NeoEsp32LcdX16Apa106InvertedMethod; + +typedef NeoEsp32LcdX16Ws2805InvertedMethod NeoEsp32LcdX16Ws2814InvertedMethod; +typedef NeoEsp32LcdX16Ws2812xInvertedMethod NeoEsp32LcdX16Ws2813InvertedMethod; +typedef NeoEsp32LcdX16Ws2812xInvertedMethod NeoEsp32LcdX16Ws2812dInvertedMethod; +typedef NeoEsp32LcdX16Ws2812xInvertedMethod NeoEsp32LcdX16Ws2811InvertedMethod; +typedef NeoEsp32LcdX16Ws2812xInvertedMethod NeoEsp32LcdX16Ws2816InvertedMethod; +typedef NeoEsp32LcdX16800KbpsInvertedMethod NeoEsp32LcdX16Ws2812InvertedMethod; +typedef NeoEsp32LcdX16Sk6812InvertedMethod NeoEsp32LcdX16Lc8812InvertedMethod; + +#endif // defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_IDF_TARGET_ESP32S3)