diff --git a/lib/NeoESP32RmtHI/include/NeoEsp32RmtHIMethod.h b/lib/NeoESP32RmtHI/include/NeoEsp32RmtHIMethod.h
deleted file mode 100644
index 273933e3b8..0000000000
--- a/lib/NeoESP32RmtHI/include/NeoEsp32RmtHIMethod.h
+++ /dev/null
@@ -1,471 +0,0 @@
-/*-------------------------------------------------------------------------
-NeoPixel driver for ESP32 RMTs using High-priority Interrupt
-
-(NB. This cannot be mixed with the non-HI driver.)
-
-Written by Will M. Miles.
-
-I invest time and resources providing this open source code,
-please support me by donating (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
-.
--------------------------------------------------------------------------*/
-
-#pragma once
-
-#if defined(ARDUINO_ARCH_ESP32)
-#if !defined(WLED_USE_SHARED_RMT) // V5 fix: don't compile this file on unsupported platforms
-
-// Use the NeoEspRmtSpeed types from the driver-based implementation
-#include
-
-
-namespace NeoEsp32RmtHiMethodDriver {
- // Install the driver for a specific channel, specifying timing properties
- esp_err_t Install(rmt_channel_t channel, uint32_t rmtBit0, uint32_t rmtBit1, uint32_t resetDuration);
-
- // Remove the driver on a specific channel
- esp_err_t Uninstall(rmt_channel_t channel);
-
- // Write a buffer of data to a specific channel.
- // Buffer reference is held until write completes.
- esp_err_t Write(rmt_channel_t channel, const uint8_t *src, size_t src_size);
-
- // Wait until transaction is complete.
- esp_err_t WaitForTxDone(rmt_channel_t channel, TickType_t wait_time);
-};
-
-template class NeoEsp32RmtHIMethodBase
-{
-public:
- typedef NeoNoSettings SettingsObject;
-
- NeoEsp32RmtHIMethodBase(uint8_t pin, uint16_t pixelCount, size_t elementSize, size_t settingsSize) :
- _sizeData(pixelCount * elementSize + settingsSize),
- _pin(pin)
- {
- construct();
- }
-
- NeoEsp32RmtHIMethodBase(uint8_t pin, uint16_t pixelCount, size_t elementSize, size_t settingsSize, NeoBusChannel channel) :
- _sizeData(pixelCount* elementSize + settingsSize),
- _pin(pin),
- _channel(channel)
- {
- construct();
- }
-
- ~NeoEsp32RmtHIMethodBase()
- {
- // wait until the last send finishes before destructing everything
- // arbitrary time out of 10 seconds
- ESP_ERROR_CHECK_WITHOUT_ABORT(NeoEsp32RmtHiMethodDriver::WaitForTxDone(_channel.RmtChannelNumber, 10000 / portTICK_PERIOD_MS));
-
- ESP_ERROR_CHECK(NeoEsp32RmtHiMethodDriver::Uninstall(_channel.RmtChannelNumber));
-
- gpio_matrix_out(_pin, SIG_GPIO_OUT_IDX, false, false);
- pinMode(_pin, INPUT);
-
- free(_dataEditing);
- free(_dataSending);
- }
-
- bool IsReadyToUpdate() const
- {
- return (ESP_OK == ESP_ERROR_CHECK_WITHOUT_ABORT_SILENT_TIMEOUT(NeoEsp32RmtHiMethodDriver::WaitForTxDone(_channel.RmtChannelNumber, 0)));
- }
-
- void Initialize()
- {
- rmt_config_t config = {};
-
- config.rmt_mode = RMT_MODE_TX;
- config.channel = _channel.RmtChannelNumber;
- config.gpio_num = static_cast(_pin);
- config.mem_block_num = 1;
- config.tx_config.loop_en = false;
-
- config.tx_config.idle_output_en = true;
- config.tx_config.idle_level = T_SPEED::IdleLevel;
-
- config.tx_config.carrier_en = false;
- config.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW;
-
- config.clk_div = T_SPEED::RmtClockDivider;
-
- ESP_ERROR_CHECK(rmt_config(&config)); // Uses ESP library
- ESP_ERROR_CHECK(NeoEsp32RmtHiMethodDriver::Install(_channel.RmtChannelNumber, T_SPEED::RmtBit0, T_SPEED::RmtBit1, T_SPEED::RmtDurationReset));
- }
-
- void Update(bool maintainBufferConsistency)
- {
- // wait for not actively sending data
- // this will time out at 10 seconds, an arbitrarily long period of time
- // and do nothing if this happens
- if (ESP_OK == ESP_ERROR_CHECK_WITHOUT_ABORT(NeoEsp32RmtHiMethodDriver::WaitForTxDone(_channel.RmtChannelNumber, 10000 / portTICK_PERIOD_MS)))
- {
- // now start the RMT transmit with the editing buffer before we swap
- ESP_ERROR_CHECK_WITHOUT_ABORT(NeoEsp32RmtHiMethodDriver::Write(_channel.RmtChannelNumber, _dataEditing, _sizeData));
-
- if (maintainBufferConsistency)
- {
- // copy editing to sending,
- // this maintains the contract that "colors present before will
- // be the same after", otherwise GetPixelColor will be inconsistent
- memcpy(_dataSending, _dataEditing, _sizeData);
- }
-
- // swap so the user can modify without affecting the async operation
- std::swap(_dataSending, _dataEditing);
- }
- }
-
- bool AlwaysUpdate()
- {
- // this method requires update to be called only if changes to buffer
- return false;
- }
-
- bool SwapBuffers()
- {
- std::swap(_dataSending, _dataEditing);
- return true;
- }
-
- uint8_t* getData() const
- {
- return _dataEditing;
- };
-
- size_t getDataSize() const
- {
- return _sizeData;
- }
-
- void applySettings([[maybe_unused]] const SettingsObject& settings)
- {
- }
-
-private:
- const size_t _sizeData; // Size of '_data*' buffers
- const uint8_t _pin; // output pin number
- const T_CHANNEL _channel; // holds instance for multi channel support
-
- // Holds data stream which include LED color values and other settings as needed
- uint8_t* _dataEditing; // exposed for get and set
- uint8_t* _dataSending; // used for async send using RMT
-
-
- void construct()
- {
- _dataEditing = static_cast(malloc(_sizeData));
- // data cleared later in Begin()
-
- _dataSending = static_cast(malloc(_sizeData));
- // no need to initialize it, it gets overwritten on every send
- }
-};
-
-// normal
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINWs2811Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINWs2812xMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINWs2816Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINWs2805Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINSk6812Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINTm1814Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINTm1829Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINTm1914Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINApa106Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINTx1812Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINGs1903Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHIN800KbpsMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHIN400KbpsMethod;
-typedef NeoEsp32RmtHINWs2805Method NeoEsp32RmtHINWs2814Method;
-
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Ws2811Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Ws2812xMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Ws2816Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Ws2805Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Sk6812Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Tm1814Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Tm1829Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Tm1914Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Apa106Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Tx1812Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Gs1903Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0800KbpsMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0400KbpsMethod;
-typedef NeoEsp32RmtHI0Ws2805Method NeoEsp32RmtHI0Ws2814Method;
-
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Ws2811Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Ws2812xMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Ws2816Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Ws2805Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Sk6812Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Tm1814Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Tm1829Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Tm1914Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Apa106Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Tx1812Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Gs1903Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1800KbpsMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1400KbpsMethod;
-typedef NeoEsp32RmtHI1Ws2805Method NeoEsp32RmtHI1Ws2814Method;
-
-#if !defined(CONFIG_IDF_TARGET_ESP32C3)
-
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Ws2811Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Ws2812xMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Ws2816Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Ws2805Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Sk6812Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Tm1814Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Tm1829Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Tm1914Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Apa106Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Tx1812Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Gs1903Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2800KbpsMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2400KbpsMethod;
-typedef NeoEsp32RmtHI2Ws2805Method NeoEsp32RmtHI2Ws2814Method;
-
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Ws2811Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Ws2812xMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Ws2816Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Ws2805Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Sk6812Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Tm1814Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Tm1829Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Tm1914Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Apa106Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Tx1812Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Gs1903Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3800KbpsMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3400KbpsMethod;
-typedef NeoEsp32RmtHI3Ws2805Method NeoEsp32RmtHI3Ws2814Method;
-
-#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32S3)
-
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Ws2811Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Ws2812xMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Ws2816Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Ws2805Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Sk6812Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Tm1814Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Tm1829Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Tm1914Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Apa106Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Tx1812Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Gs1903Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4800KbpsMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4400KbpsMethod;
-typedef NeoEsp32RmtHI4Ws2805Method NeoEsp32RmtHI4Ws2814Method;
-
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Ws2811Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Ws2812xMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Ws2816Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Ws2805Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Sk6812Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Tm1814Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Tm1829Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Tm1914Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Apa106Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Tx1812Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Gs1903Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5800KbpsMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5400KbpsMethod;
-typedef NeoEsp32RmtHI5Ws2805Method NeoEsp32RmtHI5Ws2814Method;
-
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Ws2811Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Ws2812xMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Ws2816Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Ws2805Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Sk6812Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Tm1814Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Tm1829Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Tm1914Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Apa106Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Tx1812Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Gs1903Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6800KbpsMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6400KbpsMethod;
-typedef NeoEsp32RmtHI6Ws2805Method NeoEsp32RmtHI6Ws2814Method;
-
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Ws2811Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Ws2812xMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Ws2816Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Ws2805Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Sk6812Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Tm1814Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Tm1829Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Tm1914Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Apa106Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Tx1812Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Gs1903Method;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7800KbpsMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7400KbpsMethod;
-typedef NeoEsp32RmtHI7Ws2805Method NeoEsp32RmtHI7Ws2814Method;
-
-#endif // !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32S3)
-#endif // !defined(CONFIG_IDF_TARGET_ESP32C3)
-
-// inverted
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINWs2811InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINWs2812xInvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINWs2816InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINWs2805InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINSk6812InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINTm1814InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINTm1829InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINTm1914InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINApa106InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINTx1812InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHINGs1903InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHIN800KbpsInvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHIN400KbpsInvertedMethod;
-typedef NeoEsp32RmtHINWs2805InvertedMethod NeoEsp32RmtHINWs2814InvertedMethod;
-
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Ws2811InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Ws2812xInvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Ws2816InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Ws2805InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Sk6812InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Tm1814InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Tm1829InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Tm1914InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Apa106InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Tx1812InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0Gs1903InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0800KbpsInvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI0400KbpsInvertedMethod;
-typedef NeoEsp32RmtHI0Ws2805InvertedMethod NeoEsp32RmtHI0Ws2814InvertedMethod;
-
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Ws2811InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Ws2812xInvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Ws2816InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Ws2805InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Sk6812InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Tm1814InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Tm1829InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Tm1914InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Apa106InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Tx1812InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1Gs1903InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1800KbpsInvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI1400KbpsInvertedMethod;
-typedef NeoEsp32RmtHI1Ws2805InvertedMethod NeoEsp32RmtHI1Ws2814InvertedMethod;
-
-#if !defined(CONFIG_IDF_TARGET_ESP32C3)
-
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Ws2811InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Ws2812xInvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Ws2816InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Ws2805InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Sk6812InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Tm1814InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Tm1829InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Tm1914InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Apa106InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Tx1812InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2Gs1903InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2800KbpsInvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI2400KbpsInvertedMethod;
-typedef NeoEsp32RmtHI2Ws2805InvertedMethod NeoEsp32RmtHI2Ws2814InvertedMethod;
-
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Ws2811InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Ws2812xInvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Ws2805InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Ws2816InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Sk6812InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Tm1814InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Tm1829InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Tm1914InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Apa106InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Tx1812InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3Gs1903InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3800KbpsInvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI3400KbpsInvertedMethod;
-typedef NeoEsp32RmtHI3Ws2805InvertedMethod NeoEsp32RmtHI3Ws2814InvertedMethod;
-
-#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32S3)
-
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Ws2811InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Ws2812xInvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Ws2816InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Ws2805InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Sk6812InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Tm1814InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Tm1829InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Tm1914InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Apa106InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Tx1812InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4Gs1903InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4800KbpsInvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI4400KbpsInvertedMethod;
-typedef NeoEsp32RmtHI4Ws2805InvertedMethod NeoEsp32RmtHI4Ws2814InvertedMethod;
-
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Ws2811InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Ws2812xInvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Ws2816InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Ws2805InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Sk6812InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Tm1814InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Tm1829InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Tm1914InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Apa106InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Tx1812InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5Gs1903InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5800KbpsInvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI5400KbpsInvertedMethod;
-typedef NeoEsp32RmtHI5Ws2805InvertedMethod NeoEsp32RmtHI5Ws2814InvertedMethod;
-
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Ws2811InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Ws2812xInvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Ws2816InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Ws2805InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Sk6812InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Tm1814InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Tm1829InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Tm1914InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Apa106InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Tx1812InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6Gs1903InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6800KbpsInvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI6400KbpsInvertedMethod;
-typedef NeoEsp32RmtHI6Ws2805InvertedMethod NeoEsp32RmtHI6Ws2814InvertedMethod;
-
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Ws2811InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Ws2812xInvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Ws2816InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Ws2805InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Sk6812InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Tm1814InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Tm1829InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Tm1914InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Apa106InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Tx1812InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7Gs1903InvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7800KbpsInvertedMethod;
-typedef NeoEsp32RmtHIMethodBase NeoEsp32RmtHI7400KbpsInvertedMethod;
-typedef NeoEsp32RmtHI7Ws2805InvertedMethod NeoEsp32RmtHI7Ws2814InvertedMethod;
-
-#endif // !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32S3)
-#endif // !defined(CONFIG_IDF_TARGET_ESP32C3)
-
-#endif
-#endif
diff --git a/lib/NeoESP32RmtHI/library.json b/lib/NeoESP32RmtHI/library.json
deleted file mode 100644
index 0608e59e12..0000000000
--- a/lib/NeoESP32RmtHI/library.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "name": "NeoESP32RmtHI",
- "build": { "libArchive": false },
- "platforms": ["espressif32"],
- "dependencies": [
- {
- "owner": "makuna",
- "name": "NeoPixelBus",
- "version": "^2.8.3"
- }
- ]
-}
diff --git a/lib/NeoESP32RmtHI/src/NeoEsp32RmtHIMethod.cpp b/lib/NeoESP32RmtHI/src/NeoEsp32RmtHIMethod.cpp
deleted file mode 100644
index 6c046943b5..0000000000
--- a/lib/NeoESP32RmtHI/src/NeoEsp32RmtHIMethod.cpp
+++ /dev/null
@@ -1,507 +0,0 @@
-/*-------------------------------------------------------------------------
-NeoPixel library helper functions for Esp32.
-
-A BIG thanks to Andreas Merkle for the investigation and implementation of
-a workaround to the GCC bug that drops method attributes from template methods
-
-Written by Michael C. Miller.
-
-I invest time and resources providing this open source code,
-please support me by donating (see https://github.com/Makuna/NeoPixelBus)
-
--------------------------------------------------------------------------
-This file is part of the Makuna/NeoPixelBus library.
-
-NeoPixelBus is free software: you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as
-published by the Free Software Foundation, either version 3 of
-the License, or (at your option) any later version.
-
-NeoPixelBus is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public
-License along with NeoPixel. If not, see
-.
--------------------------------------------------------------------------*/
-
-#include
-
-#if defined(ARDUINO_ARCH_ESP32) && ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
-
-#include
-#include "esp_idf_version.h"
-#include "NeoEsp32RmtHIMethod.h"
-#include "soc/soc.h"
-#include "soc/rmt_reg.h"
-
-#ifdef __riscv
-#include "riscv/interrupt.h"
-#endif
-
-
-#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0)
-#include "hal/rmt_ll.h"
-#else
-/* Shims for older ESP-IDF v3; we can safely assume original ESP32 */
-#include "soc/rmt_struct.h"
-
-// Selected RMT API functions borrowed from ESP-IDF v4.4.8
-// components/hal/esp32/include/hal/rmt_ll.h
-// Copyright 2019 Espressif Systems (Shanghai) PTE LTD
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-__attribute__((always_inline))
-static inline void rmt_ll_tx_reset_pointer(rmt_dev_t *dev, uint32_t channel)
-{
- dev->conf_ch[channel].conf1.mem_rd_rst = 1;
- dev->conf_ch[channel].conf1.mem_rd_rst = 0;
-}
-
-__attribute__((always_inline))
-static inline void rmt_ll_tx_start(rmt_dev_t *dev, uint32_t channel)
-{
- dev->conf_ch[channel].conf1.tx_start = 1;
-}
-
-__attribute__((always_inline))
-static inline void rmt_ll_tx_stop(rmt_dev_t *dev, uint32_t channel)
-{
- RMTMEM.chan[channel].data32[0].val = 0;
- dev->conf_ch[channel].conf1.tx_start = 0;
- dev->conf_ch[channel].conf1.mem_rd_rst = 1;
- dev->conf_ch[channel].conf1.mem_rd_rst = 0;
-}
-
-__attribute__((always_inline))
-static inline void rmt_ll_tx_enable_pingpong(rmt_dev_t *dev, uint32_t channel, bool enable)
-{
- dev->apb_conf.mem_tx_wrap_en = enable;
-}
-
-__attribute__((always_inline))
-static inline void rmt_ll_tx_enable_loop(rmt_dev_t *dev, uint32_t channel, bool enable)
-{
- dev->conf_ch[channel].conf1.tx_conti_mode = enable;
-}
-
-__attribute__((always_inline))
-static inline uint32_t rmt_ll_tx_get_channel_status(rmt_dev_t *dev, uint32_t channel)
-{
- return dev->status_ch[channel];
-}
-
-__attribute__((always_inline))
-static inline void rmt_ll_tx_set_limit(rmt_dev_t *dev, uint32_t channel, uint32_t limit)
-{
- dev->tx_lim_ch[channel].limit = limit;
-}
-
-__attribute__((always_inline))
-static inline void rmt_ll_enable_interrupt(rmt_dev_t *dev, uint32_t mask, bool enable)
-{
- if (enable) {
- dev->int_ena.val |= mask;
- } else {
- dev->int_ena.val &= ~mask;
- }
-}
-
-__attribute__((always_inline))
-static inline void rmt_ll_enable_tx_end_interrupt(rmt_dev_t *dev, uint32_t channel, bool enable)
-{
- dev->int_ena.val &= ~(1 << (channel * 3));
- dev->int_ena.val |= (enable << (channel * 3));
-}
-
-__attribute__((always_inline))
-static inline void rmt_ll_enable_tx_err_interrupt(rmt_dev_t *dev, uint32_t channel, bool enable)
-{
- dev->int_ena.val &= ~(1 << (channel * 3 + 2));
- dev->int_ena.val |= (enable << (channel * 3 + 2));
-}
-
-__attribute__((always_inline))
-static inline void rmt_ll_enable_tx_thres_interrupt(rmt_dev_t *dev, uint32_t channel, bool enable)
-{
- dev->int_ena.val &= ~(1 << (channel + 24));
- dev->int_ena.val |= (enable << (channel + 24));
-}
-
-__attribute__((always_inline))
-static inline void rmt_ll_clear_tx_end_interrupt(rmt_dev_t *dev, uint32_t channel)
-{
- dev->int_clr.val = (1 << (channel * 3));
-}
-
-__attribute__((always_inline))
-static inline void rmt_ll_clear_tx_err_interrupt(rmt_dev_t *dev, uint32_t channel)
-{
- dev->int_clr.val = (1 << (channel * 3 + 2));
-}
-
-__attribute__((always_inline))
-static inline void rmt_ll_clear_tx_thres_interrupt(rmt_dev_t *dev, uint32_t channel)
-{
- dev->int_clr.val = (1 << (channel + 24));
-}
-
-
-__attribute__((always_inline))
-static inline uint32_t rmt_ll_get_tx_thres_interrupt_status(rmt_dev_t *dev)
-{
- uint32_t status = dev->int_st.val;
- return (status & 0xFF000000) >> 24;
-}
-#endif
-
-
-// *********************************
-// Select method for binding interrupt
-//
-// - If the Bluetooth driver has registered a high-level interrupt, piggyback on that API
-// - If we're on a modern core, allocate the interrupt with the API (old cores are bugged)
-// - Otherwise use the low-level hardware API to manually bind the interrupt
-
-
-#if defined(CONFIG_BTDM_CTRL_HLI)
-// Espressif's bluetooth driver offers a helpful sharing layer; bring in the interrupt management calls
-#include "hal/interrupt_controller_hal.h"
-extern "C" esp_err_t hli_intr_register(intr_handler_t handler, void* arg, uint32_t intr_reg, uint32_t intr_mask);
-
-#else /* !CONFIG_BTDM_CTRL_HLI*/
-
-// Declare the our high-priority ISR handler
-extern "C" void ld_include_hli_vectors_rmt(); // an object with an address, but no space
-
-#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3)
-#include "soc/periph_defs.h"
-#endif
-
-// Select level flag
-#if defined(__riscv)
-// RISCV chips don't block interrupts while scheduling; all we need to do is be higher than the WiFi ISR
-#define INT_LEVEL_FLAG ESP_INTR_FLAG_LEVEL3
-#elif defined(CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5)
-#define INT_LEVEL_FLAG ESP_INTR_FLAG_LEVEL4
-#else
-#define INT_LEVEL_FLAG ESP_INTR_FLAG_LEVEL5
-#endif
-
-// ESP-IDF v3 cannot enable high priority interrupts through the API at all;
-// and ESP-IDF v4 on XTensa cannot enable Level 5 due to incorrect interrupt descriptor tables
-#if !defined(__XTENSA__) || (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)) || ((ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0) && CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5))
-#define NEOESP32_RMT_CAN_USE_INTR_ALLOC
-
-// XTensa cores require the assembly bridge
-#ifdef __XTENSA__
-#define HI_IRQ_HANDLER nullptr
-#define HI_IRQ_HANDLER_ARG ld_include_hli_vectors_rmt
-#else
-#define HI_IRQ_HANDLER NeoEsp32RmtMethodIsr
-#define HI_IRQ_HANDLER_ARG nullptr
-#endif
-
-#else
-/* !CONFIG_BTDM_CTRL_HLI && !NEOESP32_RMT_CAN_USE_INTR_ALLOC */
-// This is the index of the LV5 interrupt vector - see interrupt descriptor table in idf components/hal/esp32/interrupt_descriptor_table.c
-#define ESP32_LV5_IRQ_INDEX 26
-
-#endif /* NEOESP32_RMT_CAN_USE_INTR_ALLOC */
-#endif /* CONFIG_BTDM_CTRL_HLI */
-
-
-// RMT driver implementation
-struct NeoEsp32RmtHIChannelState {
- uint32_t rmtBit0, rmtBit1;
- uint32_t resetDuration;
-
- const byte* txDataStart; // data array
- const byte* txDataEnd; // one past end
- const byte* txDataCurrent; // current location
- size_t rmtOffset;
-};
-
-// Global variables
-#if defined(NEOESP32_RMT_CAN_USE_INTR_ALLOC)
-static intr_handle_t isrHandle = nullptr;
-#endif
-
-static NeoEsp32RmtHIChannelState** driverState = nullptr;
-constexpr size_t rmtBatchSize = RMT_MEM_ITEM_NUM / 2;
-
-// Fill the RMT buffer memory
-// This is implemented using many arguments instead of passing the structure object to ensure we do only one lookup
-// All the arguments are passed in registers, so they don't need to be looked up again
-static void IRAM_ATTR RmtFillBuffer(uint8_t channel, const byte** src_ptr, const byte* end, uint32_t bit0, uint32_t bit1, size_t* offset_ptr, size_t reserve) {
- // We assume that (rmtToWrite % 8) == 0
- size_t rmtToWrite = rmtBatchSize - reserve;
- rmt_item32_t* dest =(rmt_item32_t*) &RMTMEM.chan[channel].data32[*offset_ptr + reserve]; // write directly in to RMT memory
- const byte* psrc = *src_ptr;
-
- *offset_ptr ^= rmtBatchSize;
-
- if (psrc != end) {
- while (rmtToWrite > 0) {
- uint8_t data = *psrc;
- for (uint8_t bit = 0; bit < 8; bit++)
- {
- dest->val = (data & 0x80) ? bit1 : bit0;
- dest++;
- data <<= 1;
- }
- rmtToWrite -= 8;
- psrc++;
-
- if (psrc == end) {
- break;
- }
- }
-
- *src_ptr = psrc;
- }
-
- if (rmtToWrite > 0) {
- // Add end event
- rmt_item32_t bit0_val = {{.val = bit0 }};
- *dest = rmt_item32_t {{{ .duration0 = 0, .level0 = bit0_val.level1, .duration1 = 0, .level1 = bit0_val.level1 }}};
- }
-}
-
-static void IRAM_ATTR RmtStartWrite(uint8_t channel, NeoEsp32RmtHIChannelState& state) {
- // Reset context state
- state.rmtOffset = 0;
-
- // Fill the first part of the buffer with a reset event
- // FUTURE: we could do timing analysis with the last interrupt on this channel
- // Use 8 words to stay aligned with the buffer fill logic
- rmt_item32_t bit0_val = {{.val = state.rmtBit0 }};
- rmt_item32_t fill = {{{ .duration0 = 100, .level0 = bit0_val.level1, .duration1 = 100, .level1 = bit0_val.level1 }}};
- rmt_item32_t* dest = (rmt_item32_t*) &RMTMEM.chan[channel].data32[0];
- for (auto i = 0; i < 7; ++i) dest[i] = fill;
- fill.duration1 = state.resetDuration > 1400 ? (state.resetDuration - 1400) : 100;
- dest[7] = fill;
-
- // Fill the remaining buffer with real data
- RmtFillBuffer(channel, &state.txDataCurrent, state.txDataEnd, state.rmtBit0, state.rmtBit1, &state.rmtOffset, 8);
- RmtFillBuffer(channel, &state.txDataCurrent, state.txDataEnd, state.rmtBit0, state.rmtBit1, &state.rmtOffset, 0);
-
- // Start operation
- rmt_ll_clear_tx_thres_interrupt(&RMT, channel);
- rmt_ll_tx_reset_pointer(&RMT, channel);
- rmt_ll_tx_start(&RMT, channel);
-}
-
-extern "C" void IRAM_ATTR NeoEsp32RmtMethodIsr(void *arg) {
- // Tx threshold interrupt
- uint32_t status = rmt_ll_get_tx_thres_interrupt_status(&RMT);
- while (status) {
- uint8_t channel = __builtin_ffs(status) - 1;
- if (driverState[channel]) {
- // Normal case
- NeoEsp32RmtHIChannelState& state = *driverState[channel];
- RmtFillBuffer(channel, &state.txDataCurrent, state.txDataEnd, state.rmtBit0, state.rmtBit1, &state.rmtOffset, 0);
- } else {
- // Danger - another driver got invoked?
- rmt_ll_tx_stop(&RMT, channel);
- }
- rmt_ll_clear_tx_thres_interrupt(&RMT, channel);
- status = rmt_ll_get_tx_thres_interrupt_status(&RMT);
- }
-};
-
-// Wrapper around the register analysis defines
-// For all currently supported chips, this is constant for all channels; but this is not true of *all* ESP32
-static inline bool _RmtStatusIsTransmitting(rmt_channel_t channel, uint32_t status) {
- uint32_t v;
- switch(channel) {
-#ifdef RMT_STATE_CH0
- case 0: v = (status >> RMT_STATE_CH0_S) & RMT_STATE_CH0_V; break;
-#endif
-#ifdef RMT_STATE_CH1
- case 1: v = (status >> RMT_STATE_CH1_S) & RMT_STATE_CH1_V; break;
-#endif
-#ifdef RMT_STATE_CH2
- case 2: v = (status >> RMT_STATE_CH2_S) & RMT_STATE_CH2_V; break;
-#endif
-#ifdef RMT_STATE_CH3
- case 3: v = (status >> RMT_STATE_CH3_S) & RMT_STATE_CH3_V; break;
-#endif
-#ifdef RMT_STATE_CH4
- case 4: v = (status >> RMT_STATE_CH4_S) & RMT_STATE_CH4_V; break;
-#endif
-#ifdef RMT_STATE_CH5
- case 5: v = (status >> RMT_STATE_CH5_S) & RMT_STATE_CH5_V; break;
-#endif
-#ifdef RMT_STATE_CH6
- case 6: v = (status >> RMT_STATE_CH6_S) & RMT_STATE_CH6_V; break;
-#endif
-#ifdef RMT_STATE_CH7
- case 7: v = (status >> RMT_STATE_CH7_S) & RMT_STATE_CH7_V; break;
-#endif
- default: v = 0;
- }
-
- return v != 0;
-}
-
-
-esp_err_t NeoEsp32RmtHiMethodDriver::Install(rmt_channel_t channel, uint32_t rmtBit0, uint32_t rmtBit1, uint32_t reset) {
- // Validate channel number
- if (channel >= RMT_CHANNEL_MAX) {
- return ESP_ERR_INVALID_ARG;
- }
-
- esp_err_t err = ESP_OK;
- if (!driverState) {
- // First time init
- driverState = reinterpret_cast(heap_caps_calloc(RMT_CHANNEL_MAX, sizeof(NeoEsp32RmtHIChannelState*), MALLOC_CAP_INTERNAL));
- if (!driverState) return ESP_ERR_NO_MEM;
-
- // Ensure all interrupts are cleared before binding
- RMT.int_ena.val = 0;
- RMT.int_clr.val = 0xFFFFFFFF;
-
- // Bind interrupt handler
-#if defined(CONFIG_BTDM_CTRL_HLI)
- // Bluetooth driver has taken the empty high-priority interrupt. Fortunately, it allows us to
- // hook up another handler.
- err = hli_intr_register(NeoEsp32RmtMethodIsr, nullptr, (uintptr_t) &RMT.int_st, 0xFF000000);
- // 25 is the magic number of the bluetooth ISR on ESP32 - see soc/soc.h.
- intr_matrix_set(cpu_hal_get_core_id(), ETS_RMT_INTR_SOURCE, 25);
- intr_cntrl_ll_enable_interrupts(1<<25);
-#elif defined(NEOESP32_RMT_CAN_USE_INTR_ALLOC)
- // Use the platform code to allocate the interrupt
- // If we need the additional assembly bridge, we pass it as the "arg" to the IDF so it gets linked in
- err = esp_intr_alloc(ETS_RMT_INTR_SOURCE, INT_LEVEL_FLAG | ESP_INTR_FLAG_IRAM, HI_IRQ_HANDLER, (void*) HI_IRQ_HANDLER_ARG, &isrHandle);
- //err = ESP_ERR_NOT_FINISHED;
-#else
- // Broken IDF API does not allow us to reserve the interrupt; do it manually
- static volatile const void* __attribute__((used)) pleaseLinkAssembly = (void*) ld_include_hli_vectors_rmt;
- intr_matrix_set(xPortGetCoreID(), ETS_RMT_INTR_SOURCE, ESP32_LV5_IRQ_INDEX);
- ESP_INTR_ENABLE(ESP32_LV5_IRQ_INDEX);
-#endif
-
- if (err != ESP_OK) {
- heap_caps_free(driverState);
- driverState = nullptr;
- return err;
- }
- }
-
- if (driverState[channel] != nullptr) {
- return ESP_ERR_INVALID_STATE; // already in use
- }
-
- NeoEsp32RmtHIChannelState* state = reinterpret_cast(heap_caps_calloc(1, sizeof(NeoEsp32RmtHIChannelState), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT));
- if (state == nullptr) {
- return ESP_ERR_NO_MEM;
- }
-
- // Store timing information
- state->rmtBit0 = rmtBit0;
- state->rmtBit1 = rmtBit1;
- state->resetDuration = reset;
-
- // Initialize hardware
- rmt_ll_tx_stop(&RMT, channel);
- rmt_ll_tx_reset_pointer(&RMT, channel);
- rmt_ll_enable_tx_err_interrupt(&RMT, channel, false);
- rmt_ll_enable_tx_end_interrupt(&RMT, channel, false);
- rmt_ll_enable_tx_thres_interrupt(&RMT, channel, false);
- rmt_ll_clear_tx_err_interrupt(&RMT, channel);
- rmt_ll_clear_tx_end_interrupt(&RMT, channel);
- rmt_ll_clear_tx_thres_interrupt(&RMT, channel);
-
- rmt_ll_tx_enable_loop(&RMT, channel, false);
- rmt_ll_tx_enable_pingpong(&RMT, channel, true);
- rmt_ll_tx_set_limit(&RMT, channel, rmtBatchSize);
-
- driverState[channel] = state;
-
- rmt_ll_enable_tx_thres_interrupt(&RMT, channel, true);
-
- return err;
-}
-
-esp_err_t NeoEsp32RmtHiMethodDriver::Uninstall(rmt_channel_t channel) {
- if ((channel >= RMT_CHANNEL_MAX) || !driverState || !driverState[channel]) return ESP_ERR_INVALID_ARG;
-
- NeoEsp32RmtHIChannelState* state = driverState[channel];
-
- WaitForTxDone(channel, 10000 / portTICK_PERIOD_MS);
-
- // Done or not, we're out of here
- rmt_ll_tx_stop(&RMT, channel);
- rmt_ll_enable_tx_thres_interrupt(&RMT, channel, false);
- driverState[channel] = nullptr;
- heap_caps_free(state);
-
-#if !defined(CONFIG_BTDM_CTRL_HLI) /* Cannot unbind from bluetooth ISR */
- // Turn off the driver ISR and release global state if none are left
- for (uint8_t channelIndex = 0; channelIndex < RMT_CHANNEL_MAX; ++channelIndex) {
- if (driverState[channelIndex]) return ESP_OK; // done
- }
-
-#if defined(NEOESP32_RMT_CAN_USE_INTR_ALLOC)
- esp_intr_free(isrHandle);
-#else
- ESP_INTR_DISABLE(ESP32_LV5_IRQ_INDEX);
-#endif
-
- heap_caps_free(driverState);
- driverState = nullptr;
-#endif /* !defined(CONFIG_BTDM_CTRL_HLI) */
-
- return ESP_OK;
-}
-
-esp_err_t NeoEsp32RmtHiMethodDriver::Write(rmt_channel_t channel, const uint8_t *src, size_t src_size) {
- if ((channel >= RMT_CHANNEL_MAX) || !driverState || !driverState[channel]) return ESP_ERR_INVALID_ARG;
-
- NeoEsp32RmtHIChannelState& state = *driverState[channel];
- esp_err_t result = WaitForTxDone(channel, 10000 / portTICK_PERIOD_MS);
-
- if (result == ESP_OK) {
- state.txDataStart = src;
- state.txDataCurrent = src;
- state.txDataEnd = src + src_size;
- RmtStartWrite(channel, state);
- }
- return result;
-}
-
-esp_err_t NeoEsp32RmtHiMethodDriver::WaitForTxDone(rmt_channel_t channel, TickType_t wait_time) {
- if ((channel >= RMT_CHANNEL_MAX) || !driverState || !driverState[channel]) return ESP_ERR_INVALID_ARG;
-
- NeoEsp32RmtHIChannelState& state = *driverState[channel];
- // yield-wait until wait_time
- esp_err_t rv = ESP_OK;
- uint32_t status;
- while(1) {
- status = rmt_ll_tx_get_channel_status(&RMT, channel);
- if (!_RmtStatusIsTransmitting(channel, status)) break;
- if (wait_time == 0) { rv = ESP_ERR_TIMEOUT; break; };
-
- TickType_t sleep = std::min(wait_time, (TickType_t) 5);
- vTaskDelay(sleep);
- wait_time -= sleep;
- };
-
- return rv;
-}
-
-#endif
diff --git a/platformio.ini b/platformio.ini
index 9f69bf67bd..b074af8296 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -199,6 +199,7 @@ build_unflags = ${common.build_unflags}
custom_usermods =
build_flags =
-DESP8266
+ -DWLED_DISABLE_GLOBAL_PIXELBUFFER
-DFP_IN_IROM
;-Wno-deprecated-declarations
;-Wno-register ;; leaves some warnings when compiling C files: command-line option '-Wno-register' is valid for C++/ObjC++ but not for C
@@ -233,6 +234,7 @@ monitor_filters = esp8266_exception_decoder
;; compatibilty flags - same as 0.14.0 which seems to work better on some 8266 boards. Not using PIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48
build_flags_compat =
-DESP8266
+ -DWLED_DISABLE_GLOBAL_PIXELBUFFER
-DFP_IN_IROM
;;-Wno-deprecated-declarations
-Wno-misleading-indentation
@@ -256,7 +258,6 @@ lib_deps_compat =
ESPAsyncUDP
ESP8266PWM
IRremoteESP8266 @ 2.8.2
- makuna/NeoPixelBus @ 2.7.9
https://github.com/blazoncek/QuickESPNow.git#optional-debug
https://github.com/tignioj/ArduinoUZlib.git#20aff95cd80c141f80bdbf66895409a0046d2c2f
https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.4.0
diff --git a/usermods/rgb-rotary-encoder/rgb-rotary-encoder.cpp b/usermods/rgb-rotary-encoder/rgb-rotary-encoder.cpp
index 4e4742ad1d..d2230171d2 100644
--- a/usermods/rgb-rotary-encoder/rgb-rotary-encoder.cpp
+++ b/usermods/rgb-rotary-encoder/rgb-rotary-encoder.cpp
@@ -61,7 +61,7 @@ class RgbRotaryEncoderUsermod : public Usermod
// …then set only the LED pin
_pins[0] = static_cast(ledIo);
BusConfig busCfg = BusConfig(TYPE_WS2812_RGB, _pins, 0, numLeds, COL_ORDER_GRB, false, 0);
- busCfg.iType = BusManager::getI(busCfg.type, busCfg.pins, busCfg.driverType); // assign internal bus type and output driver
+ BusManager::allocateHardware(busCfg.type, busCfg.pins, busCfg.driverType); // assign internal bus type and output driver
ledBus = new BusDigital(busCfg);
if (!ledBus->isOk()) {
cleanup();
diff --git a/wled00/FX.h b/wled00/FX.h
index c874c57209..64083ef3eb 100644
--- a/wled00/FX.h
+++ b/wled00/FX.h
@@ -90,11 +90,7 @@ extern byte realtimeMode; // used in getMappedPixelIndex()
#define MAX_NUM_SEGMENTS 32
#define MAX_SEGMENT_DATA (20*1024) // 20k by default (S2 is short on free RAM), limit does not apply if PSRAM is available
#else
- #ifdef BOARD_HAS_PSRAM
- #define MAX_NUM_SEGMENTS 64
- #else
- #define MAX_NUM_SEGMENTS 32
- #endif
+ #define MAX_NUM_SEGMENTS 64
#define MAX_SEGMENT_DATA (64*1024) // 64k by default, limit does not apply if PSRAM is available
#endif
@@ -102,8 +98,6 @@ extern byte realtimeMode; // used in getMappedPixelIndex()
assuming each segment uses the same amount of data. 256 for ESP8266, 640 for ESP32. */
#define FAIR_DATA_PER_SEG (MAX_SEGMENT_DATA / MAX_NUM_SEGMENTS)
-#define MIN_SHOW_DELAY (_frametime < 16 ? 8 : 15)
-
#define NUM_COLORS 3 /* number of colors per segment */
#define SEGMENT (*strip._currentSegment)
#define SEGENV (*strip._currentSegment)
@@ -895,7 +889,13 @@ class WS2812FX {
waitForIt(); // wait until frame is over (service() has finished or time for 1 frame has passed)
void setRealtimePixelColor(unsigned i, uint32_t c);
- inline void setPixelColor(unsigned n, uint32_t c) const { if (n < getLengthTotal()) _pixels[n] = c; } // paints absolute strip pixel with index n and color c
+ inline void setPixelColor(unsigned n, uint32_t c) const {
+ #ifdef WLED_DISABLE_GLOBAL_PIXELBUFFER
+ n = getMappedPixelIndex(n); if (n < getLengthTotal()) BusManager::setPixelColor(n, c);
+ #else
+ if (n < getLengthTotal()) _pixels[n] = c;
+ #endif
+ } // paints absolute strip pixel with index n and color c
inline void resetTimebase() { timebase = 0UL - millis(); }
inline void setPixelColor(unsigned n, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) const
{ setPixelColor(n, RGBW32(r,g,b,w)); }
@@ -953,8 +953,20 @@ class WS2812FX {
};
unsigned long now, timebase;
- inline uint32_t getPixelColor(unsigned n) const { return (getMappedPixelIndex(n) < getLengthTotal()) ? _pixels[n] : 0; } // returns color of pixel n, black if out of (mapped) bounds
- inline uint32_t getPixelColorNoMap(unsigned n) const { return (n < getLengthTotal()) ? _pixels[n] : 0; } // ignores mapping table
+ inline uint32_t getPixelColor(unsigned n) const {
+ #ifdef WLED_DISABLE_GLOBAL_PIXELBUFFER
+ n = getMappedPixelIndex(n); return n < getLengthTotal() ? BusManager::getPixelColor(n) : 0;
+ #else
+ return (getMappedPixelIndex(n) < getLengthTotal()) ? _pixels[n] : 0;
+ #endif
+ } // returns color of pixel n, black if out of (mapped) bounds
+ inline uint32_t getPixelColorNoMap(unsigned n) const {
+ #ifdef WLED_DISABLE_GLOBAL_PIXELBUFFER
+ return (n < getLengthTotal()) ? BusManager::getPixelColor(n) : 0;
+ #else
+ return (n < getLengthTotal()) ? _pixels[n] : 0;
+ #endif
+ } // ignores mapping table
inline uint32_t getLastShow() const { return _lastShow; } // returns millis() timestamp of last strip.show() call
const char *getModeData(unsigned id = 0) const { return (id && id < _modeCount) ? _modeData[id] : PSTR("Solid"); }
diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp
index 916d324f17..7d2788aa60 100644
--- a/wled00/FX_fcn.cpp
+++ b/wled00/FX_fcn.cpp
@@ -12,6 +12,9 @@
#include "wled.h"
#include "FXparticleSystem.h" // TODO: better define the required function (mem service) in FX.h?
#include "colors.h"
+#if defined(ARDUINO_ARCH_ESP32)
+#include "src/WLEDpixelBus/WLEDpixelBus_RMT.h" // needed for setExpectedChannels()
+#endif
/*
Custom per-LED mapping has moved!
@@ -1193,80 +1196,44 @@ void WS2812FX::finalizeInit() {
_hasWhiteChannel = _isOffRefreshRequired = false;
BusManager::removeAll();
// TODO: ideally we would free everything segment related here to reduce fragmentation (pixel buffers, ledamp, segments, etc) but that somehow leads to heap corruption if touchig any of the buffers.
- unsigned digitalCount = 0;
- #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_HAS_PARALLEL_I2S)
- // validate the bus config: count I2S buses and check if they meet requirements
- unsigned i2sBusCount = 0;
-
- for (const auto &bus : busConfigs) {
+ // First assign driver types (RMT/I2S/... channels) for all buses before any are constructed,
+ // because parallel buses interact during channel assignment. If hardware allocation fails
+ // (no free channels), the bus is still constructed below and will fail initialization;
+ // BusManager::add() then replaces it with a BusPlaceholder.
+ #if defined(ARDUINO_ARCH_ESP32)
+ unsigned rmtBusCount = 0;
+ unsigned parHwBusCount = 0; // used for debug print only
+ #endif
+ for (auto &bus : busConfigs) {
+ BusManager::allocateHardware(bus.type, bus.pins, bus.driverType); // TODO: if no bus type can be allocated, this returns false, could fallback to placeholder
+ #if defined(ARDUINO_ARCH_ESP32)
+ // Count buses that will occupy an RMT channel (driverType is final after allocateHardware())
if (Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type)) {
- digitalCount++;
- if (bus.driverType == 1)
- i2sBusCount++;
+ if (bus.driverType == BUSDRV_RMT) rmtBusCount++;
+ else if (bus.driverType == BUSDRV_PARHW) parHwBusCount++; // parallel bus: I2S/LCD/SPI/PARLIO depending on chip (hardware driven, not BB)
}
+ #endif
}
- DEBUG_PRINTF_P(PSTR("Digital buses: %u, I2S buses: %u\n"), digitalCount, i2sBusCount);
- // Determine parallel vs single I2S usage (used for memory calculation only)
- bool useParallelI2S = false;
- #if defined(CONFIG_IDF_TARGET_ESP32S3)
- // ESP32-S3 always uses parallel LCD driver for I2S
- if (i2sBusCount > 0) {
- useParallelI2S = true;
- }
- #else
- if (i2sBusCount > 1) {
- useParallelI2S = true;
- }
- #endif
+ #if defined(ARDUINO_ARCH_ESP32)
+ DEBUG_PRINTF_P(PSTR("Digital RMT buses: %u, parallel buses: %u\n"), rmtBusCount, parHwBusCount);
+ // RMT channel tracking was already reset by BusManager::removeAll() -> resetChannelTracking()
+ WLEDpixelBus::RmtBus::setExpectedChannels((uint8_t)rmtBusCount);
#endif
DEBUG_PRINTF_P(PSTR("Heap before buses: %d\n"), getFreeHeapSize());
- // create buses/outputs
- unsigned mem = 0; // memory estimation including DMA buffer for I2S and pixel buffers
- unsigned I2SdmaMem = 0;
+ // Now construct each bus. BusManager::add() automatically falls back to a BusPlaceholder if it fails
for (auto &bus : busConfigs) {
- // assign bus types: call to getI() determines bus types/drivers, allocates and tracks polybus channels
- // store the result in iType for later use during bus creation (getI() must only be called once per BusConfig)
- // note: this needs to be determined for all buses prior to creating them as it also determines parallel I2S usage
- bus.iType = BusManager::getI(bus.type, bus.pins, bus.driverType);
+ BusManager::add(bus, false);
}
- for (auto &bus : busConfigs) {
- bool use_placeholder = false;
- unsigned busMemUsage = bus.memUsage(); // does not include DMA/RMT buffer but includes pixel buffers (segment buffer + global buffer)
- mem += busMemUsage;
- // estimate maximum I2S memory usage (only relevant for digital non-2pin busses when I2S is enabled)
- #if defined(WLED_HAS_PARALLEL_I2S)
- bool usesI2S = (bus.iType & 0x01) == 0; // I2S bus types are even numbered, can't use bus.driverType == 1 as getI() may have defaulted to RMT
- if (Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) && usesI2S) {
- #ifdef NPB_CONF_4STEP_CADENCE
- constexpr unsigned stepFactor = 4; // 4 step cadence (4 bits per pixel bit)
- #else
- constexpr unsigned stepFactor = 3; // 3 step cadence (3 bits per pixel bit)
- #endif
- unsigned i2sCommonMem = (stepFactor * bus.count * (3*Bus::hasRGB(bus.type)+Bus::hasWhite(bus.type)+Bus::hasCCT(bus.type)) * (Bus::is16bit(bus.type)+1));
- if (useParallelI2S) i2sCommonMem *= 8; // parallel I2S uses 8 channels, requiring 8x the DMA buffer size (common buffer shared between all parallel busses)
- if (i2sCommonMem > I2SdmaMem) I2SdmaMem = i2sCommonMem;
- }
- #endif
- if (mem + I2SdmaMem > MAX_LED_MEMORY + 1024) { // +1k to allow some margin to not drop buses that are allowed in UI (calculation here includes bus overhead)
- DEBUG_PRINTF_P(PSTR("Bus %d with %d LEDS memory usage exceeds limit\n"), (int)bus.type, bus.count);
- errorFlag = ERR_NORAM; // alert UI TODO: make this a distinct error: not enough memory for bus
- use_placeholder = true;
- }
- if (BusManager::add(bus, use_placeholder) != -1) {
- mem += BusManager::busses.back()->getBusSize();
- if (Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) && BusManager::busses.back()->isPlaceholder()) digitalCount--; // remove placeholder from digital count
- }
- }
- DEBUG_PRINTF_P(PSTR("Estimated buses + pixel-buffers size: %uB\n"), mem + I2SdmaMem);
busConfigs.clear();
busConfigs.shrink_to_fit();
_length = 0;
for (size_t i=0; iisOk() || bus->getStart() + bus->getLength() > MAX_LEDS) break;
+ if (!bus || bus->getStart() + bus->getLength() > MAX_LEDS) break;
+ if (!bus->isOk()) continue; // placeholder bus (failed init) — skip but keep initializing remaining buses
//RGBW mode is enabled if at least one of the strips is RGBW
_hasWhiteChannel |= bus->hasWhite();
//refresh is required to remain off if at least one of the strips requires the refresh.
@@ -1296,11 +1263,17 @@ void WS2812FX::finalizeInit() {
// update global _pixels[] buffer to match getLengthTotal() note: if allocation fails, WLED will not render anything
void WS2812FX::updatePixelBuffer() {
- uint32_t requiredMem = getLengthTotal() * sizeof(uint32_t);
p_free(_pixels); // using realloc on large buffers can cause additional fragmentation instead of reducing it
+ _pixels = nullptr;
+ #ifdef WLED_DISABLE_GLOBAL_PIXELBUFFER
+ // Skip the global framebuffer: blendSegment() encodes directly into bus encode buffers.
+ // This saves N*4 bytes of heap (e.g. 1200B for 300 LEDs, 2000B for 500 LEDs).
+ #else
+ uint32_t requiredMem = getLengthTotal() * sizeof(uint32_t);
// use PSRAM if available: there is no measurable perfomance impact between PSRAM and DRAM on S2/S3 with QSPI PSRAM for this buffer
_pixels = static_cast(allocate_buffer(requiredMem, BFRALLOC_ENFORCE_PSRAM | BFRALLOC_NOBYTEACCESS | BFRALLOC_CLEAR));
DEBUG_PRINTF_P(PSTR("strip buffer size: %uB\n"), requiredMem);
+ #endif
}
void WS2812FX::service() {
@@ -1440,11 +1413,32 @@ void WS2812FX::blendSegment(const Segment &topSegment) const {
const size_t stopIndx = startIndx + length;
uint8_t opacity = topSegment.currentBri(); // returns transitioned opacity for style FADE
uint8_t cct = topSegment.currentCCT();
- if (gammaCorrectCol) opacity = gamma8inv(opacity); // use inverse gamma on brightness for correct color scaling after gamma correction (see #5343 for details)
+ opacity = gamma8inv(opacity); // use inverse gamma on brightness for correct color scaling after gamma correction (see #5343 for details)
const Segment *segO = topSegment.getOldSegment();
const bool hasGrouping = topSegment.groupLength() != 1;
+ #ifdef WLED_DISABLE_GLOBAL_PIXELBUFFER
+ // Direct-to-bus mode: read/write pixels through the bus encode buffer instead of _pixels[].
+ // CCT is set per-segment (not per-pixel) — acceptable trade-off for the RAM saved.
+ // Gamma is applied inline when writing; multi-segment blending occurs in gamma-compressed space.
+ if (!cctFromRgb) BusManager::setSegmentCCT(cct, correctWB);
+ const auto blendPixelIntoFrame = [&](size_t idx, uint32_t newC, uint8_t o) {
+ unsigned physIdx = getMappedPixelIndex(idx);
+ uint32_t result;
+ // Fast path: fully-opaque top layer — result is just newC, no readback needed.
+ // This covers the dominant case (single segment, full brightness, default blend mode)
+ // and avoids an expensive bus decode call per pixel for fast effects like Palette.
+ if (o == 255 && blendMode == 0) {
+ result = newC;
+ } else {
+ uint32_t existing = BusManager::getPixelColor(physIdx);
+ result = color_blend(existing, segblend(newC, existing), o);
+ }
+ BusManager::setPixelColor(physIdx, result);
+ };
+ #endif
+
// fast path: handle the default case - no transitions, no grouping/spacing, no mirroring, no CCT
if (!segO && blendingStyle == TRANSITION_FADE && !hasGrouping && !topSegment.mirror && !topSegment.mirror_y) {
if (isMatrix && stopIndx <= matrixSize && !_pixelCCT) {
@@ -1461,12 +1455,18 @@ void WS2812FX::blendSegment(const Segment &topSegment) const {
if (topSegment.reverse_y) { start_offset += (height - 1) * Segment::maxWidth; y_inc = -Segment::maxWidth; }
for (int y = 0; y < height; y++) {
+ #ifndef WLED_DISABLE_GLOBAL_PIXELBUFFER
uint32_t* pRow = &_pixels[start_offset + y * y_inc];
+ #endif
const int y_width = y * width;
for (int x = 0; x < width; x++) {
- uint32_t* p = pRow + x * x_inc;
uint32_t c_a = topSegment.getPixelColorRaw(x + y_width);
+ #ifdef WLED_DISABLE_GLOBAL_PIXELBUFFER
+ blendPixelIntoFrame((size_t)(start_offset + y * y_inc + x * x_inc), c_a, opacity);
+ #else
+ uint32_t* p = pRow + x * x_inc;
*p = color_blend(*p, segblend(c_a, *p), opacity);
+ #endif
}
}
} else { // transposed
@@ -1476,7 +1476,11 @@ void WS2812FX::blendSegment(const Segment &topSegment) const {
const int py = topSegment.reverse_y ? (width - x - 1) : x; // source pixel: swap x into y, reverse if needed
const uint32_t c_a = topSegment.getPixelColorRaw(px + py * height); // height = virtual width
const size_t idx = XY(topSegment.start + x, topSegment.startY + y); // write logical (non swapped) pixel coordinate
+ #ifdef WLED_DISABLE_GLOBAL_PIXELBUFFER
+ blendPixelIntoFrame(idx, c_a, opacity);
+ #else
_pixels[idx] = color_blend(_pixels[idx], segblend(c_a, _pixels[idx]), opacity);
+ #endif
}
}
}
@@ -1484,7 +1488,9 @@ void WS2812FX::blendSegment(const Segment &topSegment) const {
#endif
} else if (!isMatrix) {
// 1D fast path, include CCT as it is more common on 1D setups
+ #ifndef WLED_DISABLE_GLOBAL_PIXELBUFFER
uint32_t* strip = _pixels;
+ #endif
int start = topSegment.start;
int off = topSegment.offset;
for (int i = 0; i < length; i++) {
@@ -1492,8 +1498,12 @@ void WS2812FX::blendSegment(const Segment &topSegment) const {
int p = topSegment.reverse ? (length - i - 1) : i;
int idx = start + p + off;
if (idx >= topSegment.stop) idx -= length;
+ #ifdef WLED_DISABLE_GLOBAL_PIXELBUFFER
+ blendPixelIntoFrame((size_t)idx, c_a, opacity);
+ #else
strip[idx] = color_blend(strip[idx], segblend(c_a, strip[idx]), opacity);
if (_pixelCCT) _pixelCCT[idx] = cct;
+ #endif
}
return;
}
@@ -1570,8 +1580,12 @@ void WS2812FX::blendSegment(const Segment &topSegment) const {
const int baseX = topSegment.start + x;
const int baseY = topSegment.startY + y;
size_t indx = XY(baseX, baseY); // absolute address on strip
+ #ifdef WLED_DISABLE_GLOBAL_PIXELBUFFER
+ blendPixelIntoFrame(indx, c, o);
+ #else
_pixels[indx] = color_blend(_pixels[indx], segblend(c, _pixels[indx]), o);
if (_pixelCCT) _pixelCCT[indx] = cct;
+ #endif
// Apply mirroring if enabled
if (topSegment.mirror || topSegment.mirror_y) {
const int mirrorX = topSegment.start + width - x - 1;
@@ -1579,6 +1593,11 @@ void WS2812FX::blendSegment(const Segment &topSegment) const {
const size_t idxMX = XY(topSegment.transpose ? baseX : mirrorX, topSegment.transpose ? mirrorY : baseY);
const size_t idxMY = XY(topSegment.transpose ? mirrorX : baseX, topSegment.transpose ? baseY : mirrorY);
const size_t idxMM = XY(mirrorX, mirrorY);
+ #ifdef WLED_DISABLE_GLOBAL_PIXELBUFFER
+ if (topSegment.mirror) blendPixelIntoFrame(idxMX, c, o);
+ if (topSegment.mirror_y) blendPixelIntoFrame(idxMY, c, o);
+ if (topSegment.mirror && topSegment.mirror_y) blendPixelIntoFrame(idxMM, c, o);
+ #else
if (topSegment.mirror) _pixels[idxMX] = color_blend(_pixels[idxMX], segblend(c, _pixels[idxMX]), o);
if (topSegment.mirror_y) _pixels[idxMY] = color_blend(_pixels[idxMY], segblend(c, _pixels[idxMY]), o);
if (topSegment.mirror && topSegment.mirror_y) _pixels[idxMM] = color_blend(_pixels[idxMM], segblend(c, _pixels[idxMM]), o);
@@ -1587,6 +1606,7 @@ void WS2812FX::blendSegment(const Segment &topSegment) const {
if (topSegment.mirror_y) _pixelCCT[idxMY] = cct;
if (topSegment.mirror && topSegment.mirror_y) _pixelCCT[idxMM] = cct;
}
+ #endif
}
};
@@ -1666,6 +1686,18 @@ void WS2812FX::blendSegment(const Segment &topSegment) const {
const auto setMirroredPixel = [&](int i, uint32_t c, uint8_t o) {
int indx = topSegment.start + i;
+ #ifdef WLED_DISABLE_GLOBAL_PIXELBUFFER
+ // direct-to-bus: apply mirroring then encode
+ if (topSegment.mirror) {
+ unsigned indxM = topSegment.stop - i - 1;
+ indxM += topSegment.offset; // offset/phase
+ if (indxM >= topSegment.stop) indxM -= length; // wrap
+ blendPixelIntoFrame(indxM, c, o);
+ }
+ indx += topSegment.offset; // offset/phase
+ if (indx >= topSegment.stop) indx -= length; // wrap
+ blendPixelIntoFrame((size_t)indx, c, o);
+ #else
// Apply mirroring
if (topSegment.mirror) {
unsigned indxM = topSegment.stop - i - 1;
@@ -1678,6 +1710,7 @@ void WS2812FX::blendSegment(const Segment &topSegment) const {
if (indx >= topSegment.stop) indx -= length; // wrap
_pixels[indx] = color_blend(_pixels[indx], segblend(c, _pixels[indx]), o);
if (_pixelCCT) _pixelCCT[indx] = cct;
+ #endif
};
// if we blend using "push" style we need to "shift" canvas to left/right/
@@ -1724,47 +1757,78 @@ void WS2812FX::blendSegment(const Segment &topSegment) const {
}
void WS2812FX::show() {
+ #ifndef WLED_DISABLE_GLOBAL_PIXELBUFFER
if (!_pixels) {
DEBUGFX_PRINTLN(F("Error: no _pixels!"));
errorFlag = ERR_NORAM;
return; // no pixels allocated, nothing to show
}
-
+ #endif
unsigned long showNow = millis();
size_t diff = showNow - _lastShow;
+ // use color gamma correction if enabled, not in realtime mode with gamma disabled or currently overriding RT mode
+ bool useGammaCorrection = gammaCorrectCol && !(realtimeMode && arlsDisableGammaCorrection && !realtimeOverride);
size_t totalLen = getLengthTotal();
- // WARNING: as WLED doesn't handle CCT on pixel level but on Segment level instead
- // we need to keep track of each pixel's CCT when blending segments (if CCT is present)
- // and then set appropriate CCT from that pixel during paint (see below).
- if ((hasCCTBus() || correctWB) && !cctFromRgb)
- _pixelCCT = static_cast(allocate_buffer(totalLen * sizeof(uint8_t), BFRALLOC_PREFER_PSRAM)); // allocate CCT buffer if necessary, prefer PSRAM
- if (_pixelCCT) memset(_pixelCCT, 127, totalLen); // set neutral (50:50) CCT
+ // CCT per-pixel tracking: only needed when multiple active segments have *different* CCT values.
+ // When all segments share the same CCT we skip the allocation entirely and set Bus::_cct once
+ // before the paint loop — identical to the original per-pixel-change path but without the buffer.
+ #ifndef WLED_DISABLE_GLOBAL_PIXELBUFFER
+ uint8_t uniformCCT = 127; // neutral 50:50 CCT; used when no buffer is allocated
+ if ((hasCCTBus() || correctWB) && !cctFromRgb) {
+ int16_t firstCCT = -1; // -1 = "no active segment seen yet"
+ for (const Segment &seg : _segments) {
+ if (!seg.isActive() || (!seg.on && !seg.isInTransition())) continue;
+ uint8_t segCCT = seg.currentCCT();
+ if (firstCCT < 0) {
+ firstCCT = segCCT;
+ uniformCCT = segCCT;
+ } else if ((uint8_t)firstCCT != segCCT) {
+ // segments have different CCT values — need per-pixel buffer
+ _pixelCCT = static_cast(allocate_buffer(totalLen * sizeof(uint8_t), BFRALLOC_PREFER_PSRAM));
+ if (_pixelCCT) memset(_pixelCCT, 127, totalLen); // set neutral (50:50) CCT
+ break;
+ }
+ }
+ }
+ #endif
if (realtimeMode == REALTIME_MODE_INACTIVE || useMainSegmentOnly || realtimeOverride > REALTIME_OVERRIDE_NONE) {
+ #ifdef WLED_DISABLE_GLOBAL_PIXELBUFFER
+ while (!BusManager::canAllShow()) yield(); // wait for all buses to finish sending the previous frame
+ // Direct-to-bus mode: clear bus encode buffers then blend segments directly into them.
+ // Per-pixel CCT tracking is not supported; CCT is set per-segment in blendSegment().
+ if (cctFromRgb) BusManager::setSegmentCCT(-1); // set once; blendSegment skips CCT if cctFromRgb
+ BusManager::clearPixels(totalLen);
+ for (Segment &seg : _segments) if (seg.isActive() && (seg.on || seg.isInTransition())) {
+ blendSegment(seg);
+ }
+ #else
// clear frame buffer
memset(_pixels, 0, sizeof(uint32_t) * totalLen);
// blend all segments into (cleared) buffer
for (Segment &seg : _segments) if (seg.isActive() && (seg.on || seg.isInTransition())) {
blendSegment(seg); // blend segment's buffer into frame buffer
}
+ #endif
}
// avoid race condition, capture _callback value
show_callback callback = _callback;
if (callback) callback(); // will call setPixelColor or setRealtimePixelColor
+ #ifndef WLED_DISABLE_GLOBAL_PIXELBUFFER
+ while (!BusManager::canAllShow()) yield(); // wait for all buses to finish sending the previous frame
// paint actual pixels
int oldCCT = Bus::getCCT(); // store original CCT value (since it is global)
// when cctFromRgb is true we implicitly calculate WW and CW from RGB values (cct==-1)
if (cctFromRgb) BusManager::setSegmentCCT(-1);
- // use color gamma correction if enabled, not in realtime mode with gamma disabled or currently overriding RT mode
- bool useGammaCorrection = gammaCorrectCol && !(realtimeMode && arlsDisableGammaCorrection && !realtimeOverride);
+ else if (!_pixelCCT) BusManager::setSegmentCCT(uniformCCT, correctWB); // uniform CCT: set once before loop
for (size_t i = 0; i < totalLen; i++) {
// when correctWB is true setSegmentCCT() will convert CCT into K with which we can then
// correct/adjust RGB value according to desired CCT value, it will still affect actual WW/CW ratio
- if (_pixelCCT) { // cctFromRgb already exluded at allocation
+ if (_pixelCCT) { // cctFromRgb already excluded at allocation
if (i == 0 || _pixelCCT[i-1] != _pixelCCT[i]) BusManager::setSegmentCCT(_pixelCCT[i], correctWB);
}
@@ -1777,10 +1841,11 @@ void WS2812FX::show() {
p_free(_pixelCCT);
_pixelCCT = nullptr;
+ #else
+ // TODO: implement bus level gamma, see PR #5722
+ #endif
- // some buses send asynchronously and this method will return before
- // all of the data has been sent.
- // See https://github.com/Makuna/NeoPixelBus/wiki/ESP32-NeoMethods#neoesp32rmt-methods
+ // pass the pixels on to the buses and start sending them out
BusManager::show();
if (diff > 0) { // skip calculation if no time has passed
@@ -1846,7 +1911,10 @@ void WS2812FX::setCCT(uint16_t k) {
// direct=true either expects the caller to call show() themselves (realtime modes) or be ok waiting for the next frame for the change to apply
// direct=false immediately triggers an effect redraw
void WS2812FX::setBrightness(uint8_t b, bool direct) {
- if (gammaCorrectBri) b = gamma8(b);
+ if (gammaCorrectBri && b > 0) {
+ (int)(powf((float)b / 255.0f, gammaCorrectVal) * 255.0f + 0.5f); // use gamma formula instead of gamma table: gamma table is only for color correction (has unity mapping if color correction is diabled)
+ if (b == 0) b = 1; // dont go to 0 brigthness if input was non zero
+ }
if (_brightness == b) return;
_brightness = b;
if (_brightness == 0) { //unfreeze all segments on power off
@@ -2213,3 +2281,4 @@ const char JSON_palette_names[] PROGMEM = R"=====([
"Semi Blue","Pink Candy","Red Reaf","Aqua Flash","Yelblu Hot","Lite Light","Red Flash","Blink Red","Red Shift","Red Tide",
"Candy2","Traffic Light"
])=====";
+
diff --git a/wled00/FXparticleSystem.cpp b/wled00/FXparticleSystem.cpp
index 30f613bdbc..9f8051eeec 100644
--- a/wled00/FXparticleSystem.cpp
+++ b/wled00/FXparticleSystem.cpp
@@ -1942,4 +1942,4 @@ static uint32_t fast_color_scaleAdd(const uint32_t c1, const uint32_t c2, const
return rb | g;
}
-#endif // !(defined(WLED_DISABLE_PARTICLESYSTEM2D) && defined(WLED_DISABLE_PARTICLESYSTEM1D))
+#endif // !(defined(WLED_DISABLE_PARTICLESYSTEM2D) && defined(WLED_DISABLE_PARTICLESYSTEM1D))
\ No newline at end of file
diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp
index f8cacc7b7b..5ba446fa9a 100644
--- a/wled00/bus_manager.cpp
+++ b/wled00/bus_manager.cpp
@@ -57,7 +57,7 @@ bool ColorOrderMap::add(uint16_t start, uint16_t len, uint8_t colorOrder) {
return true;
}
-uint8_t IRAM_ATTR ColorOrderMap::getPixelColorOrder(uint16_t pix, uint8_t defaultColorOrder) const {
+uint8_t ColorOrderMap::getPixelColorOrder(uint16_t pix, uint8_t defaultColorOrder) const {
// upper nibble contains W swap information
// when ColorOrderMap's upper nibble contains value >0 then swap information is used from it, otherwise global swap is used
for (const auto& map : _mappings) {
@@ -125,23 +125,28 @@ uint32_t Bus::autoWhiteCalc(uint32_t c, uint8_t &ww, uint8_t &cw) const {
return c;
}
+// Default implementation for Bus::getCustomBusConfig() — returns a static default.
+// BusDigital and BusPlaceholder override this when custom.active() == true.
+const CustomBusConfig& Bus::getCustomBusConfig() const {
+ static const CustomBusConfig _defaultCustom;
+ return _defaultCustom;
+}
BusDigital::BusDigital(const BusConfig &bc)
-: Bus(bc.type, bc.start, bc.autoWhite, bc.count, bc.reversed, (bc.refreshReq || bc.type == TYPE_TM1814))
+: Bus(bc.type, bc.start, bc.autoWhite, bc.count, bc.reversed, (bc.refreshReq || bc.type == TYPE_TM1814 || bc.type == TYPE_TM1815))
, _skip(bc.skipAmount) //sacrificial pixels
, _colorOrder(bc.colorOrder)
, _milliAmpsPerLed(bc.milliAmpsPerLed)
, _milliAmpsMax(bc.milliAmpsMax)
+, _milliAmpsLimit(0)
, _driverType(bc.driverType) // Store driver preference (0=RMT, 1=I2S)
+, _busPtr(nullptr)
{
DEBUGBUS_PRINTLN(F("Bus: Creating digital bus."));
+ _busSpeedFactor = bc.busSpeedFactor;
if (!isDigital(bc.type) || !bc.count) { DEBUGBUS_PRINTLN(F("Not digial or empty bus!")); return; }
- _iType = bc.iType; // reuse the iType that was determined by polyBus in getI() in finalizeInit()
- if (_iType == I_NONE) { DEBUGBUS_PRINTLN(F("Incorrect iType!")); return; }
-
if (!PinManager::allocatePin(bc.pins[0], true, PinOwner::BusDigital)) { DEBUGBUS_PRINTLN(F("Pin 0 allocated!")); return; }
_frequencykHz = 0U;
- _colorSum = 0;
_pins[0] = bc.pins[0];
if (is2Pin(bc.type)) {
if (!PinManager::allocatePin(bc.pins[1], true, PinOwner::BusDigital)) {
@@ -150,30 +155,58 @@ BusDigital::BusDigital(const BusConfig &bc)
return;
}
_pins[1] = bc.pins[1];
- _frequencykHz = bc.frequency ? bc.frequency : 2000U; // 2MHz clock if undefined
+ _frequencykHz = bc.frequency ? bc.frequency : 1000U; // 1MHz clock if undefined TODO: this is not passed to bus correctly, got lost somewhere in the refacotring
}
_hasRgb = hasRGB(bc.type);
_hasWhite = hasWhite(bc.type);
_hasCCT = hasCCT(bc.type);
uint16_t lenToCreate = bc.count;
- if (bc.type == TYPE_WS2812_1CH_X3) lenToCreate = NUM_ICS_WS2812_1CH_3X(bc.count); // only needs a third of "RGB" LEDs for NeoPixelBus
- _busPtr = PolyBus::create(_iType, _pins, lenToCreate + _skip);
+
+ // Customized channel map/timing override
+ if (bc.custom.active()) {
+ _pCustomConfig = new CustomBusConfig(bc.custom);
+ const uint8_t nch = bc.custom.is16bit ? bc.custom.numChannels * 2 : bc.custom.numChannels;
+ const WLEDpixelBus::LedTiming customTiming(bc.custom.t0h, bc.custom.t0l, bc.custom.t1h, bc.custom.t1l, bc.custom.trst);
+ _busPtr = PixelBusAllocator::create(bc.type, _pins, lenToCreate + _skip, bc.colorOrder, _driverType, bc.busSpeedFactor, _frequencykHz, nch, &customTiming);
+ if (_busPtr) {
+ _busPtr->setEncoder(WLEDpixelBus::ColorEncoder(bc.custom.channelColors, bc.custom.numChannels, bc.custom.invertMask, bc.custom.is16bit));
+ if (bc.custom.invertOutput) _busPtr->setInverted(true); // invert output, needs to be set before bus->begin() (uses native hardware inversion capability)
+ // TODO: should inverted be supported for normal buses too? probably better not as it complicates things for normal users, one more option to screw up the settings.
+ }
+ // Derive instance capabilities from actual channel map
+ _hasRgb = false; _hasWhite = false; _hasCCT = false;
+ bool hasWW = false, hasCW = false;
+ for (uint8_t i = 0; i < bc.custom.numChannels; i++) {
+ switch (bc.custom.channelColors[i]) {
+ case 1: case 2: case 3: _hasRgb = true; break; // at least one color channel is used
+ case 4: _hasWhite = true; break;
+ case 5: hasWW = true; break;
+ case 6: hasCW = true; break;
+ }
+ }
+ if (hasWW || hasCW) _hasWhite = true;
+ if (hasWW && hasCW) _hasCCT = true;
+ } else {
+ // create bus via PixelBusAllocator wrapper which will return a WLEDpixelBus::PixelBus
+ _busPtr = PixelBusAllocator::create(bc.type, _pins, lenToCreate + _skip, bc.colorOrder, _driverType, bc.busSpeedFactor, _frequencykHz);
+ }
_valid = (_busPtr != nullptr) && bc.count > 0;
+
// fix for wled#4759
- if (_valid) for (unsigned i = 0; i < _skip; i++) {
- PolyBus::setPixelColor(_busPtr, _iType, i, 0, COL_ORDER_GRB); // set sacrificial pixels to black (CO does not matter here)
+ if (_valid) {
+ _busPtr->setNumPixels(lenToCreate + _skip);
}
- else {
+
+ if (!_valid)
cleanup();
- }
- DEBUGBUS_PRINTF_P(PSTR("Bus len:%u, type:%u (RGB:%d, W:%d, CCT:%d), pins:%u,%u [itype:%u, driver:%s] mA=%d/%d %s\n"),
+
+ DEBUGBUS_PRINTF_P(PSTR("Bus len:%u, type:%u (RGB:%d, W:%d, CCT:%d), pins:%u,%u [driver:%s] mA=%d/%d %s\n"),
(int)bc.count,
(int)bc.type,
(int)_hasRgb, (int)_hasWhite, (int)_hasCCT,
(unsigned)_pins[0], is2Pin(bc.type)?(unsigned)_pins[1]:255U,
- (unsigned)_iType,
- isI2S() ? "I2S" : "RMT",
+ (_valid && _busPtr) ? _busPtr->getTypeStr() : "none",
(int)_milliAmpsPerLed, (int)_milliAmpsMax,
_valid ? " " : "FAILED"
);
@@ -193,21 +226,60 @@ BusDigital::BusDigital(const BusConfig &bc)
// if limit is set too low, brightness is limited to 1 to at least show some light
// to disable brightness limiter for a bus, set LED current to 0
+void BusDigital::setBrightness(uint8_t b) {
+ _bri = b;
+ if (!_busPtr) return;
+ if (_type == TYPE_TM1814 || _type == TYPE_TM1815) {
+ // TM1814/TM1815: coarse brightness via hardware drive current (64 steps),
+ // fine residual applied via color_fade() in setPixelColor().
+ uint8_t currentStep, residualBri;
+ WLEDpixelBus::mapBrightnessToCurrentStep(b, 64, 44, currentStep, residualBri);
+ uint8_t prefix[8];
+ memset(prefix, currentStep, 4); // C1: W R G B
+ memset(prefix + 4, ~currentStep, 4); // C2: ~W ~R ~G ~B
+ _busPtr->updatePrefix(prefix, 8);
+ _busPtr->setBusBri(residualBri); // used by color_fade() in setPixelColor()
+ } else if (_type == TYPE_APA102) {
+ // APA102: two-stage brightness. Hardware 5-bit brightness byte (0..31) for coarse
+ // control, color_fade() residual for fine interpolation between steps.
+ // minBri=8 reflects step-0 = 1/31 of max current (~3.2% of full brightness).
+ uint8_t hwStep, residualBri;
+ WLEDpixelBus::mapBrightnessToCurrentStep(b, 32, 8, hwStep, residualBri);
+ _busPtr->setApa102HwBri(hwStep);
+ _busPtr->setBusBri(residualBri); // used by color_fade() in setPixelColor()
+ } else if (is16bit()) {
+ // 16-bit LED types (SM16825, UCS8903, UCS8904): color_fade() is a no-op (_busBri=255);
+ // encoder applies full 16-bit precision via channel*_encBri.
+ _busPtr->setBusBri(255);
+ _busPtr->setEncBri(b);
+ } else {
+ _busPtr->setBusBri(b); // used by color_fade() in setPixelColor()
+ }
+}
+
void BusDigital::estimateCurrent() {
+ uint32_t colorSum = _colorSum;
uint32_t actualMilliampsPerLed = _milliAmpsPerLed;
if (_milliAmpsPerLed == 255) {
// use wacky WS2815 power model, see WLED issue #549
- _colorSum *= 3; // sum is sum of max value for each color, need to multiply by three to account for clrUnitsPerChannel being 3*255
+ // WS2815 power model: _colorSum accumulated max(R,G,B) per pixel; multiply by 3 to match clrUnitsPerChannel
+ colorSum *= 3;
actualMilliampsPerLed = 12; // from testing an actual strip
}
- // _colorSum has all the values of color channels summed, max would be getLength()*(3*255 + (255 if hasWhite()): convert to milliAmps
+ // TM1814/TM1815/APA102: colors were accumulated with the fine residual brightness (getBusBri()).
+ // Scale colorSum back to the full _bri level so ABL sees the correct hardware power draw.
+ if (_type == TYPE_TM1814 || _type == TYPE_TM1815 || _type == TYPE_APA102) {
+ const uint8_t busBri = _busPtr->getBusBri();
+ if (busBri > 0) colorSum = ((uint64_t)colorSum * _bri) / busBri;
+ }
+ // colorSum has all the values of color channels summed, max would be getLength()*(3*255 + (255 if hasWhite()): convert to milliAmps
uint32_t clrUnitsPerChannel = hasWhite() ? 4*255 : 3*255;
- _milliAmpsTotal = ((uint64_t)_colorSum * actualMilliampsPerLed) / clrUnitsPerChannel + getLength(); // add 1mA standby current per LED to total (WS2812: ~0.7mA, WS2815: ~2mA)
+ _milliAmpsTotal = ((uint64_t)colorSum * actualMilliampsPerLed) / clrUnitsPerChannel + getLength(); // add 1mA standby current per LED
}
void BusDigital::applyBriLimit(uint8_t newBri) {
// a newBri of 0 means calculate per-bus brightness limit
- _NPBbri = 255; // reset, intermediate value is set below, final value is calculated in bus::show()
+ _totalBusBri = 255; // reset, intermediate value is set below, final value is calculated in bus::show()
if (newBri == 0) {
if (_milliAmpsLimit == 0 || _milliAmpsTotal == 0) return; // ABL not used for this bus
newBri = 255;
@@ -225,22 +297,10 @@ void BusDigital::applyBriLimit(uint8_t newBri) {
}
if (newBri < 255) {
- _NPBbri = newBri; // store value so it can be updated in show() (must be updated even if ABL is not used)
- uint16_t wwcw = 0;
- unsigned hwLen = _len;
- if (_type == TYPE_WS2812_1CH_X3) hwLen = NUM_ICS_WS2812_1CH_3X(_len); // only needs a third of "RGB" LEDs for NeoPixelBus
- for (unsigned i = 0; i < hwLen; i++) {
- uint8_t co = _colorOrderMap.getPixelColorOrder(i+_start, _colorOrder); // need to revert color order for correct color scaling and CCT calc in case white is swapped
- uint32_t c = PolyBus::getPixelColor(_busPtr, _iType, i, co); // Note: if ABL would be calculated as a seperate loop (as it was before) it is slower but could use original color, making it more color-accurate
- if (hasCCT()) {
- uint8_t cctWW, cctCW;
- Bus::calculateCCT(c, cctWW, cctCW); // calculate CCT before fade (more accurate) | Note: if using "accurate" white calculation mode, approximateKelvinFromRGB can be very inaccurate (white is subtracted)
- wwcw = ((cctCW + 1) * newBri) & 0xFF00; // apply brightness to CCT (leave it in upper byte for 16bit NeoPixelBus value)
- wwcw |= ((cctWW + 1) * newBri) >> 8;
- }
- c = color_fade(c, newBri, true); // apply additional dimming note: using inline version is a bit faster but overhead of getPixelColor() dominates the speed impact by far
- PolyBus::setPixelColor(_busPtr, _iType, i, c, co, wwcw); // repaint all pixels with new brightness
- }
+ _totalBusBri = newBri; // store value so it can be updated in show() (must be updated even if ABL is not used)
+ // scaleAll() simply scales every channel byte proportionally, i.e. RGB and if available W (WW and CW)
+ // in order to enforce the brightness limit, this does not use video scaling and can scale to black if ABL limit is set too low
+ _busPtr->scaleAll(newBri);
}
_colorSum = 0; // reset for next frame
@@ -248,64 +308,67 @@ void BusDigital::applyBriLimit(uint8_t newBri) {
void BusDigital::show() {
if (!_valid) return;
- _NPBbri = (_NPBbri * _bri) / 255; // total applied brightness for use in restoreColorLossy (see applyBriLimit())
- PolyBus::show(_busPtr, _iType, _skip); // faster if buffer consistency is not important (no skipped LEDs)
+ if (BusManager::_useABL)
+ _totalBusBri = (_totalBusBri * _bri) / 255; // total applied brightness for use in restoreColorLossy (see applyBriLimit())
+ else
+ _totalBusBri = _bri; // if not using ABL, total bus brightness is just the bus brightness
+ _busPtr->show();
}
bool BusDigital::canShow() const {
if (!_valid) return true;
- return PolyBus::canShow(_busPtr, _iType);
+ return _busPtr->canShow();
+}
+
+void BusDigital::clearPixels() {
+ if (_valid && _busPtr) _busPtr->clearEncodeBuffer();
}
//If LEDs are skipped, it is possible to use the first as a status LED.
//TODO only show if no new show due in the next 50ms
void BusDigital::setStatusPixel(uint32_t c) {
if (_valid && _skip) {
- PolyBus::setPixelColor(_busPtr, _iType, 0, c, _colorOrderMap.getPixelColorOrder(_start, _colorOrder));
- if (canShow()) PolyBus::show(_busPtr, _iType);
+ _busPtr->setPixelColor(0, c, 0);
+ if (canShow()) _busPtr->show();
}
}
-// note: using WLED_O2_ATTR makes this function ~7% faster at the expense of 600 bytes of flash
-void IRAM_ATTR BusDigital::setPixelColor(unsigned pix, uint32_t c) {
+// TODO: this function may still need some optimization, making better use of the new bus architecture
+// Note: using WLED_O2_ATTR makes this function ~7% faster at the expense of 600 bytes of flash
+// Note: there is no benefit of putting this in IRAM: IRAM: 34.2fps, no IRAM: 34.3fps
+void BusDigital::setPixelColor(unsigned pix, uint32_t c) {
if (!_valid) return;
- if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT
+ if (_reversed) pix = _len - pix -1;
+ pix += _skip;
uint8_t cctWW = 0, cctCW = 0;
uint16_t wwcw = 0;
- if (hasWhite()) c = autoWhiteCalc(c, cctWW, cctCW);
- c = color_fade(c, _bri, true); // apply brightness
-
- if (hasCCT()) {
- wwcw = ((cctCW + 1) * _bri) & 0xFF00; // apply brightness to CCT (store CW in upper byte)
- wwcw |= ((cctWW + 1) * _bri) >> 8;
- if (_type == TYPE_WS2812_WWA) c = RGBW32(wwcw, wwcw >> 8, 0, W(c)); // ww,cw, 0, w
- }
-
- if (BusManager::_useABL) {
- // if using ABL, sum all color channels to estimate current and limit brightness in show()
- uint8_t r = R(c), g = G(c), b = B(c);
- if (_milliAmpsPerLed < 255) { // normal ABL
- _colorSum += r + g + b + W(c);
- } else { // wacky WS2815 power model, ignore white channel, use max of RGB (issue #549)
- _colorSum += ((r > g) ? ((r > b) ? r : b) : ((g > b) ? g : b));
+ if (c > 0) {
+ if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT
+ if (hasWhite()) {
+ c = autoWhiteCalc(c, cctWW, cctCW);
}
- }
-
- if (_reversed) pix = _len - pix -1;
- pix += _skip;
- const uint8_t co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder);
- if (_type == TYPE_WS2812_1CH_X3) { // map to correct IC, each controls 3 LEDs
- unsigned pOld = pix;
- pix = IC_INDEX_WS2812_1CH_3X(pix);
- uint32_t cOld = PolyBus::getPixelColor(_busPtr, _iType, pix, co);
- switch (pOld % 3) { // change only the single channel (TODO: this can cause loss because of get/set)
- case 0: c = RGBW32(R(cOld), W(c) , B(cOld), 0); break;
- case 1: c = RGBW32(W(c) , G(cOld), B(cOld), 0); break;
- case 2: c = RGBW32(R(cOld), G(cOld), W(c) , 0); break;
+ // Apply brightness via color_fade() on the full uint32_t — hue-preserving video scale.
+ // _busBri is the correct value for each type:
+ // normal 8-bit: _bri | TM1814/15: fine residual | 16-bit: 255 (no-op, encoder uses _encBri)
+ const uint8_t bri = _busPtr->getBusBri();
+ c = color_fade(c, bri, true);
+ if (hasCCT()) {
+ wwcw = ((uint16_t)(cctCW + 1) * bri) & 0xFF00; // scale CW, store in high byte
+ wwcw |= ((uint16_t)(cctWW + 1) * bri) >> 8; // scale WW, store in low byte
+ }
+ if (BusManager::_useABL) {
+ // Accumulate brightness-scaled channel values for current estimation.
+ // For 16-bit types c is unscaled (bri=255 above); ABL slightly over-estimates at low brightness.
+ uint8_t r = R(c), g = G(c), b = B(c);
+ if (_milliAmpsPerLed < 255) { // normal ABL
+ _colorSum += r + g + b + W(c);
+ } else { // wacky WS2815 power model, ignore white channel, use max of RGB (issue #549)
+ _colorSum += ((r > g) ? ((r > b) ? r : b) : ((g > b) ? g : b));
+ }
}
}
- PolyBus::setPixelColor(_busPtr, _iType, pix, c, co, wwcw);
+ _busPtr->setPixelColor(pix, c, wwcw);
}
// returns lossly restored color from bus
@@ -313,22 +376,9 @@ uint32_t IRAM_ATTR BusDigital::getPixelColor(unsigned pix) const {
if (!_valid) return 0;
if (_reversed) pix = _len - pix -1;
pix += _skip;
- const uint8_t co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder);
- uint32_t c = restoreColorLossy(PolyBus::getPixelColor(_busPtr, _iType, (_type==TYPE_WS2812_1CH_X3) ? IC_INDEX_WS2812_1CH_3X(pix) : pix, co),_NPBbri);
- if (_type == TYPE_WS2812_1CH_X3) { // map to correct IC, each controls 3 LEDs
- uint8_t r = R(c);
- uint8_t g = _reversed ? B(c) : G(c); // should G and B be switched if _reversed?
- uint8_t b = _reversed ? G(c) : B(c);
- switch (pix % 3) { // get only the single channel
- case 0: c = RGBW32(g, g, g, g); break;
- case 1: c = RGBW32(r, r, r, r); break;
- case 2: c = RGBW32(b, b, b, b); break;
- }
- }
- if (_type == TYPE_WS2812_WWA) {
- uint8_t w = R(c) | G(c);
- c = RGBW32(w, w, 0, w);
- }
+ //const uint8_t co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder); // TODO: do we need the color order? where is getpixelcolor used?
+ uint32_t rawC = _busPtr->getPixelColor(pix);
+ uint32_t c = restoreColorLossy(rawC, _totalBusBri);
return c;
}
@@ -339,7 +389,7 @@ size_t BusDigital::getPins(uint8_t* pinArray) const {
}
size_t BusDigital::getBusSize() const {
- return sizeof(BusDigital) + (isOk() ? PolyBus::getDataSize(_busPtr, _iType) : 0); // does not include common I2S DMA buffer
+ return sizeof(BusDigital) + (isOk() && _busPtr ? _busPtr->getEncodeBufferSize() : 0); // does not include common I2S DMA buffer
}
void BusDigital::setColorOrder(uint8_t colorOrder) {
@@ -360,11 +410,12 @@ std::vector BusDigital::getLEDTypes() {
{TYPE_SK6812_RGBW, "D", PSTR("SK6812/WS2814 RGBW")},
{TYPE_UCS8904, "D", PSTR("UCS8904 RGBW")},
{TYPE_TM1814, "D", PSTR("TM1814 RGBW")},
+ {TYPE_TM1815, "D", PSTR("TM1815 RGBW")},
{TYPE_FW1906, "D", PSTR("FW1906/WS2811 RGBCCT")},
{TYPE_WS2805, "D", PSTR("WS2805 RGBCCT")},
{TYPE_SM16825, "D", PSTR("SM16825 RGBCCT")},
{TYPE_WS2812_1CH_X3, "D", PSTR("WS2811 White")},
- {TYPE_WS2812_WWA, "D", PSTR("WS281x WWA")}, // amber ignored
+ {TYPE_WS2812_WWA, "D", PSTR("WS281x WWA")}, // amber = WW
{TYPE_WS2801, "2P", PSTR("WS2801 RGB")},
{TYPE_APA102, "2P", PSTR("APA102 RGB")},
{TYPE_LPD8806, "2P", PSTR("LPD8806 RGB")},
@@ -373,21 +424,28 @@ std::vector BusDigital::getLEDTypes() {
};
}
-bool BusDigital::isI2S() {
- return (_iType & 0x01) == 0; // I2S types have even iType values
+bool BusDigital::isParHw() {
+ return _driverType == BUSDRV_PARHW; // I2S/LCD/ParallelSPI/PARLIO are all parallel async drivers
}
void BusDigital::begin() {
if (!_valid) return;
- PolyBus::begin(_busPtr, _iType, _pins, _frequencykHz);
+ if (!_busPtr->begin()) { cleanup(); return; }
+ // TM1914: write the static mode-setting prefix once after the encode buffer is allocated.
+ if (_type == TYPE_TM1914)
+ _busPtr->updatePrefix(TM1914_PREFIX, sizeof(TM1914_PREFIX));
}
void BusDigital::cleanup() {
DEBUGBUS_PRINTLN(F("Digital Cleanup."));
- PolyBus::cleanup(_busPtr, _iType);
- _iType = I_NONE;
+ delete _pCustomConfig;
+ _pCustomConfig = nullptr;
+ if (_busPtr) {
+ _busPtr->end();
+ delete _busPtr;
+ _busPtr = nullptr;
+ }
_valid = false;
- _busPtr = nullptr;
PinManager::deallocatePin(_pins[1], PinOwner::BusDigital);
PinManager::deallocatePin(_pins[0], PinOwner::BusDigital);
}
@@ -805,7 +863,7 @@ void BusNetwork::cleanup() {
DEBUGBUS_PRINTLN(F("Virtual Cleanup."));
d_free(_data);
_data = nullptr;
- _type = I_NONE;
+ _type = TYPE_NONE;
_valid = false;
}
@@ -1252,7 +1310,38 @@ BusPlaceholder::BusPlaceholder(const BusConfig &bc)
, _milliAmpsMax(bc.milliAmpsMax)
, _text(bc.text)
{
+ _busSpeedFactor = bc.busSpeedFactor; // preserve so config is restored faithfully on reboot
+ if (bc.custom.active()) _pCustomConfig = new CustomBusConfig(bc.custom);
memcpy(_pins, bc.pins, sizeof(_pins));
+ PinOwner pinOwner = PinOwner::None;
+ if (Bus::isDigital(bc.type) && bc.type != TYPE_ONOFF) pinOwner = PinOwner::BusDigital;
+ else if (Bus::isOnOff(bc.type)) pinOwner = PinOwner::BusOnOff;
+ else if (Bus::isPWM(bc.type)) pinOwner = PinOwner::BusPwm;
+ #ifdef WLED_ENABLE_HUB75MATRIX
+ else if (Bus::isHub75(bc.type)) pinOwner = PinOwner::HUB75;
+ #endif
+
+ size_t nPins = Bus::getNumberOfPins(bc.type);
+ for (size_t i = 0; i < nPins; i++) {
+ if (_pins[i] != 255) PinManager::allocatePin(_pins[i], true, pinOwner);
+ }
+}
+
+void BusPlaceholder::cleanup() {
+ delete _pCustomConfig;
+ _pCustomConfig = nullptr;
+ PinOwner pinOwner = PinOwner::None;
+ if (Bus::isDigital(_type) && _type != TYPE_ONOFF) pinOwner = PinOwner::BusDigital;
+ else if (Bus::isOnOff(_type)) pinOwner = PinOwner::BusOnOff;
+ else if (Bus::isPWM(_type)) pinOwner = PinOwner::BusPwm;
+ #ifdef WLED_ENABLE_HUB75MATRIX
+ else if (Bus::isHub75(_type)) pinOwner = PinOwner::HUB75;
+ #endif
+
+ size_t nPins = Bus::getNumberOfPins(_type);
+ for (size_t i = 0; i < nPins; i++) {
+ if (_pins[i] != 255) PinManager::deallocatePin(_pins[i], pinOwner);
+ }
}
size_t BusPlaceholder::getPins(uint8_t* pinArray) const {
@@ -1263,22 +1352,6 @@ size_t BusPlaceholder::getPins(uint8_t* pinArray) const {
return nPins;
}
-//utility to get the approx. memory usage of a given BusConfig inclduding segmentbuffer and global buffer (4 bytes per pixel)
-size_t BusConfig::memUsage() const {
- size_t mem = (count + skipAmount) * 8; // 8 bytes per pixel for segment + global buffer
- if (Bus::isVirtual(type)) {
- mem += sizeof(BusNetwork) + (count * Bus::getNumberOfChannels(type)); // note: getNumberOfChannels() includes CCT channel if applicable but virtual buses do not use CCT channel buffer
- } else if (Bus::isDigital(type)) {
- // if any of digital buses uses I2S, there is additional common I2S DMA buffer not accounted for here
- mem += sizeof(BusDigital) + PolyBus::memUsage(count + skipAmount, iType);
- } else if (Bus::isOnOff(type)) {
- mem += sizeof(BusOnOff);
- } else {
- mem += sizeof(BusPwm);
- }
- return mem;
-}
-
int BusManager::add(const BusConfig &bc, bool placeholder) {
DEBUGBUS_PRINTF_P(PSTR("Bus: Adding bus (p:%d v:%d)\n"), getNumBusses(), getNumVirtualBusses());
unsigned digital = 0;
@@ -1291,7 +1364,7 @@ int BusManager::add(const BusConfig &bc, bool placeholder) {
}
digital += (Bus::isDigital(bc.type) && !Bus::is2Pin(bc.type));
analog += (Bus::isPWM(bc.type) ? Bus::numPWMPins(bc.type) : 0);
- if (digital > WLED_MAX_DIGITAL_CHANNELS || analog > WLED_MAX_ANALOG_CHANNELS) placeholder = true; // TODO: add errorFlag here
+ if (digital > WLED_MAX_DIGITAL_CHANNELS || analog > WLED_MAX_ANALOG_CHANNELS) placeholder = true;
if (placeholder) {
busses.push_back(make_unique(bc));
} else if (Bus::isVirtual(bc.type)) {
@@ -1307,6 +1380,15 @@ int BusManager::add(const BusConfig &bc, bool placeholder) {
} else {
busses.push_back(make_unique(bc));
}
+ // If the newly constructed bus failed to acquire resources (OOM, DMA failure, pin conflict, etc.)
+ // replace it with a BusPlaceholder so the configuration is preserved for the next reboot,
+ // where memory may be available and the bus can initialize successfully.
+ if (!placeholder && !busses.back()->isOk()) {
+ DEBUG_PRINTF_P(PSTR("Bus %u (type %u) failed initialization; replacing with placeholder.\n"), (unsigned)busses.size(), (unsigned)bc.type);
+ errorFlag = ERR_NORAM;
+ busses.back() = make_unique(bc);
+ }
+ _lastBusCache = busses[0].get(); // set cache to first bus, can be a placeholder but pointer must be valid (saves us pointer checking in hot path)
return busses.size();
}
@@ -1317,7 +1399,12 @@ static String LEDTypesToJson(const std::vector& types) {
// capabilities follows similar pattern as JSON API
int capabilities = Bus::hasRGB(type.id) | Bus::hasWhite(type.id)<<1 | Bus::hasCCT(type.id)<<2 | Bus::is16bit(type.id)<<4 | Bus::mustRefresh(type.id)<<5;
char str[256];
- sprintf_P(str, PSTR("{i:%d,c:%d,t:\"%s\",n:\"%s\"},"), type.id, capabilities, type.type, type.name);
+ WLEDpixelBus::LedTiming timing = WLEDpixelBus::getProtocol(type.id);
+ sprintf_P(str, PSTR("{i:%d,c:%d,t:\"%s\",n:\"%s\",t0h:%u,t0l:%u,t1h:%u,t1l:%u,trst:%u},"),
+ type.id, capabilities, type.type, type.name,
+ (unsigned)timing.t0h_ns, (unsigned)timing.t0l_ns,
+ (unsigned)timing.t1h_ns, (unsigned)timing.t1l_ns,
+ (unsigned)timing.reset_us);
json += str;
}
return json;
@@ -1339,19 +1426,19 @@ String BusManager::getLEDTypesJSONString() {
return json;
}
-uint8_t BusManager::getI(uint8_t busType, const uint8_t* pins, uint8_t driverPreference) {
- return PolyBus::getI(busType, pins, driverPreference);
+bool BusManager::allocateHardware(uint8_t busType, const uint8_t* pins, uint8_t& driverType) {
+ return PixelBusAllocator::allocateHardware(busType, pins, driverType);
}
+
//do not call this method from system context (network callback)
void BusManager::removeAll() {
DEBUGBUS_PRINTLN(F("Removing all."));
//prevents crashes due to deleting busses while in use.
while (!canAllShow()) yield();
+ _lastBusCache = nullptr; // Reset cache before destroying buses to avoid dangling pointer UB
busses.clear();
- #ifndef ESP8266
// Reset channel tracking for fresh allocation
- PolyBus::resetChannelTracking();
- #endif
+ PixelBusAllocator::resetChannelTracking();
}
#ifdef ESP32_DATA_IDLE_HIGH
@@ -1370,7 +1457,7 @@ void BusManager::esp32RMTInvertIdle() {
unsigned u = 0;
for (auto &bus : busses) {
if (bus->getLength()==0 || !bus->isDigital() || bus->is2Pin()) continue;
- if (static_cast