forked from Makuna/NeoPixelBus
esp32 full
This commit is contained in:
@ -7,7 +7,7 @@
|
||||
"type": "git",
|
||||
"url": "https://github.com/Makuna/NeoPixelBus"
|
||||
},
|
||||
"version": "2.9.0",
|
||||
"version": "2.9.1",
|
||||
"frameworks": "arduino",
|
||||
"platforms": "*",
|
||||
"dependencies": [
|
||||
|
@ -1,5 +1,5 @@
|
||||
name=NeoPixelBus by Makuna
|
||||
version=2.9.0
|
||||
version=2.9.1
|
||||
author=Michael C. Miller (makuna@live.com)
|
||||
maintainer=Michael C. Miller (makuna@live.com)
|
||||
sentence=A library that makes controlling NeoPixels (WS2812x and many others) and DotStars (SK6812 and many others) easy.
|
||||
|
@ -60,6 +60,25 @@ License along with NeoPixel. If not, see
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) && !defined(ARDUINO_ESP32C6_DEV) && !defined(ARDUINO_ESP32H2_DEV)
|
||||
|
||||
inline
|
||||
static uint32_t getEspCycleCount(void)
|
||||
{
|
||||
uint32_t ccount;
|
||||
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
__asm__ __volatile__("csrr %0,0x7e2":"=r" (ccount));
|
||||
//ccount = esp_cpu_get_ccount();
|
||||
#else
|
||||
__asm__ __volatile__("rsr %0,ccount":"=a" (ccount));
|
||||
#endif
|
||||
return ccount;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
// some platforms do not define this standard progmem type for some reason
|
||||
//
|
||||
#ifndef PGM_VOID_P
|
||||
|
@ -171,7 +171,7 @@ public:
|
||||
typedef NeoNoSettings SettingsObject;
|
||||
|
||||
NeoAvrMethodBase(uint8_t pin,
|
||||
[[maybe_unused]] uint16_t pixelCount,
|
||||
uint16_t pixelCount,
|
||||
[[maybe_unused]] size_t elementSize,
|
||||
[[maybe_unused]] size_t settingsSize) :
|
||||
_pin(pin),
|
||||
|
@ -58,7 +58,7 @@ public:
|
||||
|
||||
const static size_t MuxBusDataSize = 1;
|
||||
|
||||
static void EncodeIntoDma(uint8_t* dmaBuffer, const uint8_t* data, size_t sizeData, uint8_t muxId)
|
||||
static void EncodeIntoDma(uint8_t** dmaBuffer, const uint8_t* data, size_t sizeData, uint8_t muxId)
|
||||
{
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
// 1234 - order
|
||||
@ -84,7 +84,7 @@ public:
|
||||
const uint32_t EncodedOneBit = 0x01010001;
|
||||
#endif
|
||||
|
||||
uint32_t* pDma = reinterpret_cast<uint32_t*>(dmaBuffer);
|
||||
uint32_t* pDma = reinterpret_cast<uint32_t*>(*dmaBuffer);
|
||||
const uint8_t* pEnd = data + sizeData;
|
||||
|
||||
for (const uint8_t* pPixel = data; pPixel < pEnd; pPixel++)
|
||||
@ -100,6 +100,8 @@ public:
|
||||
value <<= 1;
|
||||
}
|
||||
}
|
||||
// return the buffer pointer advanced by encoding progress
|
||||
*dmaBuffer = reinterpret_cast<uint8_t*>(pDma);
|
||||
}
|
||||
};
|
||||
|
||||
@ -113,7 +115,7 @@ public:
|
||||
|
||||
const static size_t MuxBusDataSize = 2;
|
||||
|
||||
static void EncodeIntoDma(uint8_t* dmaBuffer, const uint8_t* data, size_t sizeData, uint8_t muxId)
|
||||
static void EncodeIntoDma(uint8_t** dmaBuffer, const uint8_t* data, size_t sizeData, uint8_t muxId)
|
||||
{
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
// 1234 5678 - order
|
||||
@ -177,14 +179,14 @@ public:
|
||||
}
|
||||
|
||||
protected:
|
||||
static void Fillx16(uint8_t* dmaBuffer,
|
||||
static void Fillx16(uint8_t** dmaBuffer,
|
||||
const uint8_t* data,
|
||||
size_t sizeData,
|
||||
uint8_t muxShift,
|
||||
const uint64_t EncodedZeroBit64,
|
||||
const uint64_t EncodedOneBit64)
|
||||
{
|
||||
uint64_t* pDma64 = reinterpret_cast<uint64_t*>(dmaBuffer);
|
||||
uint64_t* pDma64 = reinterpret_cast<uint64_t*>(*dmaBuffer);
|
||||
const uint8_t* pEnd = data + sizeData;
|
||||
|
||||
for (const uint8_t* pPixel = data; pPixel < pEnd; pPixel++)
|
||||
@ -200,6 +202,8 @@ protected:
|
||||
value <<= 1;
|
||||
}
|
||||
}
|
||||
// return the buffer pointer advanced by encoding progress
|
||||
*dmaBuffer = reinterpret_cast<uint8_t*>(pDma64);
|
||||
}
|
||||
};
|
||||
|
||||
@ -403,203 +407,45 @@ public:
|
||||
MuxMap.Reset();
|
||||
}
|
||||
|
||||
void StartWrite(uint8_t i2sBusNumber)
|
||||
void ResetBuffer()
|
||||
{
|
||||
if (MuxMap.IsAllMuxBusesUpdated())
|
||||
// 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())
|
||||
{
|
||||
MuxMap.ResetMuxBusesUpdated();
|
||||
i2sWrite(i2sBusNumber);
|
||||
// clear all the data in preperation for each mux channel to add
|
||||
memset(I2sBuffer, 0x00, I2sBufferSize);
|
||||
}
|
||||
}
|
||||
|
||||
void FillBuffers(const uint8_t* data,
|
||||
void FillBuffer(uint8_t** dmaBuffer,
|
||||
const uint8_t* data,
|
||||
size_t sizeData,
|
||||
uint8_t muxId,
|
||||
uint8_t i2sBusNumber)
|
||||
uint8_t muxId)
|
||||
{
|
||||
// wait for not actively sending data
|
||||
while (!i2sWriteDone(i2sBusNumber))
|
||||
{
|
||||
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 add
|
||||
memset(I2sBuffer, 0x00, I2sBufferSize);
|
||||
}
|
||||
|
||||
MuxMap.EncodeIntoDma(I2sBuffer,
|
||||
MuxMap.EncodeIntoDma(dmaBuffer,
|
||||
data,
|
||||
sizeData,
|
||||
muxId);
|
||||
|
||||
MuxMap.MarkMuxBusUpdated(muxId);
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// Implementation of a Double Buffered version of a I2sContext
|
||||
// Manages the underlying I2S details including the buffer(s)
|
||||
// This creates a front buffer that can be filled while actively sending
|
||||
// the back buffer, thus improving async operation of the i2s DMA.
|
||||
// Note that the back buffer must be DMA memory, a limited resource, so
|
||||
// the front buffer uses normal memory and copies rather than swap pointers
|
||||
//
|
||||
// T_MUXMAP - NeoEspI2sMuxMap - tracking class for mux state
|
||||
//
|
||||
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
|
||||
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
|
||||
NeoEspI2sDblBuffContext()
|
||||
//:
|
||||
//I2sBufferSize(0),
|
||||
//I2sBuffer(nullptr),
|
||||
//I2sEditBuffer(nullptr),
|
||||
//MuxMap()
|
||||
{
|
||||
}
|
||||
|
||||
void Construct(const uint8_t busNumber, uint32_t i2sSampleRate)
|
||||
{
|
||||
// construct only once on first time called
|
||||
if (I2sBuffer == 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)
|
||||
I2sBufferSize = MuxMap.MaxBusDataSize * 8 * DmaBitsPerPixelBit * T_MUXMAP::MuxBusDataSize;
|
||||
|
||||
// must have a 4 byte aligned buffer for i2s
|
||||
uint32_t alignment = I2sBufferSize % 4;
|
||||
if (alignment)
|
||||
{
|
||||
I2sBufferSize += 4 - alignment;
|
||||
}
|
||||
|
||||
size_t dmaBlockCount = (I2sBufferSize + I2S_DMA_MAX_DATA_LEN - 1) / I2S_DMA_MAX_DATA_LEN;
|
||||
|
||||
I2sBuffer = static_cast<uint8_t*>(heap_caps_malloc(I2sBufferSize, MALLOC_CAP_DMA));
|
||||
if (I2sBuffer == nullptr)
|
||||
{
|
||||
log_e("send buffer memory allocation failure (size %u)",
|
||||
I2sBufferSize);
|
||||
}
|
||||
memset(I2sBuffer, 0x00, I2sBufferSize);
|
||||
|
||||
I2sEditBuffer = static_cast<uint8_t*>(malloc(I2sBufferSize));
|
||||
if (I2sEditBuffer == nullptr)
|
||||
{
|
||||
log_e("edit buffer memory allocation failure (size %u)",
|
||||
I2sBufferSize);
|
||||
}
|
||||
memset(I2sEditBuffer, 0x00, I2sBufferSize);
|
||||
|
||||
i2sInit(busNumber,
|
||||
true,
|
||||
T_MUXMAP::MuxBusDataSize,
|
||||
i2sSampleRate,
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
// using these modes on ESP32S2 actually allows it to function
|
||||
// in both x8 and x16
|
||||
I2S_CHAN_STEREO,
|
||||
I2S_FIFO_16BIT_DUAL,
|
||||
#else
|
||||
// but they won't work on ESP32 in parallel mode, but these will
|
||||
I2S_CHAN_RIGHT_TO_LEFT,
|
||||
I2S_FIFO_16BIT_SINGLE,
|
||||
#endif
|
||||
dmaBlockCount,
|
||||
I2sBuffer,
|
||||
I2sBufferSize);
|
||||
}
|
||||
}
|
||||
|
||||
void Destruct(const uint8_t busNumber)
|
||||
{
|
||||
if (I2sBuffer == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
i2sSetPins(busNumber, -1, -1, -1, false);
|
||||
i2sDeinit(busNumber);
|
||||
|
||||
free(I2sEditBuffer);
|
||||
heap_caps_free(I2sBuffer);
|
||||
|
||||
I2sBufferSize = 0;
|
||||
I2sBuffer = nullptr;
|
||||
I2sEditBuffer = nullptr;
|
||||
|
||||
MuxMap.Reset();
|
||||
}
|
||||
|
||||
void StartWrite(uint8_t i2sBusNumber)
|
||||
{
|
||||
if (MuxMap.IsAllMuxBusesUpdated())
|
||||
{
|
||||
MuxMap.ResetMuxBusesUpdated();
|
||||
|
||||
// wait for not actively sending data
|
||||
while (!i2sWriteDone(i2sBusNumber))
|
||||
{
|
||||
yield();
|
||||
}
|
||||
|
||||
// copy edit buffer to sending buffer
|
||||
memcpy(I2sBuffer, I2sEditBuffer, I2sBufferSize);
|
||||
|
||||
i2sWrite(i2sBusNumber);
|
||||
}
|
||||
}
|
||||
|
||||
void FillBuffers(const uint8_t* data,
|
||||
size_t sizeData,
|
||||
uint8_t muxId,
|
||||
uint8_t i2sBusNumber)
|
||||
{
|
||||
// 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 add
|
||||
memset(I2sEditBuffer, 0x00, I2sBufferSize);
|
||||
}
|
||||
|
||||
MuxMap.EncodeIntoDma(I2sEditBuffer,
|
||||
data,
|
||||
sizeData,
|
||||
muxId);
|
||||
|
||||
MuxMap.MarkMuxBusUpdated(muxId);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Implementation of the low level interface into i2s mux bus
|
||||
//
|
||||
// T_BUSCONTEXT - the context to use, currently only NeoEspI2sDblBuffContext 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_BUSCONTEXT - the context to use, currently only NeoEspI2sMonoBuffContext
|
||||
// T_BUS - the bus id, NeoEsp32I2sBusZero, NeoEsp32I2sBusOne
|
||||
//
|
||||
template<typename T_BUSCONTEXT, typename T_BUS>
|
||||
@ -636,24 +482,28 @@ public:
|
||||
_muxId = s_context.MuxMap.InvalidMuxId;
|
||||
}
|
||||
|
||||
void StartWrite()
|
||||
{
|
||||
s_context.StartWrite(T_BUS::I2sBusNumber);
|
||||
}
|
||||
|
||||
bool IsWriteDone() const
|
||||
{
|
||||
return i2sWriteDone(T_BUS::I2sBusNumber);
|
||||
}
|
||||
|
||||
void FillBuffers(const uint8_t* data, size_t sizeData)
|
||||
uint8_t* BeginUpdate()
|
||||
{
|
||||
s_context.FillBuffers(data, sizeData, _muxId, T_BUS::I2sBusNumber);
|
||||
s_context.ResetBuffer();
|
||||
return s_context.I2sBuffer;
|
||||
}
|
||||
|
||||
void MarkUpdated()
|
||||
void FillBuffer(uint8_t** dmaBuffer,
|
||||
const uint8_t* data,
|
||||
size_t sizeData)
|
||||
{
|
||||
s_context.FillBuffer(dmaBuffer, data, sizeData, _muxId);
|
||||
}
|
||||
|
||||
void EndUpdate()
|
||||
{
|
||||
s_context.MuxMap.MarkMuxBusUpdated(_muxId);
|
||||
s_context.StartWrite(T_BUS::I2sBusNumber); // only when all buses are update is actual write started
|
||||
}
|
||||
|
||||
private:
|
||||
@ -677,11 +527,11 @@ public:
|
||||
typedef NeoNoSettings SettingsObject;
|
||||
|
||||
NeoEsp32I2sXMethodBase(uint8_t pin, uint16_t pixelCount, size_t elementSize, size_t settingsSize) :
|
||||
_sizeData(pixelCount * elementSize + settingsSize),
|
||||
_pin(pin),
|
||||
_pixelCount(pixelCount),
|
||||
_bus()
|
||||
{
|
||||
_bus.RegisterNewMuxBus(_sizeData + T_SPEED::ResetTimeUs / T_SPEED::ByteSendTimeUs);
|
||||
_bus.RegisterNewMuxBus((pixelCount * elementSize + settingsSize) + T_SPEED::ResetTimeUs / T_SPEED::ByteSendTimeUs);
|
||||
}
|
||||
|
||||
~NeoEsp32I2sXMethodBase()
|
||||
@ -692,8 +542,6 @@ public:
|
||||
}
|
||||
|
||||
_bus.DeregisterMuxBus(_pin);
|
||||
|
||||
free(_data);
|
||||
}
|
||||
|
||||
bool IsReadyToUpdate() const
|
||||
@ -704,37 +552,65 @@ public:
|
||||
void Initialize()
|
||||
{
|
||||
_bus.Initialize(_pin, T_SPEED::I2sSampleRate, T_INVERT::Inverted);
|
||||
}
|
||||
|
||||
_data = static_cast<uint8_t*>(malloc(_sizeData));
|
||||
if (_data == nullptr)
|
||||
template <typename T_COLOR_OBJECT,
|
||||
typename T_COLOR_FEATURE,
|
||||
typename T_SHADER>
|
||||
void Update(
|
||||
T_COLOR_OBJECT* pixels,
|
||||
size_t countPixels,
|
||||
const typename T_COLOR_FEATURE::SettingsObject& featureSettings,
|
||||
const T_SHADER& shader)
|
||||
{
|
||||
// wait for not actively sending data
|
||||
while (!_bus.IsWriteDone())
|
||||
{
|
||||
log_e("front buffer memory allocation failure");
|
||||
yield();
|
||||
}
|
||||
// data cleared later in Begin()
|
||||
}
|
||||
|
||||
void Update(bool)
|
||||
{
|
||||
_bus.FillBuffers(_data, _sizeData);
|
||||
_bus.StartWrite(); // only triggers actual write after all mux busses have updated
|
||||
}
|
||||
const size_t sendDataSize = T_COLOR_FEATURE::SettingsSize >= T_COLOR_FEATURE::PixelSize ? T_COLOR_FEATURE::SettingsSize : T_COLOR_FEATURE::PixelSize;
|
||||
uint8_t sendData[sendDataSize];
|
||||
uint8_t* data = _bus.BeginUpdate();
|
||||
|
||||
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;
|
||||
}
|
||||
// if there are settings at the front
|
||||
//
|
||||
if (T_COLOR_FEATURE::applyFrontSettings(sendData, sendDataSize, featureSettings))
|
||||
{
|
||||
_bus.FillBuffer(&data, sendData, T_COLOR_FEATURE::SettingsSize);
|
||||
}
|
||||
|
||||
uint8_t* getData() const
|
||||
{
|
||||
return _data;
|
||||
};
|
||||
// apply primary color data
|
||||
//
|
||||
T_COLOR_OBJECT* pixel = pixels;
|
||||
const T_COLOR_OBJECT* pixelEnd = pixel + countPixels;
|
||||
uint16_t stripCount = _pixelCount;
|
||||
|
||||
size_t getDataSize() const
|
||||
{
|
||||
return _sizeData;
|
||||
while (stripCount--)
|
||||
{
|
||||
typename T_COLOR_FEATURE::ColorObject color = shader.Apply(*pixel);
|
||||
T_COLOR_FEATURE::applyPixelColor(sendData, sendDataSize, color);
|
||||
|
||||
_bus.FillBuffer(&data, sendData, T_COLOR_FEATURE::PixelSize);
|
||||
|
||||
pixel++;
|
||||
|
||||
if (pixel >= pixelEnd)
|
||||
{
|
||||
// restart at first
|
||||
pixel = pixels;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// if there are settings at the back
|
||||
//
|
||||
if (T_COLOR_FEATURE::applyBackSettings(sendData, sendDataSize, featureSettings))
|
||||
{
|
||||
_bus.FillBuffer(&data, sendData, T_COLOR_FEATURE::SettingsSize);
|
||||
}
|
||||
|
||||
_bus.EndUpdate(); // triggers actual write after all mux busses have updated
|
||||
}
|
||||
|
||||
void applySettings([[maybe_unused]] const SettingsObject& settings)
|
||||
@ -742,11 +618,10 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
const size_t _sizeData; // Size of '_data' buffer
|
||||
const uint8_t _pin; // output pin number
|
||||
const uint8_t _pin; // output pin number
|
||||
const uint16_t _pixelCount; // count of pixels in the strip
|
||||
|
||||
T_BUS _bus; // holds instance for mux bus support
|
||||
uint8_t* _data; // Holds LED color values
|
||||
};
|
||||
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
@ -754,10 +629,6 @@ private:
|
||||
typedef NeoEsp32I2sMuxBus<NeoEspI2sMonoBuffContext<NeoEspI2sMuxMap<uint8_t, NeoEspI2sMuxBusSize8Bit>>, NeoEsp32I2sBusZero> NeoEsp32I2s0Mux8Bus;
|
||||
typedef NeoEsp32I2sMuxBus<NeoEspI2sMonoBuffContext<NeoEspI2sMuxMap<uint16_t, NeoEspI2sMuxBusSize16Bit>>, NeoEsp32I2sBusZero> NeoEsp32I2s0Mux16Bus;
|
||||
|
||||
typedef NeoEsp32I2sMuxBus<NeoEspI2sDblBuffContext<NeoEspI2sMuxMap<uint8_t, NeoEspI2sMuxBusSize8Bit>>, NeoEsp32I2sBusZero> NeoEsp32I2s0DblMux8Bus;
|
||||
|
||||
typedef NeoEsp32I2sXMethodBase<NeoEsp32I2sSpeedWs2812x, NeoEsp32I2s0DblMux8Bus, NeoEsp32I2sNotInverted> NeoEsp32I2s0X8DblWs2812xMethod;
|
||||
|
||||
#else
|
||||
|
||||
typedef NeoEsp32I2sMuxBus<NeoEspI2sMonoBuffContext<NeoEspI2sMuxMap<uint8_t, NeoEspI2sMuxBusSize16Bit>>, NeoEsp32I2sBusZero> NeoEsp32I2s0Mux8Bus;
|
||||
@ -767,8 +638,6 @@ typedef NeoEsp32I2sMuxBus<NeoEspI2sMonoBuffContext<NeoEspI2sMuxMap<uint16_t, Neo
|
||||
typedef NeoEsp32I2sMuxBus<NeoEspI2sMonoBuffContext<NeoEspI2sMuxMap<uint8_t, NeoEspI2sMuxBusSize8Bit>>, NeoEsp32I2sBusOne> NeoEsp32I2s1Mux8Bus;
|
||||
typedef NeoEsp32I2sMuxBus<NeoEspI2sMonoBuffContext<NeoEspI2sMuxMap<uint16_t, NeoEspI2sMuxBusSize16Bit>>, NeoEsp32I2sBusOne> NeoEsp32I2s1Mux16Bus;
|
||||
|
||||
typedef NeoEsp32I2sMuxBus<NeoEspI2sDblBuffContext<NeoEspI2sMuxMap<uint8_t, NeoEspI2sMuxBusSize8Bit>>, NeoEsp32I2sBusOne> NeoEsp32I2s1DblMux8Bus;
|
||||
|
||||
#endif
|
||||
|
||||
// NORMAL
|
||||
@ -815,8 +684,6 @@ typedef NeoEsp32I2s0X16Sk6812Method NeoEsp32I2s0X16Lc8812Method;
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
|
||||
// I2s1x8
|
||||
typedef NeoEsp32I2sXMethodBase<NeoEsp32I2sSpeedWs2812x, NeoEsp32I2s1DblMux8Bus, NeoEsp32I2sNotInverted> NeoEsp32I2s1X8DblWs2812xMethod;
|
||||
|
||||
typedef NeoEsp32I2sXMethodBase<NeoEsp32I2sSpeedWs2812x, NeoEsp32I2s1Mux8Bus, NeoEsp32I2sNotInverted> NeoEsp32I2s1X8Ws2812xMethod;
|
||||
typedef NeoEsp32I2sXMethodBase<NeoEsp32I2sSpeedWs2805, NeoEsp32I2s1Mux8Bus, NeoEsp32I2sNotInverted> NeoEsp32I2s1X8Ws2805Method;
|
||||
typedef NeoEsp32I2sXMethodBase<NeoEsp32I2sSpeedSk6812, NeoEsp32I2s1Mux8Bus, NeoEsp32I2sNotInverted> NeoEsp32I2s1X8Sk6812Method;
|
||||
|
@ -28,47 +28,15 @@ License along with NeoPixel. If not, see
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "..\NeoUtil.h"
|
||||
|
||||
#if ESP_IDF_VERSION_MAJOR>=5
|
||||
#include <soc/gpio_struct.h>
|
||||
#endif
|
||||
|
||||
static inline uint32_t getCycleCount(void)
|
||||
{
|
||||
uint32_t ccount;
|
||||
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
__asm__ __volatile__("csrr %0,0x7e2":"=r" (ccount));
|
||||
//ccount = esp_cpu_get_ccount();
|
||||
#else
|
||||
__asm__ __volatile__("rsr %0,ccount":"=a" (ccount));
|
||||
#endif
|
||||
return ccount;
|
||||
}
|
||||
|
||||
// Interrupt lock class, used for RAII interrupt disabling
|
||||
class InterruptLock {
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
portMUX_TYPE updateMux;
|
||||
#endif
|
||||
|
||||
inline void lock()
|
||||
{
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
portENTER_CRITICAL(&updateMux);
|
||||
#else
|
||||
noInterrupts();
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void unlock()
|
||||
{
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
portEXIT_CRITICAL(&updateMux);
|
||||
#else
|
||||
interrupts();
|
||||
#endif
|
||||
}
|
||||
|
||||
class InterruptLock
|
||||
{
|
||||
public:
|
||||
|
||||
inline void poll()
|
||||
@ -79,7 +47,7 @@ public:
|
||||
|
||||
inline InterruptLock()
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
: updateMux(portMUX_INITIALIZER_UNLOCKED)
|
||||
: _updateMux(portMUX_INITIALIZER_UNLOCKED)
|
||||
#endif
|
||||
{
|
||||
lock();
|
||||
@ -89,23 +57,46 @@ public:
|
||||
{
|
||||
unlock();
|
||||
}
|
||||
|
||||
private:
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
portMUX_TYPE _updateMux;
|
||||
#endif
|
||||
|
||||
inline void lock()
|
||||
{
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
portENTER_CRITICAL(&_updateMux);
|
||||
#else
|
||||
noInterrupts();
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void unlock()
|
||||
{
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
portEXIT_CRITICAL(&_updateMux);
|
||||
#else
|
||||
interrupts();
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
bool IRAM_ATTR neoEspBitBangWriteSpacingPixels(const uint8_t* pixels,
|
||||
const uint8_t* end,
|
||||
__attribute__((flatten))
|
||||
uint32_t IRAM_ATTR neoEspBitBangWriteSpacingPixels(const uint8_t* data,
|
||||
size_t dataSize,
|
||||
uint8_t pin,
|
||||
uint32_t t0h,
|
||||
uint32_t t1h,
|
||||
uint32_t period,
|
||||
size_t sizePixel,
|
||||
uint32_t tLatch,
|
||||
bool invert)
|
||||
{
|
||||
const uint8_t* dataEnd = data + dataSize;
|
||||
uint32_t setValue = _BV(pin);
|
||||
uint32_t clearValue = _BV(pin);
|
||||
uint8_t mask = 0x80;
|
||||
uint8_t subpix = *pixels++;
|
||||
uint8_t element = 0;
|
||||
uint8_t subpix = *data++;
|
||||
|
||||
uint32_t cyclesStart = 0; // trigger emediately
|
||||
uint32_t cyclesNext = 0;
|
||||
|
||||
@ -153,7 +144,7 @@ bool IRAM_ATTR neoEspBitBangWriteSpacingPixels(const uint8_t* pixels,
|
||||
|
||||
// after we have done as much work as needed for this next bit
|
||||
// now wait for the HIGH
|
||||
while (((cyclesStart = getCycleCount()) - cyclesNext) < period);
|
||||
while (((cyclesStart = getEspCycleCount()) - cyclesNext) < period);
|
||||
|
||||
// set pin state
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
@ -163,7 +154,7 @@ bool IRAM_ATTR neoEspBitBangWriteSpacingPixels(const uint8_t* pixels,
|
||||
#endif
|
||||
|
||||
// wait for the LOW
|
||||
while ((getCycleCount() - cyclesStart) < cyclesBit);
|
||||
while ((getEspCycleCount() - cyclesStart) < cyclesBit);
|
||||
|
||||
// reset pin start
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
@ -180,37 +171,18 @@ bool IRAM_ATTR neoEspBitBangWriteSpacingPixels(const uint8_t* pixels,
|
||||
{
|
||||
// no more bits to send in this byte
|
||||
// check for another byte
|
||||
if (pixels >= end)
|
||||
if (data >= dataEnd)
|
||||
{
|
||||
// no more bytes to send so stop
|
||||
break;
|
||||
}
|
||||
// reset mask to first bit and get the next byte
|
||||
mask = 0x80;
|
||||
subpix = *pixels++;
|
||||
|
||||
// Hack: permit interrupts to fire
|
||||
// If we get held up more than the latch period, stop.
|
||||
// We do this at the end of an element to ensure that each
|
||||
// element always gets valid data, even if we don't update
|
||||
// every element.
|
||||
if (tLatch)
|
||||
{
|
||||
++element;
|
||||
if (element == sizePixel)
|
||||
{
|
||||
isrGuard.poll();
|
||||
if ((getCycleCount() - cyclesNext) > tLatch)
|
||||
{
|
||||
return false; // failed
|
||||
}
|
||||
element = 0;
|
||||
}
|
||||
}
|
||||
subpix = *data++;
|
||||
}
|
||||
}
|
||||
|
||||
return true; // update complete
|
||||
return getEspCycleCount();
|
||||
}
|
||||
|
||||
|
||||
|
@ -39,14 +39,12 @@ License along with NeoPixel. If not, see
|
||||
#define CYCLES_LOOPTEST (4) // adjustment due to loop exit test instruction cycles
|
||||
#endif
|
||||
|
||||
extern bool neoEspBitBangWriteSpacingPixels(const uint8_t* pixels,
|
||||
const uint8_t* end,
|
||||
uint8_t pin,
|
||||
uint32_t t0h,
|
||||
uint32_t t1h,
|
||||
extern bool neoEspBitBangWriteSpacingPixels(const uint8_t* data,
|
||||
size_t dataSize,
|
||||
uint8_t pin,
|
||||
uint32_t t0h,
|
||||
uint32_t t1h,
|
||||
uint32_t period,
|
||||
size_t sizePixel,
|
||||
uint32_t tLatch,
|
||||
bool invert);
|
||||
|
||||
|
||||
@ -188,27 +186,25 @@ public:
|
||||
const static uint32_t TLatch = 0;
|
||||
};
|
||||
|
||||
template<typename T_SPEED, typename T_INVERTED, bool V_INTER_PIXEL_ISR> class NeoEspBitBangMethodBase
|
||||
template<typename T_SPEED, typename T_INVERTED, bool V_INTER_PIXEL_ISR>
|
||||
class NeoEspBitBangMethodBase
|
||||
{
|
||||
public:
|
||||
typedef NeoNoSettings SettingsObject;
|
||||
|
||||
NeoEspBitBangMethodBase(uint8_t pin, uint16_t pixelCount, size_t elementSize, size_t settingsSize) :
|
||||
_sizePixel(elementSize),
|
||||
_sizeData(pixelCount * elementSize + settingsSize),
|
||||
_pin(pin)
|
||||
NeoEspBitBangMethodBase(uint8_t pin,
|
||||
uint16_t pixelCount,
|
||||
[[maybe_unused]] size_t elementSize,
|
||||
[[maybe_unused]] size_t settingsSize) :
|
||||
_pin(pin),
|
||||
_pixelCount(pixelCount)
|
||||
{
|
||||
pinMode(pin, OUTPUT);
|
||||
|
||||
_data = static_cast<uint8_t*>(malloc(_sizeData));
|
||||
// data cleared later in Begin()
|
||||
}
|
||||
|
||||
~NeoEspBitBangMethodBase()
|
||||
{
|
||||
pinMode(_pin, INPUT);
|
||||
|
||||
free(_data);
|
||||
}
|
||||
|
||||
bool IsReadyToUpdate() const
|
||||
@ -225,10 +221,18 @@ public:
|
||||
_endTime = micros();
|
||||
}
|
||||
|
||||
void Update(bool)
|
||||
template <typename T_COLOR_OBJECT,
|
||||
typename T_COLOR_FEATURE,
|
||||
typename T_SHADER>
|
||||
void Update(
|
||||
T_COLOR_OBJECT* pixels,
|
||||
size_t countPixels,
|
||||
const typename T_COLOR_FEATURE::SettingsObject& featureSettings,
|
||||
const T_SHADER& shader)
|
||||
{
|
||||
bool done = false;
|
||||
for (unsigned retries = 0; !done && retries < 4; ++retries)
|
||||
bool wasInterrupted = true;
|
||||
|
||||
for (unsigned retries = 0; wasInterrupted && retries < 4; ++retries)
|
||||
{
|
||||
// Data latch = 50+ microsecond pause in the output stream. Rather than
|
||||
// put a delay at the end of the function, the ending time is noted and
|
||||
@ -241,45 +245,91 @@ public:
|
||||
yield(); // allows for system yield if needed
|
||||
}
|
||||
|
||||
done = neoEspBitBangWriteSpacingPixels(_data,
|
||||
_data + _sizeData,
|
||||
_pin,
|
||||
T_SPEED::T0H,
|
||||
T_SPEED::T1H,
|
||||
T_SPEED::Period,
|
||||
_sizePixel,
|
||||
NeoEspTLatch<T_SPEED, V_INTER_PIXEL_ISR>::TLatch,
|
||||
T_INVERTED::IdleLevel);
|
||||
wasInterrupted = false;
|
||||
|
||||
const size_t sendDataSize = T_COLOR_FEATURE::SettingsSize >= T_COLOR_FEATURE::PixelSize ? T_COLOR_FEATURE::SettingsSize : T_COLOR_FEATURE::PixelSize;
|
||||
uint8_t sendData[sendDataSize];
|
||||
uint32_t cycleLast = getEspCycleCount();
|
||||
|
||||
// if there are settings at the front
|
||||
//
|
||||
if (T_COLOR_FEATURE::applyFrontSettings(sendData, sendDataSize, featureSettings))
|
||||
{
|
||||
cycleLast = neoEspBitBangWriteSpacingPixels(sendData,
|
||||
T_COLOR_FEATURE::SettingsSize,
|
||||
_pin,
|
||||
T_SPEED::T0H,
|
||||
T_SPEED::T1H,
|
||||
T_SPEED::Period,
|
||||
T_INVERTED::IdleLevel);
|
||||
}
|
||||
|
||||
// send primary color data
|
||||
//
|
||||
T_COLOR_OBJECT* pixel = pixels;
|
||||
const T_COLOR_OBJECT* pixelEnd = pixel + countPixels;
|
||||
uint16_t stripCount = _pixelCount;
|
||||
|
||||
while (!wasInterrupted && stripCount--)
|
||||
{
|
||||
typename T_COLOR_FEATURE::ColorObject color = shader.Apply(*pixel);
|
||||
T_COLOR_FEATURE::applyPixelColor(sendData, sendDataSize, color);
|
||||
|
||||
if ((getEspCycleCount() - cycleLast) > NeoEspTLatch<T_SPEED, V_INTER_PIXEL_ISR>::TLatch)
|
||||
{
|
||||
wasInterrupted = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
cycleLast = neoEspBitBangWriteSpacingPixels(sendData,
|
||||
T_COLOR_FEATURE::PixelSize,
|
||||
_pin,
|
||||
T_SPEED::T0H,
|
||||
T_SPEED::T1H,
|
||||
T_SPEED::Period,
|
||||
T_INVERTED::IdleLevel);
|
||||
|
||||
pixel++;
|
||||
if (pixel >= pixelEnd)
|
||||
{
|
||||
// restart at first
|
||||
pixel = pixels;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if there are settings at the back
|
||||
//
|
||||
if (!wasInterrupted && T_COLOR_FEATURE::applyBackSettings(sendData, sendDataSize, featureSettings))
|
||||
{
|
||||
if ((getEspCycleCount() - cycleLast) > NeoEspTLatch<T_SPEED, V_INTER_PIXEL_ISR>::TLatch)
|
||||
{
|
||||
wasInterrupted = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
cycleLast = neoEspBitBangWriteSpacingPixels(sendData,
|
||||
T_COLOR_FEATURE::SettingsSize,
|
||||
_pin,
|
||||
T_SPEED::T0H,
|
||||
T_SPEED::T1H,
|
||||
T_SPEED::Period,
|
||||
T_INVERTED::IdleLevel);
|
||||
}
|
||||
}
|
||||
|
||||
// save EOD time for latch on next call
|
||||
_endTime = micros();
|
||||
}
|
||||
}
|
||||
|
||||
bool AlwaysUpdate()
|
||||
{
|
||||
// this method requires update to be called only if changes to buffer
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t* getData() const
|
||||
{
|
||||
return _data;
|
||||
};
|
||||
|
||||
size_t getDataSize() const
|
||||
{
|
||||
return _sizeData;
|
||||
};
|
||||
|
||||
void applySettings([[maybe_unused]] const SettingsObject& settings)
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
const size_t _sizePixel; // size of a pixel in _data
|
||||
const size_t _sizeData; // Size of '_data' buffer below
|
||||
const uint8_t _pin; // output pin number
|
||||
const uint16_t _pixelCount; // count of pixels in the strip
|
||||
|
||||
uint32_t _endTime; // Latch timing reference
|
||||
uint8_t* _data; // Holds LED color values
|
||||
|
Reference in New Issue
Block a user