Esp32 s3 LCD Parallel (#824)

Co-authored-by: Colin Iuliano <colin@coliniuliano.ca>
Co-authored-by: Colin <cholin@gmail.com>
This commit is contained in:
Michael Miller
2024-08-18 11:44:48 -07:00
committed by GitHub
parent bf72b6ecc9
commit 4b96e0d666
10 changed files with 1106 additions and 147 deletions

View File

@@ -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 <NeoPixelBus.h>

View File

@@ -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 <NeoPixelBus.h>
// Demonstrating the use of the first four channels, but the method used allows for eight
NeoPixelBus<NeoBgrFeature, NeoEsp32LcdX8Ws2811Method> strip1(120, 15); // note: older WS2811 and longer strip
NeoPixelBus<NeoGrbFeature, NeoEsp32LcdX8Ws2812xMethod> strip2(100, 2); // note: modern WS2812 with letter like WS2812b
NeoPixelBus<NeoGrbFeature, NeoEsp32LcdX8Ws2812xInvertedMethod> strip3(100, 4); // note: inverted
NeoPixelBus<NeoGrbwFeature, NeoEsp32LcdX8Sk6812Method> 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();
}

View File

@@ -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

View File

@@ -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 <string.h>
#include <stdio.h>
#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,

View File

@@ -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

View File

@@ -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
<http://www.gnu.org/licenses/>.
-------------------------------------------------------------------------*/
#include <string.h>
#include <stdio.h>
#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;
}

View File

@@ -0,0 +1,23 @@
#pragma once
#include <string.h>
#include <stdio.h>
#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

View File

@@ -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<typename T_SPEED, typename T_BUS, typename T_INVERT> class NeoEsp32I2sMethodBase
{
public:

View File

@@ -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<uint32_t*>(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<uint64_t*>(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<typename T_MUXMAP>
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<typename T_MUXMAP>
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;

View File

@@ -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
<http://www.gnu.org/licenses/>.
-------------------------------------------------------------------------*/
#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_IDF_TARGET_ESP32S3)
extern "C"
{
#include <driver/periph_ctrl.h>
#include <esp_private/gdma.h>
#include <esp_rom_gpio.h>
//#include <esp_rom_lldesc.h>
#include <hal/dma_types.h>
#include <hal/gpio_hal.h>
#include <hal/lcd_ll.h>
#include <soc/lcd_cam_struct.h>
#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<uint16_t*>(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<typename T_FLAG, typename T_MUXSIZE>
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<typename T_MUXMAP>
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<dma_descriptor_t*>(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<uint8_t*>(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<typename T_BUSCONTEXT>
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<typename T_BUSCONTEXT> T_BUSCONTEXT NeoEsp32LcdMuxBus<T_BUSCONTEXT>::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<typename T_SPEED, typename T_BUS, typename T_INVERT>
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<uint8_t*>(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<NeoEspLcdMonoBuffContext<NeoEspLcdMuxMap<uint8_t, NeoEspLcdMuxBusSize8Bit>>> NeoEsp32LcdMux8Bus;
typedef NeoEsp32LcdMuxBus<NeoEspLcdMonoBuffContext<NeoEspLcdMuxMap<uint16_t, NeoEspLcdMuxBusSize16Bit>>> 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<NeoEsp32LcdSpeedWs2812x, NeoEsp32LcdMux8Bus, NeoEsp32LcdNotInverted> NeoEsp32LcdX8Ws2812xMethod;
typedef NeoEsp32LcdXMethodBase<NeoEsp32LcdSpeeWs2805, NeoEsp32LcdMux8Bus, NeoEsp32LcdNotInverted> NeoEsp32LcdX8Ws2805Method;
typedef NeoEsp32LcdXMethodBase<NeoEsp32LcdSpeeSk6812, NeoEsp32LcdMux8Bus, NeoEsp32LcdNotInverted> NeoEsp32LcdX8Sk6812Method;
typedef NeoEsp32LcdXMethodBase<NeoEsp32LcdSpeeTm1814, NeoEsp32LcdMux8Bus, NeoEsp32LcdInverted> NeoEsp32LcdX8Tm1814Method;
typedef NeoEsp32LcdXMethodBase<NeoEsp32LcdSpeeTm1914, NeoEsp32LcdMux8Bus, NeoEsp32LcdInverted> NeoEsp32LcdX8Tm1829Method;
typedef NeoEsp32LcdXMethodBase<NeoEsp32LcdSpeeTm1829, NeoEsp32LcdMux8Bus, NeoEsp32LcdInverted> NeoEsp32LcdX8Tm1914Method;
typedef NeoEsp32LcdXMethodBase<NeoEsp32LcdSpee800Kbps, NeoEsp32LcdMux8Bus, NeoEsp32LcdNotInverted> NeoEsp32LcdX8800KbpsMethod;
typedef NeoEsp32LcdXMethodBase<NeoEsp32LcdSpee400Kbps, NeoEsp32LcdMux8Bus, NeoEsp32LcdNotInverted> NeoEsp32LcdX8400KbpsMethod;
typedef NeoEsp32LcdXMethodBase<NeoEsp32LcdSpeeApa106, NeoEsp32LcdMux8Bus, NeoEsp32LcdNotInverted> NeoEsp32LcdX8Apa106Method;
typedef NeoEsp32LcdX8Ws2805Method NeoEsp32LcdX8Ws2814Method;
typedef NeoEsp32LcdX8Ws2812xMethod NeoEsp32LcdX8Ws2813Method;
typedef NeoEsp32LcdX8Ws2812xMethod NeoEsp32LcdX8Ws2812dMethod;
typedef NeoEsp32LcdX8Ws2812xMethod NeoEsp32LcdX8Ws2811Method;
typedef NeoEsp32LcdX8Ws2812xMethod NeoEsp32LcdX8Ws2816Method;
typedef NeoEsp32LcdX8800KbpsMethod NeoEsp32LcdX8Ws2812Method;
typedef NeoEsp32LcdX8Sk6812Method NeoEsp32LcdX8Lc8812Method;
typedef NeoEsp32LcdXMethodBase<NeoEsp32LcdSpeedWs2812x, NeoEsp32LcdMux16Bus, NeoEsp32LcdNotInverted> NeoEsp32LcdX16Ws2812xMethod;
typedef NeoEsp32LcdXMethodBase<NeoEsp32LcdSpeeWs2805, NeoEsp32LcdMux16Bus, NeoEsp32LcdNotInverted> NeoEsp32LcdX16Ws2805Method;
typedef NeoEsp32LcdXMethodBase<NeoEsp32LcdSpeeSk6812, NeoEsp32LcdMux16Bus, NeoEsp32LcdNotInverted> NeoEsp32LcdX16Sk6812Method;
typedef NeoEsp32LcdXMethodBase<NeoEsp32LcdSpeeTm1814, NeoEsp32LcdMux16Bus, NeoEsp32LcdInverted> NeoEsp32LcdX16Tm1814Method;
typedef NeoEsp32LcdXMethodBase<NeoEsp32LcdSpeeTm1914, NeoEsp32LcdMux16Bus, NeoEsp32LcdInverted> NeoEsp32LcdX16Tm1829Method;
typedef NeoEsp32LcdXMethodBase<NeoEsp32LcdSpeeTm1829, NeoEsp32LcdMux16Bus, NeoEsp32LcdInverted> NeoEsp32LcdX16Tm1914Method;
typedef NeoEsp32LcdXMethodBase<NeoEsp32LcdSpee800Kbps, NeoEsp32LcdMux16Bus, NeoEsp32LcdNotInverted> NeoEsp32LcdX16800KbpsMethod;
typedef NeoEsp32LcdXMethodBase<NeoEsp32LcdSpee400Kbps, NeoEsp32LcdMux16Bus, NeoEsp32LcdNotInverted> NeoEsp32LcdX16400KbpsMethod;
typedef NeoEsp32LcdXMethodBase<NeoEsp32LcdSpeeApa106, NeoEsp32LcdMux16Bus, NeoEsp32LcdNotInverted> NeoEsp32LcdX16Apa106Method;
typedef NeoEsp32LcdX16Ws2805Method NeoEsp32LcdX16Ws2814Method;
typedef NeoEsp32LcdX16Ws2812xMethod NeoEsp32LcdX16Ws2813Method;
typedef NeoEsp32LcdX16Ws2812xMethod NeoEsp32LcdX16Ws2812dMethod;
typedef NeoEsp32LcdX16Ws2812xMethod NeoEsp32LcdX16Ws2811Method;
typedef NeoEsp32LcdX16Ws2812xMethod NeoEsp32LcdX16Ws2816Method;
typedef NeoEsp32LcdX16800KbpsMethod NeoEsp32LcdX16Ws2812Method;
typedef NeoEsp32LcdX16Sk6812Method NeoEsp32LcdX16Lc8812Method;
//--------------------------------------------------------
typedef NeoEsp32LcdXMethodBase<NeoEsp32LcdSpeedWs2812x, NeoEsp32LcdMux8Bus, NeoEsp32LcdInverted> NeoEsp32LcdX8Ws2812xInvertedMethod;
typedef NeoEsp32LcdXMethodBase<NeoEsp32LcdSpeeWs2805, NeoEsp32LcdMux8Bus, NeoEsp32LcdInverted> NeoEsp32LcdX8Ws2805InvertedMethod;
typedef NeoEsp32LcdXMethodBase<NeoEsp32LcdSpeeSk6812, NeoEsp32LcdMux8Bus, NeoEsp32LcdInverted> NeoEsp32LcdX8Sk6812InvertedMethod;
typedef NeoEsp32LcdXMethodBase<NeoEsp32LcdSpeeTm1814, NeoEsp32LcdMux8Bus, NeoEsp32LcdNotInverted> NeoEsp32LcdX8Tm1814InvertedMethod;
typedef NeoEsp32LcdXMethodBase<NeoEsp32LcdSpeeTm1914, NeoEsp32LcdMux8Bus, NeoEsp32LcdNotInverted> NeoEsp32LcdX8Tm1829InvertedMethod;
typedef NeoEsp32LcdXMethodBase<NeoEsp32LcdSpeeTm1829, NeoEsp32LcdMux8Bus, NeoEsp32LcdNotInverted> NeoEsp32LcdX8Tm1914InvertedMethod;
typedef NeoEsp32LcdXMethodBase<NeoEsp32LcdSpee800Kbps, NeoEsp32LcdMux8Bus, NeoEsp32LcdInverted> NeoEsp32LcdX8800KbpsInvertedMethod;
typedef NeoEsp32LcdXMethodBase<NeoEsp32LcdSpee400Kbps, NeoEsp32LcdMux8Bus, NeoEsp32LcdInverted> NeoEsp32LcdX8400KbpsInvertedMethod;
typedef NeoEsp32LcdXMethodBase<NeoEsp32LcdSpeeApa106, NeoEsp32LcdMux8Bus, NeoEsp32LcdInverted> NeoEsp32LcdX8Apa106InvertedMethod;
typedef NeoEsp32LcdX8Ws2805InvertedMethod NeoEsp32LcdX8Ws2814InvertedMethod;
typedef NeoEsp32LcdX8Ws2812xInvertedMethod NeoEsp32LcdX8Ws2813InvertedMethod;
typedef NeoEsp32LcdX8Ws2812xInvertedMethod NeoEsp32LcdX8Ws2812dInvertedMethod;
typedef NeoEsp32LcdX8Ws2812xInvertedMethod NeoEsp32LcdX8Ws2811InvertedMethod;
typedef NeoEsp32LcdX8Ws2812xInvertedMethod NeoEsp32LcdX8Ws2816InvertedMethod;
typedef NeoEsp32LcdX8800KbpsInvertedMethod NeoEsp32LcdX8Ws2812InvertedMethod;
typedef NeoEsp32LcdX8Sk6812InvertedMethod NeoEsp32LcdX8Lc8812InvertedMethod;
typedef NeoEsp32LcdXMethodBase<NeoEsp32LcdSpeedWs2812x, NeoEsp32LcdMux16Bus, NeoEsp32LcdInverted> NeoEsp32LcdX16Ws2812xInvertedMethod;
typedef NeoEsp32LcdXMethodBase<NeoEsp32LcdSpeeWs2805, NeoEsp32LcdMux16Bus, NeoEsp32LcdInverted> NeoEsp32LcdX16Ws2805InvertedMethod;
typedef NeoEsp32LcdXMethodBase<NeoEsp32LcdSpeeSk6812, NeoEsp32LcdMux16Bus, NeoEsp32LcdInverted> NeoEsp32LcdX16Sk6812InvertedMethod;
typedef NeoEsp32LcdXMethodBase<NeoEsp32LcdSpeeTm1814, NeoEsp32LcdMux16Bus, NeoEsp32LcdNotInverted> NeoEsp32LcdX16Tm1814InvertedMethod;
typedef NeoEsp32LcdXMethodBase<NeoEsp32LcdSpeeTm1914, NeoEsp32LcdMux16Bus, NeoEsp32LcdNotInverted> NeoEsp32LcdX16Tm1829InvertedMethod;
typedef NeoEsp32LcdXMethodBase<NeoEsp32LcdSpeeTm1829, NeoEsp32LcdMux16Bus, NeoEsp32LcdNotInverted> NeoEsp32LcdX16Tm1914InvertedMethod;
typedef NeoEsp32LcdXMethodBase<NeoEsp32LcdSpee800Kbps, NeoEsp32LcdMux16Bus, NeoEsp32LcdInverted> NeoEsp32LcdX16800KbpsInvertedMethod;
typedef NeoEsp32LcdXMethodBase<NeoEsp32LcdSpee400Kbps, NeoEsp32LcdMux16Bus, NeoEsp32LcdInverted> NeoEsp32LcdX16400KbpsInvertedMethod;
typedef NeoEsp32LcdXMethodBase<NeoEsp32LcdSpeeApa106, NeoEsp32LcdMux16Bus, NeoEsp32LcdInverted> 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)