From 2bbf213d5c7428dd58f985851a0e5a07bb78dd9c Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Sat, 20 Apr 2024 09:45:19 -0700 Subject: [PATCH] esp32 full --- library.json | 2 +- library.properties | 2 +- src/internal/NeoUtil.h | 19 ++ src/internal/methods/NeoAvrMethod.h | 2 +- src/internal/methods/NeoEsp32I2sXMethod.h | 311 ++++++------------- src/internal/methods/NeoEspBitBangMethod.cpp | 106 +++---- src/internal/methods/NeoEspBitBangMethod.h | 144 ++++++--- 7 files changed, 247 insertions(+), 339 deletions(-) diff --git a/library.json b/library.json index dd4dd34..f24d31e 100644 --- a/library.json +++ b/library.json @@ -7,7 +7,7 @@ "type": "git", "url": "https://github.com/Makuna/NeoPixelBus" }, - "version": "2.9.0", + "version": "2.9.1", "frameworks": "arduino", "platforms": "*", "dependencies": [ diff --git a/library.properties b/library.properties index 65d122c..cc40e67 100644 --- a/library.properties +++ b/library.properties @@ -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. diff --git a/src/internal/NeoUtil.h b/src/internal/NeoUtil.h index 571f2b2..8d74ca4 100644 --- a/src/internal/NeoUtil.h +++ b/src/internal/NeoUtil.h @@ -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 diff --git a/src/internal/methods/NeoAvrMethod.h b/src/internal/methods/NeoAvrMethod.h index adc5653..649d0bd 100644 --- a/src/internal/methods/NeoAvrMethod.h +++ b/src/internal/methods/NeoAvrMethod.h @@ -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), diff --git a/src/internal/methods/NeoEsp32I2sXMethod.h b/src/internal/methods/NeoEsp32I2sXMethod.h index e6960b2..87fd804 100644 --- a/src/internal/methods/NeoEsp32I2sXMethod.h +++ b/src/internal/methods/NeoEsp32I2sXMethod.h @@ -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(dmaBuffer); + uint32_t* pDma = reinterpret_cast(*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(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(dmaBuffer); + uint64_t* pDma64 = reinterpret_cast(*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(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 -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(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(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 @@ -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(malloc(_sizeData)); - if (_data == nullptr) + template + 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>, NeoEsp32I2sBusZero> NeoEsp32I2s0Mux8Bus; typedef NeoEsp32I2sMuxBus>, NeoEsp32I2sBusZero> NeoEsp32I2s0Mux16Bus; -typedef NeoEsp32I2sMuxBus>, NeoEsp32I2sBusZero> NeoEsp32I2s0DblMux8Bus; - -typedef NeoEsp32I2sXMethodBase NeoEsp32I2s0X8DblWs2812xMethod; - #else typedef NeoEsp32I2sMuxBus>, NeoEsp32I2sBusZero> NeoEsp32I2s0Mux8Bus; @@ -767,8 +638,6 @@ typedef NeoEsp32I2sMuxBus>, NeoEsp32I2sBusOne> NeoEsp32I2s1Mux8Bus; typedef NeoEsp32I2sMuxBus>, NeoEsp32I2sBusOne> NeoEsp32I2s1Mux16Bus; -typedef NeoEsp32I2sMuxBus>, NeoEsp32I2sBusOne> NeoEsp32I2s1DblMux8Bus; - #endif // NORMAL @@ -815,8 +684,6 @@ typedef NeoEsp32I2s0X16Sk6812Method NeoEsp32I2s0X16Lc8812Method; #if !defined(CONFIG_IDF_TARGET_ESP32S2) // I2s1x8 -typedef NeoEsp32I2sXMethodBase NeoEsp32I2s1X8DblWs2812xMethod; - typedef NeoEsp32I2sXMethodBase NeoEsp32I2s1X8Ws2812xMethod; typedef NeoEsp32I2sXMethodBase NeoEsp32I2s1X8Ws2805Method; typedef NeoEsp32I2sXMethodBase NeoEsp32I2s1X8Sk6812Method; diff --git a/src/internal/methods/NeoEspBitBangMethod.cpp b/src/internal/methods/NeoEspBitBangMethod.cpp index 7021dd6..f013674 100644 --- a/src/internal/methods/NeoEspBitBangMethod.cpp +++ b/src/internal/methods/NeoEspBitBangMethod.cpp @@ -28,47 +28,15 @@ License along with NeoPixel. If not, see #include +#include "..\NeoUtil.h" + #if ESP_IDF_VERSION_MAJOR>=5 #include #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(); } diff --git a/src/internal/methods/NeoEspBitBangMethod.h b/src/internal/methods/NeoEspBitBangMethod.h index 237d7b1..770d62b 100644 --- a/src/internal/methods/NeoEspBitBangMethod.h +++ b/src/internal/methods/NeoEspBitBangMethod.h @@ -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 class NeoEspBitBangMethodBase +template +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(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 + 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::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::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::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