From abdbe0b44556c5ca16ee3affdaa64f8537013bf0 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sun, 5 Oct 2025 11:20:42 -0400 Subject: [PATCH 01/15] Merge pull request #4980 from willmmiles/0_15_x_rmthi (0.15) RMT High-priority Interrupt driver backport --- .../include/NeoEsp32RmtHIMethod.h | 469 ++++++++++++++++ lib/NeoESP32RmtHI/library.json | 12 + lib/NeoESP32RmtHI/src/NeoEsp32RmtHI.S | 263 +++++++++ lib/NeoESP32RmtHI/src/NeoEsp32RmtHIMethod.cpp | 507 ++++++++++++++++++ platformio.ini | 1 + 5 files changed, 1252 insertions(+) create mode 100644 lib/NeoESP32RmtHI/include/NeoEsp32RmtHIMethod.h create mode 100644 lib/NeoESP32RmtHI/library.json create mode 100644 lib/NeoESP32RmtHI/src/NeoEsp32RmtHI.S create mode 100644 lib/NeoESP32RmtHI/src/NeoEsp32RmtHIMethod.cpp diff --git a/lib/NeoESP32RmtHI/include/NeoEsp32RmtHIMethod.h b/lib/NeoESP32RmtHI/include/NeoEsp32RmtHIMethod.h new file mode 100644 index 0000000000..02e066f741 --- /dev/null +++ b/lib/NeoESP32RmtHI/include/NeoEsp32RmtHIMethod.h @@ -0,0 +1,469 @@ +/*------------------------------------------------------------------------- +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 new file mode 100644 index 0000000000..0608e59e12 --- /dev/null +++ b/lib/NeoESP32RmtHI/library.json @@ -0,0 +1,12 @@ +{ + "name": "NeoESP32RmtHI", + "build": { "libArchive": false }, + "platforms": ["espressif32"], + "dependencies": [ + { + "owner": "makuna", + "name": "NeoPixelBus", + "version": "^2.8.3" + } + ] +} diff --git a/lib/NeoESP32RmtHI/src/NeoEsp32RmtHI.S b/lib/NeoESP32RmtHI/src/NeoEsp32RmtHI.S new file mode 100644 index 0000000000..0c60d2ebc9 --- /dev/null +++ b/lib/NeoESP32RmtHI/src/NeoEsp32RmtHI.S @@ -0,0 +1,263 @@ +/* RMT ISR shim + * Bridges from a high-level interrupt to the C++ code. + * + * This code is largely derived from Espressif's 'hli_vector.S' Bluetooth ISR. + * + */ + +#if defined(__XTENSA__) && defined(ESP32) && !defined(CONFIG_BTDM_CTRL_HLI) + +#include +#include "sdkconfig.h" +#include "soc/soc.h" + +/* If the Bluetooth driver has hooked the high-priority interrupt, we piggyback on it and don't need this. */ +#ifndef CONFIG_BTDM_CTRL_HLI + +/* + Select interrupt based on system check level + - Base ESP32: could be 4 or 5, depends on platform config + - S2: 5 + - S3: 5 +*/ + +#if CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5 +/* Use level 4 */ +#define RFI_X 4 +#define xt_highintx xt_highint4 +#else /* !CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5 */ +/* Use level 5 */ +#define RFI_X 5 +#define xt_highintx xt_highint5 +#endif /* CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5 */ + +// Register map, based on interrupt level +#define EPC_X (EPC + RFI_X) +#define EXCSAVE_X (EXCSAVE + RFI_X) + +// The sp mnemonic is used all over in ESP's assembly, though I'm not sure where it's expected to be defined? +#define sp a1 + +/* Interrupt stack size, for C code. */ +#define RMT_INTR_STACK_SIZE 512 + +/* Save area for the CPU state: + * - 64 words for the general purpose registers + * - 7 words for some of the special registers: + * - WINDOWBASE, WINDOWSTART — only WINDOWSTART is truly needed + * - SAR, LBEG, LEND, LCOUNT — since the C code might use these + * - EPC1 — since the C code might cause window overflow exceptions + * This is not laid out as standard exception frame structure + * for simplicity of the save/restore code. + */ +#define REG_FILE_SIZE (64 * 4) +#define SPECREG_OFFSET REG_FILE_SIZE +#define SPECREG_SIZE (7 * 4) +#define REG_SAVE_AREA_SIZE (SPECREG_OFFSET + SPECREG_SIZE) + + .data +_rmt_intr_stack: + .space RMT_INTR_STACK_SIZE +_rmt_save_ctx: + .space REG_SAVE_AREA_SIZE + + .section .iram1,"ax" + .global xt_highintx + .type xt_highintx,@function + .align 4 + +xt_highintx: + + movi a0, _rmt_save_ctx + /* save 4 lower registers */ + s32i a1, a0, 4 + s32i a2, a0, 8 + s32i a3, a0, 12 + rsr a2, EXCSAVE_X /* holds the value of a0 */ + s32i a2, a0, 0 + + /* Save special registers */ + addi a0, a0, SPECREG_OFFSET + rsr a2, WINDOWBASE + s32i a2, a0, 0 + rsr a2, WINDOWSTART + s32i a2, a0, 4 + rsr a2, SAR + s32i a2, a0, 8 + #if XCHAL_HAVE_LOOPS + rsr a2, LBEG + s32i a2, a0, 12 + rsr a2, LEND + s32i a2, a0, 16 + rsr a2, LCOUNT + s32i a2, a0, 20 + #endif + rsr a2, EPC1 + s32i a2, a0, 24 + + /* disable exception mode, window overflow */ + movi a0, PS_INTLEVEL(RFI_X+1) | PS_EXCM + wsr a0, PS + rsync + + /* Save the remaining physical registers. + * 4 registers are already saved, which leaves 60 registers to save. + * (FIXME: consider the case when the CPU is configured with physical 32 registers) + * These 60 registers are saved in 5 iterations, 12 registers at a time. + */ + movi a1, 5 + movi a3, _rmt_save_ctx + 4 * 4 + + /* This is repeated 5 times, each time the window is shifted by 12 registers. + * We come here with a1 = downcounter, a3 = save pointer, a2 and a0 unused. + */ +1: + s32i a4, a3, 0 + s32i a5, a3, 4 + s32i a6, a3, 8 + s32i a7, a3, 12 + s32i a8, a3, 16 + s32i a9, a3, 20 + s32i a10, a3, 24 + s32i a11, a3, 28 + s32i a12, a3, 32 + s32i a13, a3, 36 + s32i a14, a3, 40 + s32i a15, a3, 44 + + /* We are about to rotate the window, so that a12-a15 will become the new a0-a3. + * Copy a0-a3 to a12-15 to still have access to these values. + * At the same time we can decrement the counter and adjust the save area pointer + */ + + /* a0 is constant (_rmt_save_ctx), no need to copy */ + addi a13, a1, -1 /* copy and decrement the downcounter */ + /* a2 is scratch so no need to copy */ + addi a15, a3, 48 /* copy and adjust the save area pointer */ + beqz a13, 2f /* have saved all registers ? */ + rotw 3 /* rotate the window and go back */ + j 1b + + /* the loop is complete */ +2: + rotw 4 /* this brings us back to the original window */ + /* a0 still points to _rmt_save_ctx */ + + /* Can clear WINDOWSTART now, all registers are saved */ + rsr a2, WINDOWBASE + /* WINDOWSTART = (1 << WINDOWBASE) */ + movi a3, 1 + ssl a2 + sll a3, a3 + wsr a3, WINDOWSTART + +_highint_stack_switch: + movi a0, 0 + movi sp, _rmt_intr_stack + RMT_INTR_STACK_SIZE - 16 + s32e a0, sp, -12 /* For GDB: set null SP */ + s32e a0, sp, -16 /* For GDB: set null PC */ + movi a0, _highint_stack_switch /* For GDB: cosmetics, for the frame where stack switch happened */ + + /* Set up PS for C, disable all interrupts except NMI and debug, and clear EXCM. */ + movi a6, PS_INTLEVEL(RFI_X) | PS_UM | PS_WOE + wsr a6, PS + rsync + + /* Call C handler */ + mov a6, sp + call4 NeoEsp32RmtMethodIsr + + l32e sp, sp, -12 /* switch back to the original stack */ + + /* Done with C handler; re-enable exception mode, disabling window overflow */ + movi a2, PS_INTLEVEL(RFI_X+1) | PS_EXCM /* TOCHECK */ + wsr a2, PS + rsync + + /* Restore the special registers. + * WINDOWSTART will be restored near the end. + */ + movi a0, _rmt_save_ctx + SPECREG_OFFSET + l32i a2, a0, 8 + wsr a2, SAR + #if XCHAL_HAVE_LOOPS + l32i a2, a0, 12 + wsr a2, LBEG + l32i a2, a0, 16 + wsr a2, LEND + l32i a2, a0, 20 + wsr a2, LCOUNT + #endif + l32i a2, a0, 24 + wsr a2, EPC1 + + /* Restoring the physical registers. + * This is the reverse to the saving process above. + */ + + /* Rotate back to the final window, then start loading 12 registers at a time, + * in 5 iterations. + * Again, a1 is the downcounter and a3 is the save area pointer. + * After each rotation, a1 and a3 are copied from a13 and a15. + * To simplify the loop, we put the initial values into a13 and a15. + */ + rotw -4 + movi a15, _rmt_save_ctx + 64 * 4 /* point to the end of the save area */ + movi a13, 5 + +1: + /* Copy a1 and a3 from their previous location, + * at the same time decrementing and adjusting the save area pointer. + */ + addi a1, a13, -1 + addi a3, a15, -48 + + /* Load 12 registers */ + l32i a4, a3, 0 + l32i a5, a3, 4 + l32i a6, a3, 8 + l32i a7, a3, 12 + l32i a8, a3, 16 + l32i a9, a3, 20 + l32i a10, a3, 24 + l32i a11, a3, 28 /* ensure PS and EPC written */ + l32i a12, a3, 32 + l32i a13, a3, 36 + l32i a14, a3, 40 + l32i a15, a3, 44 + + /* Done with the loop? */ + beqz a1, 2f + /* If no, rotate the window and repeat */ + rotw -3 + j 1b + +2: + /* Done with the loop. Only 4 registers (a0-a3 in the original window) remain + * to be restored. Also need to restore WINDOWSTART, since all the general + * registers are now in place. + */ + movi a0, _rmt_save_ctx + + l32i a2, a0, SPECREG_OFFSET + 4 + wsr a2, WINDOWSTART + + l32i a1, a0, 4 + l32i a2, a0, 8 + l32i a3, a0, 12 + rsr a0, EXCSAVE_X /* holds the value of a0 before the interrupt handler */ + + /* Return from the interrupt, restoring PS from EPS_X */ + rfi RFI_X + + +/* The linker has no reason to link in this file; all symbols it exports are already defined + (weakly!) in the default int handler. Define a symbol here so we can use it to have the + linker inspect this anyway. */ + + .global ld_include_hli_vectors_rmt +ld_include_hli_vectors_rmt: + + +#endif // CONFIG_BTDM_CTRL_HLI +#endif // XTensa \ No newline at end of file diff --git a/lib/NeoESP32RmtHI/src/NeoEsp32RmtHIMethod.cpp b/lib/NeoESP32RmtHI/src/NeoEsp32RmtHIMethod.cpp new file mode 100644 index 0000000000..8353201f08 --- /dev/null +++ b/lib/NeoESP32RmtHI/src/NeoEsp32RmtHIMethod.cpp @@ -0,0 +1,507 @@ +/*------------------------------------------------------------------------- +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 d26b9fa7e8..e77b31629b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -253,6 +253,7 @@ lib_deps = ;; https://github.com/softhack007/FastLED.git#ESP32-C6 ;; patched version needed for -C6 IRremoteESP8266 @ 2.8.2 ;;makuna/NeoPixelBus @ 2.7.5 ;; WLEDMM will be added in board specific sections + ;;makuna/NeoPixelBus @ 2.8.3 https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.4.2 bitbank2/AnimatedGIF@^1.4.7 https://github.com/Aircoookie/GifDecoder.git#bc3af189b6b1e06946569f6b4287f0b79a860f8e From 4c6b3524bf93e6886027296b898571658b6e9338 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Mon, 5 Jan 2026 21:05:19 +0100 Subject: [PATCH 02/15] upgraade all esp32 buildenv to NeoPixelBus 2.7.9 only esp32dev_legacy stays on NPB 2.7.5 --- platformio.ini | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/platformio.ini b/platformio.ini index e77b31629b..262600b0ff 100644 --- a/platformio.ini +++ b/platformio.ini @@ -252,7 +252,7 @@ lib_deps = fastled/FastLED @ 3.7.1 ;; needed to prevent compiler errors when using newer framework versions ;; https://github.com/softhack007/FastLED.git#ESP32-C6 ;; patched version needed for -C6 IRremoteESP8266 @ 2.8.2 - ;;makuna/NeoPixelBus @ 2.7.5 ;; WLEDMM will be added in board specific sections + ;;makuna/NeoPixelBus @ 2.7.9 ;; WLEDMM will be added in board specific sections ;;makuna/NeoPixelBus @ 2.8.3 https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.4.2 bitbank2/AnimatedGIF@^1.4.7 @@ -430,8 +430,7 @@ build_flagsV4 = -g ;;; V4.4.x libraries (without LOROL_LITTLEFS; with newer NeoPixelBus) lib_depsV4 = esp32async/AsyncTCP @ 3.4.7 - makuna/NeoPixelBus @ 2.7.5 - ;; makuna/NeoPixelBus @ 2.7.9 ;; experimental + makuna/NeoPixelBus @ 2.7.9 ;; experimental ${common_mm.HUB75_lib_deps} ${env.lib_deps} @@ -465,8 +464,7 @@ build_flags = -g default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv lib_deps = esp32async/AsyncTCP @ 3.4.7 - makuna/NeoPixelBus @ 2.7.5 - ;; makuna/NeoPixelBus @ 2.7.9 ;; experimental + makuna/NeoPixelBus @ 2.7.9 ;; experimental ${env.lib_deps} board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs monitor_filters = esp32_exception_decoder @@ -503,7 +501,6 @@ build_flags = -g lib_deps = esp32async/AsyncTCP @ 3.4.7 - ;; makuna/NeoPixelBus @ 2.7.5 ;; standard makuna/NeoPixelBus @ 2.7.9 ;; experimental - reduces LED glitches on -S2 ${env.lib_deps} board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs @@ -530,8 +527,7 @@ build_flags = -g lib_deps = esp32async/AsyncTCP @ 3.4.7 - makuna/NeoPixelBus @ 2.7.5 - ;; makuna/NeoPixelBus @ 2.7.9 ;; experimental + makuna/NeoPixelBus @ 2.7.9 ;; experimental ${env.lib_deps} board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs monitor_filters = esp32_exception_decoder @@ -558,8 +554,7 @@ build_flags = -g ;; ARDUINO_USB_MODE, ARDUINO_USB_CDC_ON_BOOT lib_deps = esp32async/AsyncTCP @ 3.4.7 - makuna/NeoPixelBus @ 2.7.5 - ;; makuna/NeoPixelBus @ 2.7.9 ;; experimental + makuna/NeoPixelBus @ 2.7.9 ;; experimental ${env.lib_deps} board_build.partitions = ${esp32.large_partitions} ;; default partioning for 8MB flash - can be overridden in build envs monitor_filters = esp32_exception_decoder From 86473a85b8cd9baf60a2de80d66021e0abc6330b Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Mon, 5 Jan 2026 21:06:43 +0100 Subject: [PATCH 03/15] RMTHI backport to NPB 2.7.9 fingers crossed --- .../include/NeoEsp32RmtHIMethod.h | 18 +++++++++++++ lib/NeoESP32RmtHI/library.json | 2 +- wled00/bus_wrapper.h | 25 +++++++++++++------ 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/lib/NeoESP32RmtHI/include/NeoEsp32RmtHIMethod.h b/lib/NeoESP32RmtHI/include/NeoEsp32RmtHIMethod.h index 02e066f741..7d50191ade 100644 --- a/lib/NeoESP32RmtHI/include/NeoEsp32RmtHIMethod.h +++ b/lib/NeoESP32RmtHI/include/NeoEsp32RmtHIMethod.h @@ -33,6 +33,24 @@ License along with NeoPixel. If not, see // Use the NeoEspRmtSpeed types from the driver-based implementation #include +#if !defined(ESP_ERROR_CHECK_WITHOUT_ABORT_SILENT_TIMEOUT) +// macro backported from NPB 2.8.3 +#if defined NDEBUG || defined CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT +#define ESP_ERROR_CHECK_WITHOUT_ABORT_SILENT_TIMEOUT(x) ({ \ + esp_err_t err_rc_ = (x); \ + err_rc_; \ + }) +#else +#define ESP_ERROR_CHECK_WITHOUT_ABORT_SILENT_TIMEOUT(x) ({ \ + esp_err_t err_rc_ = (x); \ + if (unlikely(err_rc_ != ESP_OK && err_rc_ != ESP_ERR_TIMEOUT)) { \ + _esp_error_check_failed_without_abort(err_rc_, __FILE__, __LINE__, \ + __ASSERT_FUNC, #x); \ + } \ + err_rc_; \ + }) +#endif // NDEBUG +#endif namespace NeoEsp32RmtHiMethodDriver { // Install the driver for a specific channel, specifying timing properties diff --git a/lib/NeoESP32RmtHI/library.json b/lib/NeoESP32RmtHI/library.json index 0608e59e12..292cfd781c 100644 --- a/lib/NeoESP32RmtHI/library.json +++ b/lib/NeoESP32RmtHI/library.json @@ -6,7 +6,7 @@ { "owner": "makuna", "name": "NeoPixelBus", - "version": "^2.8.3" + "version": "^2.7.9" } ] } diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index 64dcd5b979..846277bab0 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -183,10 +183,21 @@ bool canUseSerial(void); // WLEDMM (wled_serial.cpp) returns true if Serial ca #define B_8266_BB_UCS_4 NeoPixelBusLg //4 chan, esp8266, bb (any pin) #endif + +// RMT driver selection +#if !defined(WLED_USE_SHARED_RMT) && !defined(__riscv) && defined(ARDUINO_ARCH_ESP32) && (ESP_IDF_VERSION_MAJOR >= 4) + +#include +#define NeoEsp32RmtMethod(x) NeoEsp32RmtHIN ## x ## Method +#else +#define NeoEsp32RmtMethod(x) NeoEsp32RmtN ## x ## Method +#endif + + /*** ESP32 Neopixel methods ***/ #ifdef ARDUINO_ARCH_ESP32 //RGB -#define B_32_RN_NEO_3 NeoPixelBusLg +#define B_32_RN_NEO_3 NeoPixelBusLg #ifndef WLED_NO_I2S0_PIXELBUS #define B_32_I0_NEO_3 NeoPixelBusLg #endif @@ -195,7 +206,7 @@ bool canUseSerial(void); // WLEDMM (wled_serial.cpp) returns true if Serial ca #endif //#define B_32_BB_NEO_3 NeoPixelBrightnessBus // NeoEsp8266BitBang800KbpsMethod //RGBW -#define B_32_RN_NEO_4 NeoPixelBusLg +#define B_32_RN_NEO_4 NeoPixelBusLg #ifndef WLED_NO_I2S0_PIXELBUS #define B_32_I0_NEO_4 NeoPixelBusLg #endif @@ -204,7 +215,7 @@ bool canUseSerial(void); // WLEDMM (wled_serial.cpp) returns true if Serial ca #endif //#define B_32_BB_NEO_4 NeoPixelBrightnessBus // NeoEsp8266BitBang800KbpsMethod //400Kbps -#define B_32_RN_400_3 NeoPixelBusLg +#define B_32_RN_400_3 NeoPixelBusLg #ifndef WLED_NO_I2S0_PIXELBUS #define B_32_I0_400_3 NeoPixelBusLg #endif @@ -213,7 +224,7 @@ bool canUseSerial(void); // WLEDMM (wled_serial.cpp) returns true if Serial ca #endif //#define B_32_BB_400_3 NeoPixelBrightnessBus // NeoEsp8266BitBang400KbpsMethod //TM1814 (RGBW) -#define B_32_RN_TM1_4 NeoPixelBusLg +#define B_32_RN_TM1_4 NeoPixelBusLg #ifndef WLED_NO_I2S0_PIXELBUS #define B_32_I0_TM1_4 NeoPixelBusLg #endif @@ -222,7 +233,7 @@ bool canUseSerial(void); // WLEDMM (wled_serial.cpp) returns true if Serial ca #endif //Bit Bang theoratically possible, but very undesirable and not needed (no pin restrictions on RMT and I2S) //TM1829 (RGB) -#define B_32_RN_TM2_3 NeoPixelBusLg +#define B_32_RN_TM2_3 NeoPixelBusLg #ifndef WLED_NO_I2S0_PIXELBUS #define B_32_I0_TM2_3 NeoPixelBusLg #endif @@ -231,7 +242,7 @@ bool canUseSerial(void); // WLEDMM (wled_serial.cpp) returns true if Serial ca #endif //Bit Bang theoratically possible, but very undesirable and not needed (no pin restrictions on RMT and I2S) //UCS8903 -#define B_32_RN_UCS_3 NeoPixelBusLg +#define B_32_RN_UCS_3 NeoPixelBusLg #ifndef WLED_NO_I2S0_PIXELBUS #define B_32_I0_UCS_3 NeoPixelBusLg #endif @@ -240,7 +251,7 @@ bool canUseSerial(void); // WLEDMM (wled_serial.cpp) returns true if Serial ca #endif //Bit Bang theoratically possible, but very undesirable and not needed (no pin restrictions on RMT and I2S) //UCS8904 -#define B_32_RN_UCS_4 NeoPixelBusLg +#define B_32_RN_UCS_4 NeoPixelBusLg #ifndef WLED_NO_I2S0_PIXELBUS #define B_32_I0_UCS_4 NeoPixelBusLg #endif From 8d41bc409c8f0bd497241abb7c239040592d32f9 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Mon, 5 Jan 2026 21:23:51 +0100 Subject: [PATCH 04/15] don't compile RMTHI on unsupprted (old) platforms --- lib/NeoESP32RmtHI/include/NeoEsp32RmtHIMethod.h | 2 ++ lib/NeoESP32RmtHI/src/NeoEsp32RmtHI.S | 2 ++ lib/NeoESP32RmtHI/src/NeoEsp32RmtHIMethod.cpp | 2 ++ wled00/bus_wrapper.h | 2 +- 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/NeoESP32RmtHI/include/NeoEsp32RmtHIMethod.h b/lib/NeoESP32RmtHI/include/NeoEsp32RmtHIMethod.h index 7d50191ade..05abfb70c3 100644 --- a/lib/NeoESP32RmtHI/include/NeoEsp32RmtHIMethod.h +++ b/lib/NeoESP32RmtHI/include/NeoEsp32RmtHIMethod.h @@ -29,6 +29,7 @@ License along with NeoPixel. If not, see #pragma once #if defined(ARDUINO_ARCH_ESP32) +#if !defined(WLED_USE_SHARED_RMT) && !defined(__riscv) && (ESP_IDF_VERSION_MAJOR >= 4) // WLEDMM don't compile this file on unsupported platforms // Use the NeoEspRmtSpeed types from the driver-based implementation #include @@ -485,3 +486,4 @@ typedef NeoEsp32RmtHI7Ws2805InvertedMethod NeoEsp32RmtHI7Ws2814InvertedMethod; #endif // !defined(CONFIG_IDF_TARGET_ESP32C3) #endif +#endif diff --git a/lib/NeoESP32RmtHI/src/NeoEsp32RmtHI.S b/lib/NeoESP32RmtHI/src/NeoEsp32RmtHI.S index 0c60d2ebc9..9be11a6536 100644 --- a/lib/NeoESP32RmtHI/src/NeoEsp32RmtHI.S +++ b/lib/NeoESP32RmtHI/src/NeoEsp32RmtHI.S @@ -6,6 +6,7 @@ */ #if defined(__XTENSA__) && defined(ESP32) && !defined(CONFIG_BTDM_CTRL_HLI) +#if !defined(WLED_USE_SHARED_RMT) && !defined(__riscv) && defined(ARDUINO_ARCH_ESP32) && (ESP_IDF_VERSION_MAJOR >= 4) // WLEDMM don't compile this file on unsupported platforms #include #include "sdkconfig.h" @@ -260,4 +261,5 @@ ld_include_hli_vectors_rmt: #endif // CONFIG_BTDM_CTRL_HLI +#endif // WLEDMM #endif // XTensa \ No newline at end of file diff --git a/lib/NeoESP32RmtHI/src/NeoEsp32RmtHIMethod.cpp b/lib/NeoESP32RmtHI/src/NeoEsp32RmtHIMethod.cpp index 8353201f08..63cd316373 100644 --- a/lib/NeoESP32RmtHI/src/NeoEsp32RmtHIMethod.cpp +++ b/lib/NeoESP32RmtHI/src/NeoEsp32RmtHIMethod.cpp @@ -30,6 +30,7 @@ License along with NeoPixel. If not, see #include #if defined(ARDUINO_ARCH_ESP32) +#if !defined(WLED_USE_SHARED_RMT) && !defined(__riscv) && (ESP_IDF_VERSION_MAJOR >= 4) // WLEDMM don't compile this file on unsupported platforms #include #include "esp_idf_version.h" @@ -504,4 +505,5 @@ esp_err_t NeoEsp32RmtHiMethodDriver::WaitForTxDone(rmt_channel_t channel, TickTy return rv; } +#endif #endif \ No newline at end of file diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index 846277bab0..41e7b39c7f 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -184,7 +184,7 @@ bool canUseSerial(void); // WLEDMM (wled_serial.cpp) returns true if Serial ca #endif -// RMT driver selection +// RMT driver selection - only for Xtensa and ESP-IDF 4.x #if !defined(WLED_USE_SHARED_RMT) && !defined(__riscv) && defined(ARDUINO_ARCH_ESP32) && (ESP_IDF_VERSION_MAJOR >= 4) #include From d70a3a84e8d19a0143acaea848d01191644cf3fb Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Mon, 5 Jan 2026 22:16:51 +0100 Subject: [PATCH 05/15] additional protection --- platformio.ini | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/platformio.ini b/platformio.ini index 262600b0ff..93acef19fe 100644 --- a/platformio.ini +++ b/platformio.ini @@ -301,6 +301,7 @@ build_flags = -D NON32XFER_HANDLER ;; ask forgiveness for PROGMEM misuse -D WLED_DISABLE_PARTICLESYSTEM2D -D WLED_DISABLE_PIXELFORGE ;; not enought space in flash + -D WLED_USE_SHARED_RMT ;; don't use the RMTHI driver (not compatible with esp8266) ;; special library dependencies for 8266 (workaround for upsteam #5136) - replaces env.lib_deps lib8266_deps = @@ -331,6 +332,7 @@ build_flags_compat = -DVTABLES_IN_FLASH -DMIMETYPE_MINIMAL -DWLED_SAVE_IRAM ;; needed to prevent linker error + -D WLED_USE_SHARED_RMT ;; don't use the RMTHI driver (not compatible with esp8266) ;; this platform version was used for WLED 0.14.0 platform_compat = espressif8266@4.2.0 @@ -354,6 +356,7 @@ build_flags = -g -D CONFIG_ASYNC_TCP_TASK_STACK_SIZE=9472 ;; WLEDMM increase stack by 1.25Kb, as audioreactive needs bigger SETTINGS_STACK_BUF_SIZE -D CONFIG_ASYNC_TCP_STACK_SIZE=9472 -D LOROL_LITTLEFS ;; use LITTLEFS library by lorol in ESP32 core 1.x.x instead of built-in in 2.x.x + -D WLED_USE_SHARED_RMT ;; don't use the RMTHI driver (not compatible with esp-idf v3.x) ;; -D CORE_DEBUG_LEVEL=5 ;; enable core debug messages ;; -DDEBUG -DWLED_DEBUG ;; enable WLED debug messages lib_deps = @@ -521,6 +524,7 @@ build_flags = -g -D CONFIG_ASYNC_TCP_TASK_STACK_SIZE=9472 ;; WLEDMM increase stack by 1.25Kb, as audioreactive needs bigger SETTINGS_STACK_BUF_SIZE -D CONFIG_ASYNC_TCP_STACK_SIZE=9472 ;; *sigh* newer asyncTCP uses this instead of .._TASK_.. -DCO + -D WLED_USE_SHARED_RMT ;; don't use the RMTHI driver (not compatible with -C3) -DARDUINO_USB_MODE=1 ;; this flag is mandatory for ESP32-C3 ;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry: ;; ARDUINO_USB_CDC_ON_BOOT From fafcbf9cf49a3cef759b3d6b47f956da6e9898a3 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Tue, 6 Jan 2026 12:30:27 +0100 Subject: [PATCH 06/15] no file access waiting if we have the flicker-free LEDs driver --- wled00/file.cpp | 6 ++++-- wled00/wled.h | 10 +++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/wled00/file.cpp b/wled00/file.cpp index 6ac56161de..6aa82fab63 100644 --- a/wled00/file.cpp +++ b/wled00/file.cpp @@ -49,13 +49,15 @@ void closeFile() { if (!f) {doCloseFile = false; return;} // WLEDMM only do all this hick-hack when f is an open file - unsigned long t_wait = millis(); bool oldLock = suspendStripService; + #if !defined(WLEDMM_NO_FILEWAIT) // not necessary if we have the flicker-free RMTHI driver + unsigned long t_wait = millis(); if (strip.isUpdating()) suspendStripService = true; // WLEDMM schedule short pause to prevent LEDs glitching during flash write while(strip.isUpdating() && (millis() - t_wait < 72)) delay(1); // WLEDMM try to catch a moment when strip is idle while(strip.isUpdating() && (millis() - t_wait < 96)) delay(0); // try harder //if (strip.isUpdating()) USER_PRINTLN("closeFile: strip still updating."); delay(2); // might help + #endif #else bool oldLock = suspendStripService; // fix build f***u* on 8266 #endif @@ -548,7 +550,7 @@ bool handleFileRead(AsyncWebServerRequest* request, String path){ #endif // wait for strip to finish updating, accessing FS during sendout causes glitches - #ifdef ARDUINO_ARCH_ESP32 + #if defined(ARDUINO_ARCH_ESP32) && !defined(WLEDMM_NO_FILEWAIT) // not necessary if we have the flicker-free RMTHI driver unsigned wait_start = millis(); while (strip.isUpdating() && (millis() - wait_start < 40)) delay(1); // wait max 40ms #endif diff --git a/wled00/wled.h b/wled00/wled.h index 00218b8c52..7e93713bcf 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -7,7 +7,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2601051 +#define VERSION 2601061 // WLEDMM - you can check for this define in usermods, to only enabled WLEDMM specific code in the "right" fork. Its not defined in AC WLED. #define _MoonModules_WLED_ @@ -180,6 +180,14 @@ #include "src/dependencies/json/AsyncJson-v6.h" #include "src/dependencies/json/ArduinoJson-v6.h" + +// WLEDMM: Do we have the flicker-free RMTHI driver? +#if defined(ARDUINO_ARCH_ESP32) +#if !defined(WLED_USE_SHARED_RMT) && !defined(__riscv) && (ESP_IDF_VERSION_MAJOR >= 4) +#define WLEDMM_NO_FILEWAIT 1 +#endif +#endif + // ESP32-WROVER features SPI RAM (aka PSRAM) which can be allocated using ps_malloc() // we can create custom PSRAMDynamicJsonDocument to use such feature (replacing DynamicJsonDocument) // The following is a construct to enable code to compile without it. From ad0409789e989965d6dec0eabd8a7e309792b493 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Tue, 6 Jan 2026 19:02:45 +0100 Subject: [PATCH 07/15] WLEDMM_NO_FILEWAIT => WLEDMM_FILEWAIT reversed the logic, to make it more straight forward. --- wled00/file.cpp | 4 ++-- wled00/wled.h | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/wled00/file.cpp b/wled00/file.cpp index 6aa82fab63..88e2d9389a 100644 --- a/wled00/file.cpp +++ b/wled00/file.cpp @@ -50,7 +50,7 @@ void closeFile() { if (!f) {doCloseFile = false; return;} // WLEDMM only do all this hick-hack when f is an open file bool oldLock = suspendStripService; - #if !defined(WLEDMM_NO_FILEWAIT) // not necessary if we have the flicker-free RMTHI driver + #if defined(WLEDMM_FILEWAIT) // only wait if we don't have the flicker-free RMTHI driver unsigned long t_wait = millis(); if (strip.isUpdating()) suspendStripService = true; // WLEDMM schedule short pause to prevent LEDs glitching during flash write while(strip.isUpdating() && (millis() - t_wait < 72)) delay(1); // WLEDMM try to catch a moment when strip is idle @@ -550,7 +550,7 @@ bool handleFileRead(AsyncWebServerRequest* request, String path){ #endif // wait for strip to finish updating, accessing FS during sendout causes glitches - #if defined(ARDUINO_ARCH_ESP32) && !defined(WLEDMM_NO_FILEWAIT) // not necessary if we have the flicker-free RMTHI driver + #if defined(ARDUINO_ARCH_ESP32) && defined(WLEDMM_FILEWAIT) // only wait if we don't have the flicker-free RMTHI driver unsigned wait_start = millis(); while (strip.isUpdating() && (millis() - wait_start < 40)) delay(1); // wait max 40ms #endif diff --git a/wled00/wled.h b/wled00/wled.h index 7e93713bcf..72d6690854 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -182,9 +182,9 @@ // WLEDMM: Do we have the flicker-free RMTHI driver? -#if defined(ARDUINO_ARCH_ESP32) -#if !defined(WLED_USE_SHARED_RMT) && !defined(__riscv) && (ESP_IDF_VERSION_MAJOR >= 4) -#define WLEDMM_NO_FILEWAIT 1 +#if !defined(ARDUINO_ARCH_ESP32) || (defined(WLED_USE_SHARED_RMT) || defined(__riscv) || (ESP_IDF_VERSION_MAJOR < 4)) +#ifndef WLEDMM_FILEWAIT +#define WLEDMM_FILEWAIT 1 // wait for LEDs output completion before file reading/writing #endif #endif From cec6e229df4d0003b6afb7017546e71cbd9c91e2 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Tue, 6 Jan 2026 19:16:09 +0100 Subject: [PATCH 08/15] small simplification --- wled00/wled.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/wled.h b/wled00/wled.h index 72d6690854..38feb58e3d 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -182,7 +182,7 @@ // WLEDMM: Do we have the flicker-free RMTHI driver? -#if !defined(ARDUINO_ARCH_ESP32) || (defined(WLED_USE_SHARED_RMT) || defined(__riscv) || (ESP_IDF_VERSION_MAJOR < 4)) +#if !defined(ARDUINO_ARCH_ESP32) || defined(WLED_USE_SHARED_RMT) || defined(__riscv) || (ESP_IDF_VERSION_MAJOR < 4) #ifndef WLEDMM_FILEWAIT #define WLEDMM_FILEWAIT 1 // wait for LEDs output completion before file reading/writing #endif From 3af89875cf03057adaa9a0fce0e49c99dcb52253 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Tue, 6 Jan 2026 23:15:24 +0100 Subject: [PATCH 09/15] disable RMTHI on -S2 my Lolin S2 mini "ESP32-S2FNR2 (revision v0.0)" get into a boot loop with the RMTHI driver enabled. More debugging tomorrow. --- platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/platformio.ini b/platformio.ini index 93acef19fe..1e41b59699 100644 --- a/platformio.ini +++ b/platformio.ini @@ -501,6 +501,7 @@ build_flags = -g -DARDUINO_USB_MODE=0 ;; this flag is mandatory for ESP32-S2 ! ;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry: ;; ARDUINO_USB_CDC_ON_BOOT + -D WLED_USE_SHARED_RMT ;; RMTHI fails miserably on my Lolin S2 mini "ESP32-S2FNR2 (revision v0.0)" - I don't know why (boot loop when trying to connect wifi) lib_deps = esp32async/AsyncTCP @ 3.4.7 From d1d020bc5b3023ea7a4e538174585afcf41e47c8 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Wed, 7 Jan 2026 00:02:58 +0100 Subject: [PATCH 10/15] disable RMTHI on -S3 same as on -S3: interrupt watchdog reset at the time when Wifi tries to connect. --- platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/platformio.ini b/platformio.ini index 1e41b59699..56e01eb48d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -557,6 +557,7 @@ build_flags = -g -DCO ;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry: ;; ARDUINO_USB_MODE, ARDUINO_USB_CDC_ON_BOOT + -D WLED_USE_SHARED_RMT ;; RMTHI causes interrupt watchdog reset - needs more investigation lib_deps = esp32async/AsyncTCP @ 3.4.7 makuna/NeoPixelBus @ 2.7.9 ;; experimental From 68eee6b05cc841685220e0e25f3a82e3ac1a32bb Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Wed, 7 Jan 2026 11:39:27 +0100 Subject: [PATCH 11/15] re-enable RMTHI for debugging, simplified buildenv for deugging * re-enabled RMTI on -S3 * added [env:esp32s3dev_8MB_PSRAM_opi] --- platformio.ini | 51 ++++++++++++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/platformio.ini b/platformio.ini index 56e01eb48d..c5bc8f4555 100644 --- a/platformio.ini +++ b/platformio.ini @@ -525,10 +525,10 @@ build_flags = -g -D CONFIG_ASYNC_TCP_TASK_STACK_SIZE=9472 ;; WLEDMM increase stack by 1.25Kb, as audioreactive needs bigger SETTINGS_STACK_BUF_SIZE -D CONFIG_ASYNC_TCP_STACK_SIZE=9472 ;; *sigh* newer asyncTCP uses this instead of .._TASK_.. -DCO - -D WLED_USE_SHARED_RMT ;; don't use the RMTHI driver (not compatible with -C3) -DARDUINO_USB_MODE=1 ;; this flag is mandatory for ESP32-C3 ;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry: ;; ARDUINO_USB_CDC_ON_BOOT + -D WLED_USE_SHARED_RMT ;; don't use the RMTHI driver (not compatible with -C3) lib_deps = esp32async/AsyncTCP @ 3.4.7 @@ -557,7 +557,7 @@ build_flags = -g -DCO ;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry: ;; ARDUINO_USB_MODE, ARDUINO_USB_CDC_ON_BOOT - -D WLED_USE_SHARED_RMT ;; RMTHI causes interrupt watchdog reset - needs more investigation + ;-D WLED_USE_SHARED_RMT ;; RMTHI causes interrupt watchdog reset - needs more investigation lib_deps = esp32async/AsyncTCP @ 3.4.7 makuna/NeoPixelBus @ 2.7.9 ;; experimental @@ -811,27 +811,34 @@ board_build.flash_mode = dio ;; some boards do not boot with the faster "qio" mo ;; ; board_build.flash_mode = dio ;; try this if you have problems at startup ;; monitor_filters = esp32_exception_decoder -;; [env:esp32s3dev_8MB_PSRAM_opi] + +;; only for testing. +[env:esp32s3dev_8MB_PSRAM_opi] ;; ESP32-S3 development board, with 8MB FLASH and >= 8MB PSRAM (memory_type: qio_opi) -;; board = esp32-s3-devkitc-1 ;; generic dev board; the next line adds PSRAM support -;; board_build.arduino.memory_type = qio_opi ;; use with PSRAM: 8MB or 16MB -;; platform = ${esp32s3.platform} -;; platform_packages = ${esp32s3.platform_packages} -;; upload_speed = 921600 -;; build_unflags = ${common.build_unflags} -;; build_flags = ${common.build_flags} ${esp32s3.build_flags} -;; -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 -;; ;-D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip -;; -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") -;; ; -D WLED_RELEASE_NAME=ESP32-S3_PSRAM -;; -D WLED_USE_PSRAM -DBOARD_HAS_PSRAM ; tells WLED that PSRAM shall be used -;; -D WLED_USE_PSRAM_JSON -DALL_JSON_TO_PSRAM ; WLEDMM --> force all JSON stuff into PSRAM; gives more free heap -;; lib_deps = ${esp32s3.lib_deps} -;; ${esp32.AR_lib_deps} -;; board_build.partitions = ${esp32.large_partitions} -;; board_build.f_flash = 80000000L -;; board_build.flash_mode = qio -;; monitor_filters = esp32_exception_decoder +board = esp32-s3-devkitc-1 ;; generic dev board; the next lines add PSRAM support +board_build.flash_mode = qio +board_build.arduino.memory_type = qio_opi ;; use with PSRAM: 8MB or 16MB +platform = ${esp32s3.platform} +platform_packages = ${esp32s3.platform_packages} +upload_speed = 921600 +build_unflags = ${common.build_unflags} + ; -DCORE_DEBUG_LEVEL=0 + ;;-DNDEBUG ;; removing this flag leads to a massive flood of runtime warnings from NeoPixelBus 2.7.9 +build_flags = ${common.build_flags} ${esp32s3.build_flags} + ; -DCORE_DEBUG_LEVEL=5 + ;;-DWLED_DEBUG ; -DDEBUG + ; -D WLED_USE_SHARED_RMT ;; RMTHI causes interrupt watchdog reset - needs more investigation + -D ARDUINO_USB_CDC_ON_BOOT=0 -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip + ;;-D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") + -D WLED_RELEASE_NAME=ESP32-S3_PSRAM_opi + -D BOARD_HAS_PSRAM -D WLED_USE_PSRAM ; tells WLED that PSRAM shall be used + -D WLED_USE_PSRAM_JSON -D ALL_JSON_TO_PSRAM ; WLEDMM --> force all JSON stuff into PSRAM; gives more free heap +lib_deps = ${esp32s3.lib_deps} + ${esp32.AR_lib_deps} +board_build.partitions = ${esp32.large_partitions} +board_build.f_flash = 80000000L +monitor_filters = esp32_exception_decoder + ;; [env:esp32s3dev_16MB_PSRAM_opi] ;; extends = env:esp32s3dev_8MB_PSRAM_opi From dc10a76295adfe3a756a205ef800a90097e60c11 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Wed, 7 Jan 2026 12:06:24 +0100 Subject: [PATCH 12/15] (minor) fix for WLED_RELEASE_NAM --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index c5bc8f4555..0709a1f249 100644 --- a/platformio.ini +++ b/platformio.ini @@ -830,7 +830,7 @@ build_flags = ${common.build_flags} ${esp32s3.build_flags} ; -D WLED_USE_SHARED_RMT ;; RMTHI causes interrupt watchdog reset - needs more investigation -D ARDUINO_USB_CDC_ON_BOOT=0 -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip ;;-D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") - -D WLED_RELEASE_NAME=ESP32-S3_PSRAM_opi + -D WLED_RELEASE_NAME=ESP32_S3_PSRAM_opi -D BOARD_HAS_PSRAM -D WLED_USE_PSRAM ; tells WLED that PSRAM shall be used -D WLED_USE_PSRAM_JSON -D ALL_JSON_TO_PSRAM ; WLEDMM --> force all JSON stuff into PSRAM; gives more free heap lib_deps = ${esp32s3.lib_deps} From db847f590b910cf406b3790bc995f18373e063de Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Thu, 8 Jan 2026 12:18:42 +0100 Subject: [PATCH 13/15] make sure that the interrupt driver is compiled see discussion in PR #308 experimental sanity checks added, will possibly remove them later. --- lib/NeoESP32RmtHI/include/NeoEsp32RmtHIMethod.h | 2 +- lib/NeoESP32RmtHI/src/NeoEsp32RmtHI.S | 8 ++++++-- lib/NeoESP32RmtHI/src/NeoEsp32RmtHIMethod.cpp | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/NeoESP32RmtHI/include/NeoEsp32RmtHIMethod.h b/lib/NeoESP32RmtHI/include/NeoEsp32RmtHIMethod.h index 05abfb70c3..23bafcfdaf 100644 --- a/lib/NeoESP32RmtHI/include/NeoEsp32RmtHIMethod.h +++ b/lib/NeoESP32RmtHI/include/NeoEsp32RmtHIMethod.h @@ -29,7 +29,7 @@ License along with NeoPixel. If not, see #pragma once #if defined(ARDUINO_ARCH_ESP32) -#if !defined(WLED_USE_SHARED_RMT) && !defined(__riscv) && (ESP_IDF_VERSION_MAJOR >= 4) // WLEDMM don't compile this file on unsupported platforms +#if !defined(WLED_USE_SHARED_RMT) // WLEDMM don't compile this file on unsupported platforms // Use the NeoEspRmtSpeed types from the driver-based implementation #include diff --git a/lib/NeoESP32RmtHI/src/NeoEsp32RmtHI.S b/lib/NeoESP32RmtHI/src/NeoEsp32RmtHI.S index 9be11a6536..25a80d8977 100644 --- a/lib/NeoESP32RmtHI/src/NeoEsp32RmtHI.S +++ b/lib/NeoESP32RmtHI/src/NeoEsp32RmtHI.S @@ -5,8 +5,12 @@ * */ +#if !defined(ESP32) && !defined(ESP8266) // WLEDMM buildenv sanity check (experimental) +#error neither ESP32 nor ESP8266, don't know what to do +#endif + #if defined(__XTENSA__) && defined(ESP32) && !defined(CONFIG_BTDM_CTRL_HLI) -#if !defined(WLED_USE_SHARED_RMT) && !defined(__riscv) && defined(ARDUINO_ARCH_ESP32) && (ESP_IDF_VERSION_MAJOR >= 4) // WLEDMM don't compile this file on unsupported platforms +#if !defined(WLED_USE_SHARED_RMT) // WLEDMM don't compile this file on unsupported platforms #include #include "sdkconfig.h" @@ -261,5 +265,5 @@ ld_include_hli_vectors_rmt: #endif // CONFIG_BTDM_CTRL_HLI -#endif // WLEDMM +#endif // WLED_USE_SHARED_RMT #endif // XTensa \ No newline at end of file diff --git a/lib/NeoESP32RmtHI/src/NeoEsp32RmtHIMethod.cpp b/lib/NeoESP32RmtHI/src/NeoEsp32RmtHIMethod.cpp index 63cd316373..ff1465e53d 100644 --- a/lib/NeoESP32RmtHI/src/NeoEsp32RmtHIMethod.cpp +++ b/lib/NeoESP32RmtHI/src/NeoEsp32RmtHIMethod.cpp @@ -30,7 +30,7 @@ License along with NeoPixel. If not, see #include #if defined(ARDUINO_ARCH_ESP32) -#if !defined(WLED_USE_SHARED_RMT) && !defined(__riscv) && (ESP_IDF_VERSION_MAJOR >= 4) // WLEDMM don't compile this file on unsupported platforms +#if !defined(WLED_USE_SHARED_RMT) // WLEDMM don't compile this file on unsupported platforms #include #include "esp_idf_version.h" From 74a8a957930ed7d2ef043e970374ba5b3821d41d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Frank=20M=C3=B6hle?= <91616163+softhack007@users.noreply.github.com> Date: Thu, 8 Jan 2026 23:52:52 +0100 Subject: [PATCH 14/15] -S3 and -S2 work with RMTHI now MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tested and the previous crash is gone 👌 --- platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index bce43959ee..cad9f9ea53 100644 --- a/platformio.ini +++ b/platformio.ini @@ -501,7 +501,7 @@ build_flags = -g -DARDUINO_USB_MODE=0 ;; this flag is mandatory for ESP32-S2 ! ;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry: ;; ARDUINO_USB_CDC_ON_BOOT - -D WLED_USE_SHARED_RMT ;; RMTHI fails miserably on my Lolin S2 mini "ESP32-S2FNR2 (revision v0.0)" - I don't know why (boot loop when trying to connect wifi) + ; -D WLED_USE_SHARED_RMT ;; un-comment to use the standard RMT driver instead of RMTHI lib_deps = esp32async/AsyncTCP @ 3.4.7 @@ -557,7 +557,7 @@ build_flags = -g -DCO ;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry: ;; ARDUINO_USB_MODE, ARDUINO_USB_CDC_ON_BOOT - ;-D WLED_USE_SHARED_RMT ;; RMTHI causes interrupt watchdog reset - needs more investigation + ;-D WLED_USE_SHARED_RMT ;; un-comment to use the standard RMT driver instead of RMTHI lib_deps = esp32async/AsyncTCP @ 3.4.7 makuna/NeoPixelBus @ 2.7.9 ;; experimental From 5ce9ba64e21661b3d4970b3b7231771ac5da3626 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Fri, 9 Jan 2026 16:12:52 +0100 Subject: [PATCH 15/15] finishing touches and cleanup * add NeoESP32RmtHI to lib_ignore for unsupported platforms (secondary safety net) * comment out [env:esp32s3dev_8MB_PSRAM_opi] --- platformio.ini | 66 ++++++++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 29 deletions(-) diff --git a/platformio.ini b/platformio.ini index cad9f9ea53..dd5aaf61f0 100644 --- a/platformio.ini +++ b/platformio.ini @@ -319,6 +319,7 @@ lib_deps = makuna/NeoPixelBus @ 2.7.5 ${esp8266.lib8266_deps} ;; use proven library versions for 8266 lib_ignore = + NeoESP32RmtHI ;; compatibilty flags - same as 0.14.0 which seems to work better on some 8266 boards. Not using PIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48 build_flags_compat = @@ -366,6 +367,8 @@ lib_deps = makuna/NeoPixelBus @ 2.7.5 ;; makuna/NeoPixelBus @ 2.7.9 ;; experimental ${env.lib_deps} +lib_ignore = + NeoESP32RmtHI monitor_filters = esp32_exception_decoder board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs default_partitions = ${esp32.default_partitions} ;; backwards compatibility @@ -534,6 +537,8 @@ lib_deps = esp32async/AsyncTCP @ 3.4.7 makuna/NeoPixelBus @ 2.7.9 ;; experimental ${env.lib_deps} +lib_ignore = + NeoESP32RmtHI board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs monitor_filters = esp32_exception_decoder @@ -811,34 +816,32 @@ board_build.flash_mode = dio ;; some boards do not boot with the faster "qio" mo ;; ; board_build.flash_mode = dio ;; try this if you have problems at startup ;; monitor_filters = esp32_exception_decoder - ;; only for testing. -[env:esp32s3dev_8MB_PSRAM_opi] -;; ESP32-S3 development board, with 8MB FLASH and >= 8MB PSRAM (memory_type: qio_opi) -board = esp32-s3-devkitc-1 ;; generic dev board; the next lines add PSRAM support -board_build.flash_mode = qio -board_build.arduino.memory_type = qio_opi ;; use with PSRAM: 8MB or 16MB -platform = ${esp32s3.platform} -platform_packages = ${esp32s3.platform_packages} -upload_speed = 921600 -build_unflags = ${common.build_unflags} - ; -DCORE_DEBUG_LEVEL=0 - ;;-DNDEBUG ;; removing this flag leads to a massive flood of runtime warnings from NeoPixelBus 2.7.9 -build_flags = ${common.build_flags} ${esp32s3.build_flags} - ; -DCORE_DEBUG_LEVEL=5 - ;;-DWLED_DEBUG ; -DDEBUG - ; -D WLED_USE_SHARED_RMT ;; RMTHI causes interrupt watchdog reset - needs more investigation - -D ARDUINO_USB_CDC_ON_BOOT=0 -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip - ;;-D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") - -D WLED_RELEASE_NAME=ESP32_S3_PSRAM_opi - -D BOARD_HAS_PSRAM -D WLED_USE_PSRAM ; tells WLED that PSRAM shall be used - -D WLED_USE_PSRAM_JSON -D ALL_JSON_TO_PSRAM ; WLEDMM --> force all JSON stuff into PSRAM; gives more free heap -lib_deps = ${esp32s3.lib_deps} - ${esp32.AR_lib_deps} -board_build.partitions = ${esp32.large_partitions} -board_build.f_flash = 80000000L -monitor_filters = esp32_exception_decoder - +;; [env:esp32s3dev_8MB_PSRAM_opi] +;; ESP32-S3 development board, with 8MB FLASH and >= 8MB PSRAM (memory_type: qio_opi) +;; board = esp32-s3-devkitc-1 ;; generic dev board; the next lines add PSRAM support +;; board_build.flash_mode = qio +;; board_build.arduino.memory_type = qio_opi ;; use with PSRAM: 8MB or 16MB +;; platform = ${esp32s3.platform} +;; platform_packages = ${esp32s3.platform_packages} +;; upload_speed = 921600 +;; build_unflags = ${common.build_unflags} +;; ; -DCORE_DEBUG_LEVEL=0 +;; ;;-DNDEBUG ;; removing this flag leads to a massive flood of runtime warnings from NeoPixelBus 2.7.9 +;; build_flags = ${common.build_flags} ${esp32s3.build_flags} +;; ; -DCORE_DEBUG_LEVEL=5 +;; ;;-DWLED_DEBUG ; -DDEBUG +;; ; -D WLED_USE_SHARED_RMT ;; RMTHI causes interrupt watchdog reset - needs more investigation +;; -D ARDUINO_USB_CDC_ON_BOOT=0 -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip +;; ;;-D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") +;; -D WLED_RELEASE_NAME=ESP32_S3_PSRAM_opi +;; -D BOARD_HAS_PSRAM -D WLED_USE_PSRAM ; tells WLED that PSRAM shall be used +;; -D WLED_USE_PSRAM_JSON -D ALL_JSON_TO_PSRAM ; WLEDMM --> force all JSON stuff into PSRAM; gives more free heap +;; lib_deps = ${esp32s3.lib_deps} +;; ${esp32.AR_lib_deps} +;; board_build.partitions = ${esp32.large_partitions} +;; board_build.f_flash = 80000000L +;; monitor_filters = esp32_exception_decoder ;; [env:esp32s3dev_16MB_PSRAM_opi] ;; extends = env:esp32s3dev_8MB_PSRAM_opi @@ -1303,7 +1306,9 @@ platform_packages = ${esp32_legacy.platform_packages} build_unflags = ${esp32_legacy.build_unflags} build_flags = ${common.build_flags} ${esp32_legacy.build_flags} ${common_mm.build_flags_S} ${common_mm.build_disable_sync_interfaces} lib_deps = ${esp32_legacy.lib_deps} ${common_mm.lib_deps_S} -lib_ignore = ${common_mm.DMXin_lib_ignore} ;; requires V4 framework +lib_ignore = + ${common_mm.DMXin_lib_ignore} ;; requires V4 framework + ${esp32_legacy.lib_ignore} upload_speed = 460800 ; or 921600 ;; new V4 platform ;;platform = ${esp32.platform} @@ -2743,6 +2748,7 @@ lib_ignore = OneWire ; not needed as we don't include USERMOD_DALLASTEMPERATURE U8g2 ; not needed as we don't include USERMOD_FOUR_LINE_DISPLAY ${common_mm.animartrix_lib_ignore} ;; Tips our memory usage over the limit + ${esp32c3.lib_ignore} ; RAM: [== ] 22.1% (used 72408 bytes from 327680 bytes) ; Flash: [======== ] 83.5% (used 1313420 bytes from 1572864 bytes) @@ -2827,7 +2833,9 @@ build_flags = ${common.build_flags} ${esp32c3.build_flags} -D WLED_USE_MY_CONFIG ;-D WLED_DEBUG -D SR_DEBUG lib_deps = ${esp32c3.lib_deps} ${common_mm.lib_deps_S} -lib_ignore = IRremoteESP8266 ; use with WLED_DISABLE_INFRARED for faster compilation +lib_ignore = + IRremoteESP8266 ; use with WLED_DISABLE_INFRARED for faster compilation + ${esp32c3.lib_ignore} monitor_filters = esp32_exception_decoder ; RAM: [== ] 22.5% (used 73740 bytes from 327680 bytes) ; Flash: [==========] 96.3% (used 1515336 bytes from 1572864 bytes)