forked from Makuna/NeoPixelBus
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:
@@ -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>
|
||||
|
||||
|
@@ -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();
|
||||
}
|
@@ -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
|
||||
|
@@ -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,
|
||||
|
@@ -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
|
||||
|
157
src/internal/methods/FractionClk.c
Normal file
157
src/internal/methods/FractionClk.c
Normal 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;
|
||||
}
|
||||
|
23
src/internal/methods/FractionClk.h
Normal file
23
src/internal/methods/FractionClk.h
Normal 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
|
||||
|
@@ -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:
|
||||
|
@@ -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;
|
||||
|
837
src/internal/methods/NeoEsp32LcdXMethod.h
Normal file
837
src/internal/methods/NeoEsp32LcdXMethod.h
Normal 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)
|
Reference in New Issue
Block a user