diff --git a/lib/NeoESP32RmtHI/include/NeoEsp32RmtHIMethod.h b/lib/NeoESP32RmtHI/include/NeoEsp32RmtHIMethod.h
deleted file mode 100644
index 02e066f741..0000000000
--- a/lib/NeoESP32RmtHI/include/NeoEsp32RmtHIMethod.h
+++ /dev/null
@@ -1,469 +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)
-
-// 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
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 8353201f08..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)
-
-#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
\ No newline at end of file
diff --git a/platformio.ini b/platformio.ini
index f58c1a8c5a..63be201caa 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -130,7 +130,6 @@ upload_speed = 115200
lib_compat_mode = strict
lib_deps =
IRremoteESP8266 @ 2.8.2
- https://github.com/Makuna/NeoPixelBus.git#1d7ff38f14d04a976f9c6e365c83a232bcba04fc
https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.4.2
marvinroger/AsyncMqttClient @ 0.9.0
# for I2C interface
@@ -256,7 +255,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 a8f6e1cd76..5030aa080b 100644
--- a/wled00/FX.h
+++ b/wled00/FX.h
@@ -88,11 +88,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
@@ -100,8 +96,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)
@@ -897,7 +891,12 @@ 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 ESP8266
+ if (!_pixels) return; // direct-to-bus mode: use Segment::setPixelColor() for effect output
+ #endif
+ if (n < getLengthTotal()) _pixels[n] = c;
+ } // 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)); }
@@ -955,8 +954,18 @@ 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 ESP8266
+ if (!_pixels) return (getMappedPixelIndex(n) < getLengthTotal()) ? BusManager::getPixelColor(getMappedPixelIndex(n)) : 0;
+ #endif
+ 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 {
+ #ifdef ESP8266
+ if (!_pixels) return (n < getLengthTotal()) ? BusManager::getPixelColor(n) : 0;
+ #endif
+ return (n < getLengthTotal()) ? _pixels[n] : 0;
+ } // 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 34fde40058..91ac076906 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"
+#endif
/*
Custom per-LED mapping has moved!
@@ -1206,79 +1209,59 @@ void WS2812FX::finalizeInit() {
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(CONFIG_IDF_TARGET_ESP32C3)
+ unsigned rmtBusCount = 0;
+ //#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3)
+ #if defined(ARDUINO_ARCH_ESP32)
// validate the bus config: count I2S buses and check if they meet requirements
unsigned i2sBusCount = 0;
+ #endif
for (const auto &bus : busConfigs) {
if (Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type)) {
digitalCount++;
- if (bus.driverType == 1)
+ //#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3)
+ #if defined(ARDUINO_ARCH_ESP32)
+ if (bus.driverType == BUSDRV_I2S)
i2sBusCount++;
+ else
+ rmtBusCount++;
+ #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;
- }
+ //#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3)
+ #if defined(ARDUINO_ARCH_ESP32)
+ DEBUG_PRINTF_P(PSTR("Digital buses: %u, I2S buses: %u\n"), digitalCount, i2sBusCount);
#endif
+
+ #if defined(ARDUINO_ARCH_ESP32)
+ WLEDpixelBus::RmtBus::resetAutoChannel();
+ 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;
+ // Pass 1: assign driver types (RMT/I2S channels) for all buses before any are constructed,
+ // because parallel I2S buses interact during channel assignment.
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::allocateHardware(bus.type, bus.pins, bus.driverType);
}
+ // Pass 2: construct each bus. BusManager::add() automatically falls back to a BusPlaceholder
+ // (and sets errorFlag) if a bus fails to initialize (OOM, DMA failure, pin conflict, etc.).
+ // The placeholder preserves the full config so the bus is retried on the next reboot.
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(CONFIG_IDF_TARGET_ESP32C3) && !defined(ESP8266)
- 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
+ if (BusManager::add(bus, false) != -1) {
+ if (Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) && BusManager::busses.back()->isPlaceholder())
+ digitalCount--; // placeholder does not consume a digital channel slot
}
}
- 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.
@@ -1308,11 +1291,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 ESP8266
+ // On ESP8266, 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() {
@@ -1452,11 +1441,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 ESP8266
+ // 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) {
@@ -1473,12 +1483,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 ESP8266
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 ESP8266
+ 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
@@ -1488,7 +1504,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 ESP8266
+ blendPixelIntoFrame(idx, c_a, opacity);
+ #else
_pixels[idx] = color_blend(_pixels[idx], segblend(c_a, _pixels[idx]), opacity);
+ #endif
}
}
}
@@ -1496,7 +1516,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 ESP8266
uint32_t* strip = _pixels;
+ #endif
int start = topSegment.start;
int off = topSegment.offset;
for (int i = 0; i < length; i++) {
@@ -1504,8 +1526,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 ESP8266
+ 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;
}
@@ -1582,8 +1608,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 ESP8266
+ 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;
@@ -1591,6 +1621,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 ESP8266
+ 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);
@@ -1599,6 +1634,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
}
};
@@ -1676,6 +1712,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 ESP8266
+ // 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;
@@ -1688,6 +1736,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/
@@ -1732,47 +1781,78 @@ void WS2812FX::blendSegment(const Segment &topSegment) const {
}
void WS2812FX::show() {
+ #ifndef ESP8266
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 ESP8266
+ 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 ESP8266
+ 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 ESP8266
+ 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);
}
@@ -1785,10 +1865,24 @@ void WS2812FX::show() {
p_free(_pixelCCT);
_pixelCCT = nullptr;
+ #else
+ /*
+ // TODO: this is not working, something wrong with color encoding/color order and it should use the raw pixel color, maybe move this down to bus level?
+ if (useGammaCorrection) {
+ for (auto &bus : BusManager::busses) {
+ if (bus->isDigital() && bus->isOk()) {
+ BusDigital &busd = static_cast(*bus);
+ for (int i = 0; i < busd.getLength(); i++) {
+ uint32_t c = busd.getPixelColor(i); // TODO: this uses restore color lossy, which is not needed.
+ if (c > 0) c = gamma32(c); // apply gamma correction if enabled note: applying gamma after brightness has too much color loss
+ busd.setPixelColor(i, c);
+ }
+ }
+ }
+ }*/
+ #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
@@ -1854,7 +1948,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
@@ -2221,3 +2318,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 d0257eb735..d0fbc22c5f 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 1ba808d8b0..eea5403519 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 type == TYPE_CUSTOM_BUS.
+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)) {
@@ -157,23 +162,51 @@ BusDigital::BusDigital(const BusConfig &bc)
_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);
+
+ // For TYPE_CUSTOM_BUS: store custom config, use per-instance numChannels and timing
+ if (bc.type == TYPE_CUSTOM_BUS) {
+ _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, 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;
+ 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);
+ }
_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->setPixel(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->setPixel(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,13 @@ 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_CUSTOM_BUS, "D", PSTR("Custom Digital")}, // fully configurable channel map
{TYPE_WS2801, "2P", PSTR("WS2801 RGB")},
{TYPE_APA102, "2P", PSTR("APA102 RGB")},
{TYPE_LPD8806, "2P", PSTR("LPD8806 RGB")},
@@ -374,20 +426,27 @@ std::vector BusDigital::getLEDTypes() {
}
bool BusDigital::isI2S() {
- return (_iType & 0x01) == 0; // I2S types have even iType values
+ return _driverType == BUSDRV_I2S; // I2S/LCD/ParallelSPI
}
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);
}
@@ -783,7 +842,7 @@ void BusNetwork::cleanup() {
DEBUGBUS_PRINTLN(F("Virtual Cleanup."));
d_free(_data);
_data = nullptr;
- _type = I_NONE;
+ _type = TYPE_NONE;
_valid = false;
}
@@ -1230,7 +1289,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.type == TYPE_CUSTOM_BUS) _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 {
@@ -1241,22 +1331,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;
@@ -1269,7 +1343,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)) {
@@ -1285,6 +1359,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,19 +1400,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
@@ -1411,14 +1494,24 @@ void BusManager::off() {
void BusManager::show() {
applyABL(); // apply brightness limit, updates _gMilliAmpsUsed
for (auto &bus : busses) {
- bus->show();
+ if (bus->getDriverType() == BUSDRV_BITBANG) bus->show(); // run bit bang buses first, they do block interrupts and cant run in parallel with other types
+ }
+ for (auto &bus : busses) {
+ if (bus->getDriverType() != BUSDRV_BITBANG) bus->show();
}
}
void IRAM_ATTR BusManager::setPixelColor(unsigned pix, uint32_t c) {
+ if (_lastBusCache->containsPixel(pix)) {
+ _lastBusCache->setPixelColor(pix - _lastBusCache->getStart(), c);
+ return;
+ }
for (auto &bus : busses) {
- if (!bus->containsPixel(pix)) continue;
- bus->setPixelColor(pix - bus->getStart(), c);
+ if (bus->containsPixel(pix)) {
+ bus->setPixelColor(pix - bus->getStart(), c);
+ _lastBusCache = bus.get();
+ return;
+ }
}
}
@@ -1439,6 +1532,17 @@ uint32_t BusManager::getPixelColor(unsigned pix) {
return 0;
}
+void BusManager::clearPixels(size_t n) {
+ // memset each bus encode buffer directly — vastly faster than N encode calls.
+ // All-zero bytes encode as black for all supported LED protocols (RGB, RGBW, GRB, I2S 4-step, etc).
+ unsigned covered = 0;
+ for (auto &bus : busses) {
+ if (covered >= n) break;
+ bus->clearPixels();
+ covered += bus->getLength();
+ }
+}
+
bool BusManager::canAllShow() {
for (const auto &bus : busses) if (!bus->canShow()) return false;
return true;
@@ -1498,17 +1602,13 @@ void BusManager::applyABL() {
if (_gMilliAmpsMax > 0) {
uint8_t newBri = 255;
uint32_t globalMax = _gMilliAmpsMax > MA_FOR_ESP ? _gMilliAmpsMax - MA_FOR_ESP : 1; // subtract ESP current consumption, fully limit if too low
- if (globalMax > totalLEDs) { // check if budget is larger than standby current
- if (milliAmpsSum > globalMax) {
- newBri = globalMax * 255 / milliAmpsSum + 1; // scale brightness down to stay in current limit, +1 to avoid 0 brightness
- milliAmpsSum = globalMax; // update total used current
- }
- } else {
- newBri = 1; // limit too low, set brightness to minimum
- milliAmpsSum = totalLEDs; // estimate total used current as minimum
+ if (milliAmpsSum > globalMax) { // check if global current limit is exceeded
+ newBri = globalMax * 255 / milliAmpsSum + 1; // scale brightness down to stay in current limit, +1 to avoid 0 brightness
+ if (newBri == 0) newBri = 1; // safety clamp
+ milliAmpsSum = globalMax; // update total used current
}
- // apply brightness limit to each bus, if its 255 it will only reset _colorSum
+ // apply brightness limit to each bus; resets bus _colorSum for next frame
for (auto &bus : busses) {
if (bus->isDigital() && bus->isOk()) {
BusDigital &busd = static_cast