Compare commits

...

13 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
9672b9bee0 Final improvements: enhanced UI feedback and cleanup
Co-authored-by: DedeHai <6280424+DedeHai@users.noreply.github.com>
2025-09-18 08:01:52 +00:00
copilot-swe-agent[bot]
6d0921bdd7 Implement user backup functionality with web API and UI
Co-authored-by: DedeHai <6280424+DedeHai@users.noreply.github.com>
2025-09-18 07:59:15 +00:00
copilot-swe-agent[bot]
ea4661e907 Initial plan 2025-09-18 07:47:38 +00:00
Blaž Kristan
b375c718b0 Merge pull request #4771 from wled/fix-4643
Fix for #4643 - Missing MQTT updates
2025-09-17 12:20:41 +02:00
Will Miles
b16fbafd83 Merge pull request #4890 from willmmiles/esp32-rmthi
Import RmtHI driver
2025-09-16 21:32:16 -04:00
Damian Schneider
76cb2e9988 Improvements to heap-memory and PSRAM handling (#4791)
* Improved heap and PSRAM handling

- Segment `allocateData()` uses more elaborate DRAM checking to reduce fragmentation and allow for larger setups to run on low heap
- Segment data allocation fails if minimum contiguous block size runs low to keep the UI working
- Increased `MAX_SEGMENT_DATA` to account for better segment data handling
- Memory allocation functions try to keep enough DRAM for segment data
- Added constant `PSRAM_THRESHOLD` to improve PSARM usage
- Increase MIN_HEAP_SIZE to reduce risk of breaking UI due to low memory for JSON response
- ESP32 makes use of IRAM (no 8bit access) for pixeluffers, freeing up to 50kB of RAM
- Fix to properly get available heap on all platforms: added function `getFreeHeapSize()`
- Bugfix for effects that divide by SEGLEN: don't run FX in service() if segment is not active
-Syntax fix in AR: calloc() uses (numelements, size) as arguments

* Added new functions for allocation and heap checking

- added `allocate_buffer()` function that can be used to allocate large buffers: takes parameters to set preferred ram location, including 32bit accessible RAM on ESP32. Returns null if heap runs low or switches to PSRAM
- getFreeHeapSize() and getContiguousFreeHeap() helper functions for all platforms to correctly report free useable heap
- updated some constants
- updated segment data allocation to free the data if it is large

- replaced "psramsafe" variable with it's #ifdef: BOARD_HAS_PSRAM and made accomodating changes
- added some compile-time checks to handle invalid env. definitions
- updated all allocation functions and some of the logic behind them
- added use of fast RTC-Memory where available
- increased MIN_HEAP_SIZE for all systems (improved stability in tests)
- updated memory calculation in web-UI to account for required segment buffer
- added UI alerts if buffer allocation fails
- made getUsedSegmentData() non-private (used in buffer alloc function)
- changed MAX_SEGMENT_DATA
- added more detailed memory log to DEBUG output
- added debug output to buffer alloc function
2025-09-16 19:46:16 +02:00
Will Miles
2e834852d5 RmtHI: Add missing includes
While these were both fortunately included in Arduino.h, as
@coderabbitai suggests, it's best practice to be explicit for anything
one uses directly.
2025-09-01 22:03:18 -04:00
Will Miles
3e9e18dae2 Undo RMTHi change to platformio.ini
PlatformIO's Library Dependency Finder will take care of it based on
the #include.
2025-09-01 22:00:24 -04:00
Will Miles
831b68e60b usermods CI: Use correct base esp32 platform 2025-09-01 21:56:19 -04:00
Will Miles
5e3803a5bb RmtHI: Fix incorrect method typedefs 2025-09-01 21:51:36 -04:00
Will Miles
91d7e0c051 RmtHI: Remove incorrect default selection block 2025-09-01 21:50:57 -04:00
Will Miles
19a49e83d1 Import RmtHI driver
Pull the RMT High-priority Interrupt driver in to a vendored local
library, pending inclusion in upstream NeoPixelBus.

Driver is enabled only for XTensa chips; there's some unresolved
issue with nested interrupts on RISCV.
2025-08-30 15:57:04 -04:00
Blaž Kristan
5c74f0fa21 Fix wled#4643 2025-07-12 18:49:03 +02:00
23 changed files with 1995 additions and 234 deletions

1
.gitignore vendored
View File

@@ -23,3 +23,4 @@ wled-update.sh
/wled00/Release
/wled00/wled00.ino.cpp
/wled00/html_*.h
compile_commands.json

View File

@@ -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
<http://www.gnu.org/licenses/>.
-------------------------------------------------------------------------*/
#pragma once
#if defined(ARDUINO_ARCH_ESP32)
// Use the NeoEspRmtSpeed types from the driver-based implementation
#include <NeoPixelBus.h>
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<typename T_SPEED, typename T_CHANNEL> 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<gpio_num_t>(_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<uint8_t*>(malloc(_sizeData));
// data cleared later in Begin()
_dataSending = static_cast<uint8_t*>(malloc(_sizeData));
// no need to initialize it, it gets overwritten on every send
}
};
// normal
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2811, NeoEsp32RmtChannelN> NeoEsp32RmtHINWs2811Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannelN> NeoEsp32RmtHINWs2812xMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannelN> NeoEsp32RmtHINWs2816Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2805, NeoEsp32RmtChannelN> NeoEsp32RmtHINWs2805Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedSk6812, NeoEsp32RmtChannelN> NeoEsp32RmtHINSk6812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1814, NeoEsp32RmtChannelN> NeoEsp32RmtHINTm1814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1829, NeoEsp32RmtChannelN> NeoEsp32RmtHINTm1829Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1914, NeoEsp32RmtChannelN> NeoEsp32RmtHINTm1914Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedApa106, NeoEsp32RmtChannelN> NeoEsp32RmtHINApa106Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTx1812, NeoEsp32RmtChannelN> NeoEsp32RmtHINTx1812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedGs1903, NeoEsp32RmtChannelN> NeoEsp32RmtHINGs1903Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed800Kbps, NeoEsp32RmtChannelN> NeoEsp32RmtHIN800KbpsMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed400Kbps, NeoEsp32RmtChannelN> NeoEsp32RmtHIN400KbpsMethod;
typedef NeoEsp32RmtHINWs2805Method NeoEsp32RmtHINWs2814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2811, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Ws2811Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Ws2812xMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Ws2816Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2805, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Ws2805Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedSk6812, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Sk6812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1814, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Tm1814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1829, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Tm1829Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1914, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Tm1914Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedApa106, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Apa106Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTx1812, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Tx1812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedGs1903, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Gs1903Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed800Kbps, NeoEsp32RmtChannel0> NeoEsp32RmtHI0800KbpsMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed400Kbps, NeoEsp32RmtChannel0> NeoEsp32RmtHI0400KbpsMethod;
typedef NeoEsp32RmtHI0Ws2805Method NeoEsp32RmtHI0Ws2814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2811, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Ws2811Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Ws2812xMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Ws2816Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2805, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Ws2805Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedSk6812, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Sk6812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1814, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Tm1814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1829, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Tm1829Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1914, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Tm1914Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedApa106, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Apa106Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTx1812, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Tx1812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedGs1903, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Gs1903Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed800Kbps, NeoEsp32RmtChannel1> NeoEsp32RmtHI1800KbpsMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed400Kbps, NeoEsp32RmtChannel1> NeoEsp32RmtHI1400KbpsMethod;
typedef NeoEsp32RmtHI1Ws2805Method NeoEsp32RmtHI1Ws2814Method;
#if !defined(CONFIG_IDF_TARGET_ESP32C3)
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2811, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Ws2811Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Ws2812xMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Ws2816Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2805, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Ws2805Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedSk6812, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Sk6812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1814, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Tm1814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1829, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Tm1829Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1914, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Tm1914Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedApa106, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Apa106Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTx1812, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Tx1812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedGs1903, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Gs1903Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed800Kbps, NeoEsp32RmtChannel2> NeoEsp32RmtHI2800KbpsMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed400Kbps, NeoEsp32RmtChannel2> NeoEsp32RmtHI2400KbpsMethod;
typedef NeoEsp32RmtHI2Ws2805Method NeoEsp32RmtHI2Ws2814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2811, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Ws2811Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Ws2812xMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Ws2816Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2805, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Ws2805Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedSk6812, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Sk6812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1814, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Tm1814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1829, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Tm1829Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1914, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Tm1914Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedApa106, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Apa106Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTx1812, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Tx1812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedGs1903, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Gs1903Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed800Kbps, NeoEsp32RmtChannel3> NeoEsp32RmtHI3800KbpsMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed400Kbps, NeoEsp32RmtChannel3> NeoEsp32RmtHI3400KbpsMethod;
typedef NeoEsp32RmtHI3Ws2805Method NeoEsp32RmtHI3Ws2814Method;
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32S3)
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2811, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Ws2811Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Ws2812xMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Ws2816Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2805, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Ws2805Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedSk6812, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Sk6812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1814, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Tm1814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1829, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Tm1829Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1914, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Tm1914Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedApa106, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Apa106Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTx1812, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Tx1812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedGs1903, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Gs1903Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed800Kbps, NeoEsp32RmtChannel4> NeoEsp32RmtHI4800KbpsMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed400Kbps, NeoEsp32RmtChannel4> NeoEsp32RmtHI4400KbpsMethod;
typedef NeoEsp32RmtHI4Ws2805Method NeoEsp32RmtHI4Ws2814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2811, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Ws2811Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Ws2812xMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Ws2816Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2805, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Ws2805Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedSk6812, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Sk6812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1814, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Tm1814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1829, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Tm1829Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1914, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Tm1914Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedApa106, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Apa106Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTx1812, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Tx1812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedGs1903, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Gs1903Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed800Kbps, NeoEsp32RmtChannel5> NeoEsp32RmtHI5800KbpsMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed400Kbps, NeoEsp32RmtChannel5> NeoEsp32RmtHI5400KbpsMethod;
typedef NeoEsp32RmtHI5Ws2805Method NeoEsp32RmtHI5Ws2814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2811, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Ws2811Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Ws2812xMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Ws2816Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2805, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Ws2805Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedSk6812, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Sk6812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1814, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Tm1814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1829, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Tm1829Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1914, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Tm1914Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedApa106, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Apa106Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTx1812, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Tx1812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedGs1903, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Gs1903Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed800Kbps, NeoEsp32RmtChannel6> NeoEsp32RmtHI6800KbpsMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed400Kbps, NeoEsp32RmtChannel6> NeoEsp32RmtHI6400KbpsMethod;
typedef NeoEsp32RmtHI6Ws2805Method NeoEsp32RmtHI6Ws2814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2811, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Ws2811Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Ws2812xMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Ws2816Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2805, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Ws2805Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedSk6812, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Sk6812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1814, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Tm1814Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1829, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Tm1829Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1914, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Tm1914Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedApa106, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Apa106Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTx1812, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Tx1812Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedGs1903, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Gs1903Method;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed800Kbps, NeoEsp32RmtChannel7> NeoEsp32RmtHI7800KbpsMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed400Kbps, NeoEsp32RmtChannel7> NeoEsp32RmtHI7400KbpsMethod;
typedef NeoEsp32RmtHI7Ws2805Method NeoEsp32RmtHI7Ws2814Method;
#endif // !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32S3)
#endif // !defined(CONFIG_IDF_TARGET_ESP32C3)
// inverted
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2811, NeoEsp32RmtChannelN> NeoEsp32RmtHINWs2811InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannelN> NeoEsp32RmtHINWs2812xInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannelN> NeoEsp32RmtHINWs2816InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2805, NeoEsp32RmtChannelN> NeoEsp32RmtHINWs2805InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedSk6812, NeoEsp32RmtChannelN> NeoEsp32RmtHINSk6812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1814, NeoEsp32RmtChannelN> NeoEsp32RmtHINTm1814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1829, NeoEsp32RmtChannelN> NeoEsp32RmtHINTm1829InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1914, NeoEsp32RmtChannelN> NeoEsp32RmtHINTm1914InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedApa106, NeoEsp32RmtChannelN> NeoEsp32RmtHINApa106InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTx1812, NeoEsp32RmtChannelN> NeoEsp32RmtHINTx1812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedGs1903, NeoEsp32RmtChannelN> NeoEsp32RmtHINGs1903InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed800Kbps, NeoEsp32RmtChannelN> NeoEsp32RmtHIN800KbpsInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed400Kbps, NeoEsp32RmtChannelN> NeoEsp32RmtHIN400KbpsInvertedMethod;
typedef NeoEsp32RmtHINWs2805InvertedMethod NeoEsp32RmtHINWs2814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2811, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Ws2811InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Ws2812xInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Ws2816InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2805, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Ws2805InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedSk6812, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Sk6812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1814, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Tm1814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1829, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Tm1829InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1914, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Tm1914InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedApa106, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Apa106InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTx1812, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Tx1812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedGs1903, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Gs1903InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed800Kbps, NeoEsp32RmtChannel0> NeoEsp32RmtHI0800KbpsInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed400Kbps, NeoEsp32RmtChannel0> NeoEsp32RmtHI0400KbpsInvertedMethod;
typedef NeoEsp32RmtHI0Ws2805InvertedMethod NeoEsp32RmtHI0Ws2814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2811, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Ws2811InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Ws2812xInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Ws2816InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2805, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Ws2805InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedSk6812, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Sk6812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1814, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Tm1814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1829, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Tm1829InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1914, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Tm1914InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedApa106, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Apa106InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTx1812, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Tx1812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedGs1903, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Gs1903InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed800Kbps, NeoEsp32RmtChannel1> NeoEsp32RmtHI1800KbpsInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed400Kbps, NeoEsp32RmtChannel1> NeoEsp32RmtHI1400KbpsInvertedMethod;
typedef NeoEsp32RmtHI1Ws2805InvertedMethod NeoEsp32RmtHI1Ws2814InvertedMethod;
#if !defined(CONFIG_IDF_TARGET_ESP32C3)
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2811, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Ws2811InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Ws2812xInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Ws2816InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2805, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Ws2805InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedSk6812, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Sk6812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1814, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Tm1814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1829, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Tm1829InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1914, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Tm1914InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedApa106, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Apa106InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTx1812, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Tx1812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedGs1903, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Gs1903InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed800Kbps, NeoEsp32RmtChannel2> NeoEsp32RmtHI2800KbpsInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed400Kbps, NeoEsp32RmtChannel2> NeoEsp32RmtHI2400KbpsInvertedMethod;
typedef NeoEsp32RmtHI2Ws2805InvertedMethod NeoEsp32RmtHI2Ws2814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2811, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Ws2811InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Ws2812xInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2805, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Ws2805InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Ws2816InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedSk6812, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Sk6812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1814, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Tm1814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1829, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Tm1829InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1914, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Tm1914InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedApa106, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Apa106InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTx1812, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Tx1812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedGs1903, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Gs1903InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed800Kbps, NeoEsp32RmtChannel3> NeoEsp32RmtHI3800KbpsInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed400Kbps, NeoEsp32RmtChannel3> NeoEsp32RmtHI3400KbpsInvertedMethod;
typedef NeoEsp32RmtHI3Ws2805InvertedMethod NeoEsp32RmtHI3Ws2814InvertedMethod;
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32S3)
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2811, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Ws2811InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Ws2812xInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Ws2816InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2805, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Ws2805InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedSk6812, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Sk6812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1814, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Tm1814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1829, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Tm1829InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1914, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Tm1914InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedApa106, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Apa106InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTx1812, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Tx1812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedGs1903, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Gs1903InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed800Kbps, NeoEsp32RmtChannel4> NeoEsp32RmtHI4800KbpsInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed400Kbps, NeoEsp32RmtChannel4> NeoEsp32RmtHI4400KbpsInvertedMethod;
typedef NeoEsp32RmtHI4Ws2805InvertedMethod NeoEsp32RmtHI4Ws2814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2811, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Ws2811InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Ws2812xInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Ws2816InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2805, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Ws2805InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedSk6812, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Sk6812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1814, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Tm1814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1829, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Tm1829InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1914, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Tm1914InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedApa106, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Apa106InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTx1812, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Tx1812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedGs1903, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Gs1903InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed800Kbps, NeoEsp32RmtChannel5> NeoEsp32RmtHI5800KbpsInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed400Kbps, NeoEsp32RmtChannel5> NeoEsp32RmtHI5400KbpsInvertedMethod;
typedef NeoEsp32RmtHI5Ws2805InvertedMethod NeoEsp32RmtHI5Ws2814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2811, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Ws2811InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Ws2812xInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Ws2816InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2805, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Ws2805InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedSk6812, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Sk6812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1814, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Tm1814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1829, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Tm1829InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1914, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Tm1914InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedApa106, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Apa106InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTx1812, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Tx1812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedGs1903, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Gs1903InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed800Kbps, NeoEsp32RmtChannel6> NeoEsp32RmtHI6800KbpsInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed400Kbps, NeoEsp32RmtChannel6> NeoEsp32RmtHI6400KbpsInvertedMethod;
typedef NeoEsp32RmtHI6Ws2805InvertedMethod NeoEsp32RmtHI6Ws2814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2811, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Ws2811InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Ws2812xInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Ws2816InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2805, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Ws2805InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedSk6812, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Sk6812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1814, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Tm1814InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1829, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Tm1829InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1914, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Tm1914InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedApa106, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Apa106InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTx1812, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Tx1812InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedGs1903, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Gs1903InvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed800Kbps, NeoEsp32RmtChannel7> NeoEsp32RmtHI7800KbpsInvertedMethod;
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed400Kbps, NeoEsp32RmtChannel7> NeoEsp32RmtHI7400KbpsInvertedMethod;
typedef NeoEsp32RmtHI7Ws2805InvertedMethod NeoEsp32RmtHI7Ws2814InvertedMethod;
#endif // !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32S3)
#endif // !defined(CONFIG_IDF_TARGET_ESP32C3)
#endif

View File

@@ -0,0 +1,12 @@
{
"name": "NeoESP32RmtHI",
"build": { "libArchive": false },
"platforms": ["espressif32"],
"dependencies": [
{
"owner": "makuna",
"name": "NeoPixelBus",
"version": "^2.8.3"
}
]
}

View File

@@ -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 <freertos/xtensa_context.h>
#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

View File

@@ -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
<http://www.gnu.org/licenses/>.
-------------------------------------------------------------------------*/
#include <Arduino.h>
#if defined(ARDUINO_ARCH_ESP32)
#include <algorithm>
#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<NeoEsp32RmtHIChannelState**>(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<NeoEsp32RmtHIChannelState*>(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

View File

@@ -141,7 +141,6 @@ lib_deps =
fastled/FastLED @ 3.6.0
IRremoteESP8266 @ 2.8.2
makuna/NeoPixelBus @ 2.8.3
#https://github.com/makuna/NeoPixelBus.git#CoreShaderBeta
https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.4.2
marvinroger/AsyncMqttClient @ 0.9.0
# for I2C interface

View File

@@ -224,8 +224,8 @@ void FFTcode(void * parameter)
DEBUGSR_PRINT("FFT started on core: "); DEBUGSR_PRINTLN(xPortGetCoreID());
// allocate FFT buffers on first call
if (vReal == nullptr) vReal = (float*) calloc(sizeof(float), samplesFFT);
if (vImag == nullptr) vImag = (float*) calloc(sizeof(float), samplesFFT);
if (vReal == nullptr) vReal = (float*) calloc(samplesFFT, sizeof(float));
if (vImag == nullptr) vImag = (float*) calloc(samplesFFT, sizeof(float));
if ((vReal == nullptr) || (vImag == nullptr)) {
// something went wrong
if (vReal) free(vReal); vReal = nullptr;

View File

@@ -88,23 +88,26 @@ extern byte realtimeMode; // used in getMappedPixelIndex()
#endif
#define FPS_CALC_SHIFT 7 // bit shift for fixed point math
/* each segment uses 82 bytes of SRAM memory, so if you're application fails because of
insufficient memory, decreasing MAX_NUM_SEGMENTS may help */
// heap memory limit for effects data, pixel buffers try to reserve it if PSRAM is available
#ifdef ESP8266
#define MAX_NUM_SEGMENTS 16
/* How much data bytes all segments combined may allocate */
#define MAX_SEGMENT_DATA 5120
#define MAX_SEGMENT_DATA (6*1024) // 6k by default
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
#define MAX_NUM_SEGMENTS 20
#define MAX_SEGMENT_DATA (MAX_NUM_SEGMENTS*512) // 10k by default (S2 is short on free RAM)
#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
#define MAX_NUM_SEGMENTS 32 // warning: going beyond 32 may consume too much RAM for stable operation
#define MAX_SEGMENT_DATA (MAX_NUM_SEGMENTS*1280) // 40k by default
#ifdef BOARD_HAS_PSRAM
#define MAX_NUM_SEGMENTS 64
#else
#define MAX_NUM_SEGMENTS 32
#endif
#define MAX_SEGMENT_DATA (64*1024) // 64k by default, limit does not apply if PSRAM is available
#endif
/* How much data bytes each segment should max allocate to leave enough space for other segments,
assuming each segment uses the same amount of data. 256 for ESP8266, 640 for ESP32. */
#define FAIR_DATA_PER_SEG (MAX_SEGMENT_DATA / WS2812FX::getMaxSegments())
#define FAIR_DATA_PER_SEG (MAX_SEGMENT_DATA / MAX_NUM_SEGMENTS)
#define MIN_SHOW_DELAY (_frametime < 16 ? 8 : 15)
@@ -533,7 +536,6 @@ class Segment {
protected:
inline static unsigned getUsedSegmentData() { return Segment::_usedSegmentData; }
inline static void addUsedSegmentData(int len) { Segment::_usedSegmentData += len; }
inline uint32_t *getPixels() const { return pixels; }
@@ -600,8 +602,8 @@ class Segment {
, _t(nullptr)
{
DEBUGFX_PRINTF_P(PSTR("-- Creating segment: %p [%d,%d:%d,%d]\n"), this, (int)start, (int)stop, (int)startY, (int)stopY);
// allocate render buffer (always entire segment)
pixels = static_cast<uint32_t*>(d_calloc(sizeof(uint32_t), length())); // error handling is also done in isActive()
// allocate render buffer (always entire segment), prefer PSRAM if DRAM is running low. Note: impact on FPS with PSRAM buffer is low (<2% with QSPI PSRAM)
pixels = static_cast<uint32_t*>(allocate_buffer(length() * sizeof(uint32_t), BFRALLOC_PREFER_PSRAM | BFRALLOC_NOBYTEACCESS | BFRALLOC_CLEAR));
if (!pixels) {
DEBUGFX_PRINTLN(F("!!! Not enough RAM for pixel buffer !!!"));
extern byte errorFlag;
@@ -623,7 +625,7 @@ class Segment {
#endif
clearName();
deallocateData();
d_free(pixels);
p_free(pixels);
}
Segment& operator= (const Segment &orig); // copy assignment
@@ -646,7 +648,7 @@ class Segment {
inline uint16_t groupLength() const { return grouping + spacing; }
inline uint8_t getLightCapabilities() const { return _capabilities; }
inline void deactivate() { setGeometry(0,0); }
inline Segment &clearName() { d_free(name); name = nullptr; return *this; }
inline Segment &clearName() { p_free(name); name = nullptr; return *this; }
inline Segment &setName(const String &name) { return setName(name.c_str()); }
inline static unsigned vLength() { return Segment::_vLength; }
@@ -672,6 +674,7 @@ class Segment {
inline uint16_t dataSize() const { return _dataLen; }
bool allocateData(size_t len); // allocates effect data buffer in heap and clears it
void deallocateData(); // deallocates (frees) effect data buffer from heap
inline static unsigned getUsedSegmentData() { return Segment::_usedSegmentData; }
/**
* Flags that before the next effect is calculated,
* the internal segment state should be reset.
@@ -868,8 +871,8 @@ class WS2812FX {
}
~WS2812FX() {
d_free(_pixels);
d_free(_pixelCCT); // just in case
p_free(_pixels);
p_free(_pixelCCT); // just in case
d_free(customMappingTable);
_mode.clear();
_modeData.clear();

89
wled00/FX_fcn.cpp Normal file → Executable file
View File

@@ -68,10 +68,10 @@ Segment::Segment(const Segment &orig) {
if (!stop) return; // nothing to do if segment is inactive/invalid
if (orig.pixels) {
// allocate pixel buffer: prefer IRAM/PSRAM
pixels = static_cast<uint32_t*>(d_malloc(sizeof(uint32_t) * orig.length()));
pixels = static_cast<uint32_t*>(allocate_buffer(orig.length() * sizeof(uint32_t), BFRALLOC_PREFER_PSRAM | BFRALLOC_NOBYTEACCESS));
if (pixels) {
memcpy(pixels, orig.pixels, sizeof(uint32_t) * orig.length());
if (orig.name) { name = static_cast<char*>(d_malloc(strlen(orig.name)+1)); if (name) strcpy(name, orig.name); }
if (orig.name) { name = static_cast<char*>(allocate_buffer(strlen(orig.name)+1, BFRALLOC_PREFER_PSRAM)); if (name) strcpy(name, orig.name); }
if (orig.data) { if (allocateData(orig._dataLen)) memcpy(data, orig.data, orig._dataLen); }
} else {
DEBUGFX_PRINTLN(F("!!! Not enough RAM for pixel buffer !!!"));
@@ -97,10 +97,10 @@ Segment& Segment::operator= (const Segment &orig) {
//DEBUG_PRINTF_P(PSTR("-- Copying segment: %p -> %p\n"), &orig, this);
if (this != &orig) {
// clean destination
if (name) { d_free(name); name = nullptr; }
if (name) { p_free(name); name = nullptr; }
if (_t) stopTransition(); // also erases _t
deallocateData();
d_free(pixels);
p_free(pixels);
// copy source
memcpy((void*)this, (void*)&orig, sizeof(Segment));
// erase pointers to allocated data
@@ -111,10 +111,10 @@ Segment& Segment::operator= (const Segment &orig) {
// copy source data
if (orig.pixels) {
// allocate pixel buffer: prefer IRAM/PSRAM
pixels = static_cast<uint32_t*>(d_malloc(sizeof(uint32_t) * orig.length()));
pixels = static_cast<uint32_t*>(allocate_buffer(orig.length() * sizeof(uint32_t), BFRALLOC_PREFER_PSRAM | BFRALLOC_NOBYTEACCESS));
if (pixels) {
memcpy(pixels, orig.pixels, sizeof(uint32_t) * orig.length());
if (orig.name) { name = static_cast<char*>(d_malloc(strlen(orig.name)+1)); if (name) strcpy(name, orig.name); }
if (orig.name) { name = static_cast<char*>(allocate_buffer(strlen(orig.name)+1, BFRALLOC_PREFER_PSRAM)); if (name) strcpy(name, orig.name); }
if (orig.data) { if (allocateData(orig._dataLen)) memcpy(data, orig.data, orig._dataLen); }
} else {
DEBUG_PRINTLN(F("!!! Not enough RAM for pixel buffer !!!"));
@@ -130,10 +130,10 @@ Segment& Segment::operator= (const Segment &orig) {
Segment& Segment::operator= (Segment &&orig) noexcept {
//DEBUG_PRINTF_P(PSTR("-- Moving segment: %p -> %p\n"), &orig, this);
if (this != &orig) {
if (name) { d_free(name); name = nullptr; } // free old name
if (name) { p_free(name); name = nullptr; } // free old name
if (_t) stopTransition(); // also erases _t
deallocateData(); // free old runtime data
d_free(pixels); // free old pixel buffer
p_free(pixels); // free old pixel buffer
// move source data
memcpy((void*)this, (void*)&orig, sizeof(Segment));
orig.name = nullptr;
@@ -147,35 +147,38 @@ Segment& Segment::operator= (Segment &&orig) noexcept {
// allocates effect data buffer on heap and initialises (erases) it
bool Segment::allocateData(size_t len) {
if (len == 0) return false; // nothing to do
if (data && _dataLen >= len) { // already allocated enough (reduce fragmentation)
if (len == 0) return false; // nothing to do
if (data && _dataLen >= len) { // already allocated enough (reduce fragmentation)
if (call == 0) {
//DEBUG_PRINTF_P(PSTR("-- Clearing data (%d): %p\n"), len, this);
memset(data, 0, len); // erase buffer if called during effect initialisation
if (_dataLen < FAIR_DATA_PER_SEG) { // segment data is small
//DEBUG_PRINTF_P(PSTR("-- Clearing data (%d): %p\n"), len, this);
memset(data, 0, len); // erase buffer if called during effect initialisation
return true; // no need to reallocate
}
}
return true;
else
return true;
}
//DEBUG_PRINTF_P(PSTR("-- Allocating data (%d): %p\n"), len, this);
// limit to MAX_SEGMENT_DATA if there is no PSRAM, otherwise prefer functionality over speed
#ifndef BOARD_HAS_PSRAM
if (Segment::getUsedSegmentData() + len - _dataLen > MAX_SEGMENT_DATA) {
// not enough memory
DEBUG_PRINTF_P(PSTR("!!! Not enough RAM: %d/%d !!!\n"), len, Segment::getUsedSegmentData());
DEBUG_PRINTF_P(PSTR("SegmentData limit reached: %d/%d\n"), len, Segment::getUsedSegmentData());
errorFlag = ERR_NORAM;
return false;
}
// prefer DRAM over SPI RAM on ESP32 since it is slow
if (data) {
data = (byte*)d_realloc_malloc(data, len); // realloc with malloc fallback
if (!data) {
data = nullptr;
Segment::addUsedSegmentData(-_dataLen); // subtract original buffer size
_dataLen = 0; // reset data length
}
}
else data = (byte*)d_malloc(len);
#endif
if (data) {
memset(data, 0, len); // erase buffer
Segment::addUsedSegmentData(len - _dataLen);
d_free(data); // free data and try to allocate again (segment buffer may be blocking contiguous heap)
Segment::addUsedSegmentData(-_dataLen); // subtract buffer size
}
data = static_cast<byte*>(allocate_buffer(len, BFRALLOC_PREFER_DRAM | BFRALLOC_CLEAR)); // prefer DRAM over PSRAM for speed
if (data) {
Segment::addUsedSegmentData(len);
_dataLen = len;
//DEBUG_PRINTF_P(PSTR("--- Allocated data (%p): %d/%d -> %p\n"), this, len, Segment::getUsedSegmentData(), data);
return true;
@@ -209,7 +212,11 @@ void Segment::deallocateData() {
void Segment::resetIfRequired() {
if (!reset || !isActive()) return;
//DEBUG_PRINTF_P(PSTR("-- Segment reset: %p\n"), this);
if (data && _dataLen > 0) memset(data, 0, _dataLen); // prevent heap fragmentation (just erase buffer instead of deallocateData())
if (data && _dataLen > 0) {
if (_dataLen > FAIR_DATA_PER_SEG) deallocateData(); // do not keep large allocations
else memset(data, 0, _dataLen); // can prevent heap fragmentation
DEBUG_PRINTF_P(PSTR("-- Segment %p reset, data cleared\n"), this);
}
if (pixels) for (size_t i = 0; i < length(); i++) pixels[i] = BLACK; // clear pixel buffer
next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0;
reset = false;
@@ -466,7 +473,7 @@ void Segment::setGeometry(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, ui
if (length() != oldLength) {
// allocate render buffer (always entire segment), prefer IRAM/PSRAM. Note: impact on FPS with PSRAM buffer is low (<2% with QSPI PSRAM) on S2/S3
p_free(pixels);
pixels = static_cast<uint32_t*>(d_malloc(sizeof(uint32_t) * length()));
pixels = static_cast<uint32_t*>(allocate_buffer(length() * sizeof(uint32_t), BFRALLOC_PREFER_PSRAM | BFRALLOC_NOBYTEACCESS));
if (!pixels) {
DEBUGFX_PRINTLN(F("!!! Not enough RAM for pixel buffer !!!"));
deallocateData();
@@ -581,8 +588,8 @@ Segment &Segment::setName(const char *newName) {
if (newName) {
const int newLen = min(strlen(newName), (size_t)WLED_MAX_SEGNAME_LEN);
if (newLen) {
if (name) d_free(name); // free old name
name = static_cast<char*>(d_malloc(newLen+1));
if (name) p_free(name); // free old name
name = static_cast<char*>(allocate_buffer(newLen+1, BFRALLOC_PREFER_PSRAM));
if (mode == FX_MODE_2DSCROLLTEXT) startTransition(strip.getTransition(), true); // if the name changes in scrolling text mode, we need to copy the segment for blending
if (name) strlcpy(name, newName, newLen+1);
return *this;
@@ -1177,7 +1184,10 @@ void WS2812FX::finalizeInit() {
mem += bus.memUsage(Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) ? digitalCount++ : 0); // includes global buffer
if (mem <= MAX_LED_MEMORY) {
if (BusManager::add(bus) == -1) break;
} else DEBUG_PRINTF_P(PSTR("Out of LED memory! Bus %d (%d) #%u not created."), (int)bus.type, (int)bus.count, digitalCount);
} else {
errorFlag = ERR_NORAM_PX; // alert UI
DEBUG_PRINTF_P(PSTR("Out of LED memory! Bus %d (%d) #%u not created."), (int)bus.type, (int)bus.count, digitalCount);
}
}
busConfigs.clear();
busConfigs.shrink_to_fit();
@@ -1209,10 +1219,11 @@ void WS2812FX::finalizeInit() {
deserializeMap(); // (re)load default ledmap (will also setUpMatrix() if ledmap does not exist)
// allocate frame buffer after matrix has been set up (gaps!)
d_free(_pixels); // using realloc on large buffers can cause additional fragmentation instead of reducing it
_pixels = static_cast<uint32_t*>(d_malloc(getLengthTotal() * sizeof(uint32_t)));
p_free(_pixels); // using realloc on large buffers can cause additional fragmentation instead of reducing it
// 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<uint32_t*>(allocate_buffer(getLengthTotal() * sizeof(uint32_t), BFRALLOC_ENFORCE_PSRAM | BFRALLOC_NOBYTEACCESS | BFRALLOC_CLEAR));
DEBUG_PRINTF_P(PSTR("strip buffer size: %uB\n"), getLengthTotal() * sizeof(uint32_t));
DEBUG_PRINTF_P(PSTR("Heap after strip init: %uB\n"), ESP.getFreeHeap());
DEBUG_PRINTF_P(PSTR("Heap after strip init: %uB\n"), getFreeHeapSize());
}
void WS2812FX::service() {
@@ -1552,7 +1563,11 @@ void WS2812FX::blendSegment(const Segment &topSegment) const {
}
void WS2812FX::show() {
if (!_pixels) return; // no pixels allocated, nothing to show
if (!_pixels) {
DEBUGFX_PRINTLN(F("Error: no _pixels!"));
errorFlag = ERR_NORAM;
return; // no pixels allocated, nothing to show
}
unsigned long showNow = millis();
size_t diff = showNow - _lastShow;
@@ -1562,7 +1577,7 @@ void WS2812FX::show() {
// 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<uint8_t*>(d_malloc(totalLen * sizeof(uint8_t))); // allocate CCT buffer if necessary
_pixelCCT = static_cast<uint8_t*>(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
if (realtimeMode == REALTIME_MODE_INACTIVE || useMainSegmentOnly || realtimeOverride > REALTIME_OVERRIDE_NONE) {
@@ -1596,7 +1611,7 @@ void WS2812FX::show() {
}
Bus::setCCT(oldCCT); // restore old CCT for ABL adjustments
d_free(_pixelCCT);
p_free(_pixelCCT);
_pixelCCT = nullptr;
// some buses send asynchronously and this method will return before

View File

@@ -39,35 +39,29 @@ uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb);
uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, const byte *buffer, uint8_t bri=255, bool isRGBW=false);
//util.cpp
// PSRAM allocation wrappers
#if !defined(ESP8266) && !defined(CONFIG_IDF_TARGET_ESP32C3)
// memory allocation wrappers
extern "C" {
void *p_malloc(size_t); // prefer PSRAM over DRAM
void *p_calloc(size_t, size_t); // prefer PSRAM over DRAM
void *p_realloc(void *, size_t); // prefer PSRAM over DRAM
void *p_realloc_malloc(void *ptr, size_t size); // realloc with malloc fallback, prefer PSRAM over DRAM
inline void p_free(void *ptr) { heap_caps_free(ptr); }
void *d_malloc(size_t); // prefer DRAM over PSRAM
void *d_calloc(size_t, size_t); // prefer DRAM over PSRAM
void *d_realloc(void *, size_t); // prefer DRAM over PSRAM
void *d_realloc_malloc(void *ptr, size_t size); // realloc with malloc fallback, prefer DRAM over PSRAM
// prefer DRAM over PSRAM (if available) in d_ alloc functions
void *d_malloc(size_t);
void *d_calloc(size_t, size_t);
void *d_realloc_malloc(void *ptr, size_t size);
#ifndef ESP8266
inline void d_free(void *ptr) { heap_caps_free(ptr); }
#else
inline void d_free(void *ptr) { free(ptr); }
#endif
#if defined(BOARD_HAS_PSRAM)
// prefer PSRAM over DRAM in p_ alloc functions
void *p_malloc(size_t);
void *p_calloc(size_t, size_t);
void *p_realloc_malloc(void *ptr, size_t size);
inline void p_free(void *ptr) { heap_caps_free(ptr); }
#else
#define p_malloc d_malloc
#define p_calloc d_calloc
#define p_free d_free
#endif
}
#else
extern "C" {
void *realloc_malloc(void *ptr, size_t size);
}
#define p_malloc malloc
#define p_calloc calloc
#define p_realloc realloc
#define p_realloc_malloc realloc_malloc
#define p_free free
#define d_malloc malloc
#define d_calloc calloc
#define d_realloc realloc
#define d_realloc_malloc realloc_malloc
#define d_free free
#endif
//color mangling macros
#define RGBW32(r,g,b,w) (uint32_t((byte(w) << 24) | (byte(r) << 16) | (byte(g) << 8) | (byte(b))))
@@ -902,7 +896,7 @@ void BusManager::esp32RMTInvertIdle() {
else if (lvl == RMT_IDLE_LEVEL_LOW) lvl = RMT_IDLE_LEVEL_HIGH;
else continue;
rmt_set_idle_level(ch, idle_out, lvl);
u++
u++;
}
}
#endif

View File

@@ -244,53 +244,61 @@
typedef NeoEsp32I2s1Tm1914Method X1Tm1914Method;
#endif
// RMT driver selection
#if !defined(WLED_USE_SHARED_RMT) && !defined(__riscv)
#include <NeoEsp32RmtHIMethod.h>
#define NeoEsp32RmtMethod(x) NeoEsp32RmtHIN ## x ## Method
#else
#define NeoEsp32RmtMethod(x) NeoEsp32RmtN ## x ## Method
#endif
//RGB
#define B_32_RN_NEO_3 NeoPixelBus<NeoGrbFeature, NeoEsp32RmtNWs2812xMethod> // ESP32, S2, S3, C3
#define B_32_RN_NEO_3 NeoPixelBus<NeoGrbFeature, NeoEsp32RmtMethod(Ws2812x)> // ESP32, S2, S3, C3
//#define B_32_IN_NEO_3 NeoPixelBus<NeoGrbFeature, NeoEsp32I2sNWs2812xMethod> // ESP32 (dynamic I2S selection)
#define B_32_I2_NEO_3 NeoPixelBus<NeoGrbFeature, X1Ws2812xMethod> // ESP32, S2, S3 (automatic I2S selection, see typedef above)
#define B_32_IP_NEO_3 NeoPixelBus<NeoGrbFeature, X8Ws2812xMethod> // parallel I2S (ESP32, S2, S3)
//RGBW
#define B_32_RN_NEO_4 NeoPixelBus<NeoGrbwFeature, NeoEsp32RmtNSk6812Method>
#define B_32_RN_NEO_4 NeoPixelBus<NeoGrbwFeature, NeoEsp32RmtMethod(Sk6812)>
#define B_32_I2_NEO_4 NeoPixelBus<NeoGrbwFeature, X1Sk6812Method>
#define B_32_IP_NEO_4 NeoPixelBus<NeoGrbwFeature, X8Sk6812Method> // parallel I2S
//400Kbps
#define B_32_RN_400_3 NeoPixelBus<NeoGrbFeature, NeoEsp32RmtN400KbpsMethod>
#define B_32_RN_400_3 NeoPixelBus<NeoGrbFeature, NeoEsp32RmtMethod(400Kbps)>
#define B_32_I2_400_3 NeoPixelBus<NeoGrbFeature, X1400KbpsMethod>
#define B_32_IP_400_3 NeoPixelBus<NeoGrbFeature, X8400KbpsMethod> // parallel I2S
//TM1814 (RGBW)
#define B_32_RN_TM1_4 NeoPixelBus<NeoWrgbTm1814Feature, NeoEsp32RmtNTm1814Method>
#define B_32_RN_TM1_4 NeoPixelBus<NeoWrgbTm1814Feature, NeoEsp32RmtMethod(Tm1814)>
#define B_32_I2_TM1_4 NeoPixelBus<NeoWrgbTm1814Feature, X1Tm1814Method>
#define B_32_IP_TM1_4 NeoPixelBus<NeoWrgbTm1814Feature, X8Tm1814Method> // parallel I2S
//TM1829 (RGB)
#define B_32_RN_TM2_3 NeoPixelBus<NeoBrgFeature, NeoEsp32RmtNTm1829Method>
#define B_32_RN_TM2_3 NeoPixelBus<NeoBrgFeature, NeoEsp32RmtMethod(Tm1829)>
#define B_32_I2_TM2_3 NeoPixelBus<NeoBrgFeature, X1Tm1829Method>
#define B_32_IP_TM2_3 NeoPixelBus<NeoBrgFeature, X8Tm1829Method> // parallel I2S
//UCS8903
#define B_32_RN_UCS_3 NeoPixelBus<NeoRgbUcs8903Feature, NeoEsp32RmtNWs2812xMethod>
#define B_32_RN_UCS_3 NeoPixelBus<NeoRgbUcs8903Feature, NeoEsp32RmtMethod(Ws2812x)>
#define B_32_I2_UCS_3 NeoPixelBus<NeoRgbUcs8903Feature, X1800KbpsMethod>
#define B_32_IP_UCS_3 NeoPixelBus<NeoRgbUcs8903Feature, X8800KbpsMethod> // parallel I2S
//UCS8904
#define B_32_RN_UCS_4 NeoPixelBus<NeoRgbwUcs8904Feature, NeoEsp32RmtNWs2812xMethod>
#define B_32_RN_UCS_4 NeoPixelBus<NeoRgbwUcs8904Feature, NeoEsp32RmtMethod(Ws2812x)>
#define B_32_I2_UCS_4 NeoPixelBus<NeoRgbwUcs8904Feature, X1800KbpsMethod>
#define B_32_IP_UCS_4 NeoPixelBus<NeoRgbwUcs8904Feature, X8800KbpsMethod>// parallel I2S
//APA106
#define B_32_RN_APA106_3 NeoPixelBus<NeoGrbFeature, NeoEsp32RmtNApa106Method>
#define B_32_RN_APA106_3 NeoPixelBus<NeoGrbFeature, NeoEsp32RmtMethod(Apa106)>
#define B_32_I2_APA106_3 NeoPixelBus<NeoGrbFeature, X1Apa106Method>
#define B_32_IP_APA106_3 NeoPixelBus<NeoGrbFeature, X8Apa106Method> // parallel I2S
//FW1906 GRBCW
#define B_32_RN_FW6_5 NeoPixelBus<NeoGrbcwxFeature, NeoEsp32RmtNWs2812xMethod>
#define B_32_RN_FW6_5 NeoPixelBus<NeoGrbcwxFeature, NeoEsp32RmtMethod(Ws2812x)>
#define B_32_I2_FW6_5 NeoPixelBus<NeoGrbcwxFeature, X1800KbpsMethod>
#define B_32_IP_FW6_5 NeoPixelBus<NeoGrbcwxFeature, X8800KbpsMethod> // parallel I2S
//WS2805 RGBWC
#define B_32_RN_2805_5 NeoPixelBus<NeoGrbwwFeature, NeoEsp32RmtNWs2805Method>
#define B_32_RN_2805_5 NeoPixelBus<NeoGrbwwFeature, NeoEsp32RmtMethod(Ws2805)>
#define B_32_I2_2805_5 NeoPixelBus<NeoGrbwwFeature, X1Ws2805Method>
#define B_32_IP_2805_5 NeoPixelBus<NeoGrbwwFeature, X8Ws2805Method> // parallel I2S
//TM1914 (RGB)
#define B_32_RN_TM1914_3 NeoPixelBus<NeoGrbTm1914Feature, NeoEsp32RmtNTm1914Method>
#define B_32_RN_TM1914_3 NeoPixelBus<NeoGrbTm1914Feature, NeoEsp32RmtMethod(Tm1914)>
#define B_32_I2_TM1914_3 NeoPixelBus<NeoGrbTm1914Feature, X1Tm1914Method>
#define B_32_IP_TM1914_3 NeoPixelBus<NeoGrbTm1914Feature, X8Tm1914Method> // parallel I2S
//Sm16825 (RGBWC)
#define B_32_RN_SM16825_5 NeoPixelBus<NeoRgbcwSm16825eFeature, NeoEsp32RmtNWs2812xMethod>
#define B_32_RN_SM16825_5 NeoPixelBus<NeoRgbcwSm16825eFeature, NeoEsp32RmtMethod(Ws2812x)>
#define B_32_I2_SM16825_5 NeoPixelBus<NeoRgbcwSm16825eFeature, X1Ws2812xMethod>
#define B_32_IP_SM16825_5 NeoPixelBus<NeoRgbcwSm16825eFeature, X8Ws2812xMethod> // parallel I2S
#endif

View File

@@ -201,7 +201,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
}
#endif
DEBUG_PRINTF_P(PSTR("Heap before buses: %d\n"), ESP.getFreeHeap());
DEBUG_PRINTF_P(PSTR("Heap before buses: %d\n"), getFreeHeapSize());
JsonArray ins = hw_led["ins"];
if (!ins.isNull()) {
int s = 0; // bus iterator

View File

@@ -546,8 +546,21 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit");
#endif
#endif
// minimum heap size required to process web requests
#define MIN_HEAP_SIZE 8192
// minimum heap size required to process web requests: try to keep free heap above this value
#ifdef ESP8266
#define MIN_HEAP_SIZE (9*1024)
#else
#define MIN_HEAP_SIZE (15*1024) // WLED allocation functions (util.cpp) try to keep this much contiguous heap free for other tasks
#endif
// threshold for PSRAM use: if heap is running low, requests to allocate_buffer(prefer DRAM) above PSRAM_THRESHOLD may be put in PSRAM
// if heap is depleted, PSRAM will be used regardless of threshold
#if defined(CONFIG_IDF_TARGET_ESP32S3)
#define PSRAM_THRESHOLD (12*1024) // S3 has plenty of DRAM
#elif defined(CONFIG_IDF_TARGET_ESP32)
#define PSRAM_THRESHOLD (5*1024)
#else
#define PSRAM_THRESHOLD (2*1024) // S2 does not have a lot of RAM. C3 and ESP8266 do not support PSRAM: the value is not used
#endif
// Web server limits
#ifdef ESP8266

View File

@@ -195,7 +195,6 @@
if (isAna(t)) return 5; // analog
let len = parseInt(d.getElementsByName("LC"+n)[0].value);
len += parseInt(d.getElementsByName("SL"+n)[0].value); // skipped LEDs are allocated too
let dbl = 0;
let ch = 3*hasRGB(t) + hasW(t) + hasCCT(t);
let mul = 1;
if (isDig(t)) {
@@ -207,7 +206,7 @@
mul = 2;
}
}
return len * ch * mul + dbl;
return len * ch * mul + len * 4; // add 4 bytes per LED for segment buffer (TODO: how to account for global buffer?)
}
function UI(change=false)

View File

@@ -17,6 +17,73 @@
function setBckFilename(x) {
x.setAttribute("download","wled_" + x.getAttribute("download") + (sd=="WLED"?"":("_" +sd)));
}
function userBackup(type) {
if (!confirm("Create internal backup for " + type + "? This will overwrite any existing backup.")) return;
var btn = gId("ubk" + type.charAt(0).toUpperCase() + type.slice(1));
btn.disabled = true;
btn.innerHTML = "Creating...";
var xhr = new XMLHttpRequest();
xhr.open('POST', getURL('/backup/' + type), true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
btn.disabled = false;
btn.innerHTML = "Create " + type.charAt(0).toUpperCase() + type.slice(1) + " Backup";
if (xhr.status === 200) {
showToast(xhr.responseText, false);
updateBackupButtons();
} else {
showToast("Backup failed: " + xhr.responseText, true);
}
}
};
xhr.send();
}
function userRestore(type) {
var message = "Restore " + type + " from internal backup? This will overwrite current " + type + ".";
if (type === 'config') message += " Device will reboot after restore.";
if (!confirm(message)) return;
var btn = gId("ures" + type.charAt(0).toUpperCase() + type.slice(1));
btn.disabled = true;
btn.innerHTML = "Restoring...";
var xhr = new XMLHttpRequest();
xhr.open('POST', getURL('/restore/' + type), true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
showToast(xhr.responseText, false);
if (type === 'config') {
setTimeout(function() { window.location.href = "/"; }, 3000);
} else {
btn.disabled = false;
btn.innerHTML = "Restore " + type.charAt(0).toUpperCase() + type.slice(1);
}
} else {
btn.disabled = false;
btn.innerHTML = "Restore " + type.charAt(0).toUpperCase() + type.slice(1);
showToast("Restore failed: " + xhr.responseText, true);
}
}
};
xhr.send();
}
function updateBackupButtons() {
var xhr = new XMLHttpRequest();
xhr.open('GET', getURL('/backup/status'), true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
try {
var status = JSON.parse(xhr.responseText);
gId("uresCfg").style.display = status.config ? "inline-block" : "none";
gId("uresPresets").style.display = status.presets ? "inline-block" : "none";
gId("uresPalettes").style.display = status.palettes ? "inline-block" : "none";
gId("uresMappings").style.display = status.mappings ? "inline-block" : "none";
} catch(e) {
console.error("Failed to parse backup status:", e);
}
}
};
xhr.send();
}
function S() {
getLoc();
if (loc) {
@@ -26,6 +93,7 @@
loadJS(getURL('/settings/s.js?p=6'), false, undefined, ()=>{
setBckFilename(gId("bckcfg"));
setBckFilename(gId("bckpresets"));
updateBackupButtons();
}); // If we set async false, file is loaded and executed, then next statement is processed
if (loc) d.Sf.action = getURL('/settings/sec');
}
@@ -70,6 +138,22 @@
<a class="btn lnk" id="bckpresets" href="/cfg.json" download="cfg">Backup configuration</a><br>
<div>Restore configuration<br><input type="file" name="data2" accept=".json"> <button type="button" onclick="uploadFile(d.Sf.data2,'/cfg.json');">Upload</button><br></div>
<hr>
<h3>Internal User Backup</h3>
<div class="warn">&#9888; Internal backups are stored on the device filesystem and will be lost if the device is reset or reflashed.<br>
User backups will OVERWRITE existing backups of the same type.</div>
<h4>Configuration</h4>
<button type="button" id="ubkCfg" onclick="userBackup('config')">Create Config Backup</button>
<button type="button" id="uresCfg" onclick="userRestore('config')" style="display:none;">Restore Config</button><br><br>
<h4>Presets</h4>
<button type="button" id="ubkPresets" onclick="userBackup('presets')">Create Presets Backup</button>
<button type="button" id="uresPresets" onclick="userRestore('presets')" style="display:none;">Restore Presets</button><br><br>
<h4>Custom Palettes</h4>
<button type="button" id="ubkPalettes" onclick="userBackup('palettes')">Create Palettes Backup</button>
<button type="button" id="uresPalettes" onclick="userRestore('palettes')" style="display:none;">Restore Palettes</button><br><br>
<h4>Custom Mappings</h4>
<button type="button" id="ubkMappings" onclick="userBackup('mappings')">Create Mappings Backup</button>
<button type="button" id="uresMappings" onclick="userRestore('mappings')" style="display:none;">Restore Mappings</button><br><br>
<hr>
<h3>About</h3>
<a href="https://github.com/wled-dev/WLED/" target="_blank">WLED</a>&#32;version ##VERSION##<!-- Autoreplaced from package.json --><br><br>
<a href="https://kno.wled.ge/about/contributors/" target="_blank">Contributors, dependencies and special thanks</a><br>

View File

@@ -103,6 +103,21 @@ inline bool readObjectFromFile(const String &file, const char* key, JsonDocument
bool copyFile(const char* src_path, const char* dst_path);
bool backupFile(const char* filename);
bool restoreFile(const char* filename);
bool userBackupFile(const char* filename);
bool userRestoreFile(const char* filename);
bool userBackupExists(const char* filename);
bool userBackupConfig();
bool userRestoreConfig();
bool userBackupConfigExists();
bool userBackupPresets();
bool userRestorePresets();
bool userBackupPresetsExists();
int userBackupPalettes();
int userRestorePalettes();
bool userBackupPalettesExist();
int userBackupMappings();
int userRestoreMappings();
bool userBackupMappingsExist();
bool validateJsonFile(const char* filename);
void dumpFilesToSerial();
@@ -434,35 +449,44 @@ inline uint8_t hw_random8() { return HW_RND_REGISTER; };
inline uint8_t hw_random8(uint32_t upperlimit) { return (hw_random8() * upperlimit) >> 8; }; // input range 0-255
inline uint8_t hw_random8(uint32_t lowerlimit, uint32_t upperlimit) { uint32_t range = upperlimit - lowerlimit; return lowerlimit + hw_random8(range); }; // input range 0-255
// PSRAM allocation wrappers
#if !defined(ESP8266) && !defined(CONFIG_IDF_TARGET_ESP32C3)
// memory allocation wrappers (util.cpp)
extern "C" {
void *p_malloc(size_t); // prefer PSRAM over DRAM
void *p_calloc(size_t, size_t); // prefer PSRAM over DRAM
void *p_realloc(void *, size_t); // prefer PSRAM over DRAM
void *p_realloc_malloc(void *ptr, size_t size); // realloc with malloc fallback, prefer PSRAM over DRAM
inline void p_free(void *ptr) { heap_caps_free(ptr); }
void *d_malloc(size_t); // prefer DRAM over PSRAM
void *d_calloc(size_t, size_t); // prefer DRAM over PSRAM
void *d_realloc(void *, size_t); // prefer DRAM over PSRAM
void *d_realloc_malloc(void *ptr, size_t size); // realloc with malloc fallback, prefer DRAM over PSRAM
// prefer DRAM in d_xalloc functions, PSRAM as fallback
void *d_malloc(size_t);
void *d_calloc(size_t, size_t);
void *d_realloc_malloc(void *ptr, size_t size);
#ifndef ESP8266
inline void d_free(void *ptr) { heap_caps_free(ptr); }
#else
inline void d_free(void *ptr) { free(ptr); }
#endif
#if defined(BOARD_HAS_PSRAM)
// prefer PSRAM in p_xalloc functions, DRAM as fallback
void *p_malloc(size_t);
void *p_calloc(size_t, size_t);
void *p_realloc_malloc(void *ptr, size_t size);
inline void p_free(void *ptr) { heap_caps_free(ptr); }
#else
#define p_malloc d_malloc
#define p_calloc d_calloc
#define p_realloc_malloc d_realloc_malloc
#define p_free d_free
#endif
}
#ifndef ESP8266
inline size_t getFreeHeapSize() { return heap_caps_get_free_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); } // returns free heap (ESP.getFreeHeap() can include other memory types)
inline size_t getContiguousFreeHeap() { return heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); } // returns largest contiguous free block
#else
extern "C" {
void *realloc_malloc(void *ptr, size_t size);
}
#define p_malloc malloc
#define p_calloc calloc
#define p_realloc realloc
#define p_realloc_malloc realloc_malloc
#define p_free free
#define d_malloc malloc
#define d_calloc calloc
#define d_realloc realloc
#define d_realloc_malloc realloc_malloc
#define d_free free
inline size_t getFreeHeapSize() { return ESP.getFreeHeap(); } // returns free heap
inline size_t getContiguousFreeHeap() { return ESP.getMaxFreeBlockSize(); } // returns largest contiguous free block
#endif
#define BFRALLOC_NOBYTEACCESS (1 << 0) // ESP32 has 32bit accessible DRAM (usually ~50kB free) that must not be byte-accessed
#define BFRALLOC_PREFER_DRAM (1 << 1) // prefer DRAM over PSRAM
#define BFRALLOC_ENFORCE_DRAM (1 << 2) // use DRAM only, no PSRAM
#define BFRALLOC_PREFER_PSRAM (1 << 3) // prefer PSRAM over DRAM
#define BFRALLOC_ENFORCE_PSRAM (1 << 4) // use PSRAM if available, otherwise uses DRAM
#define BFRALLOC_CLEAR (1 << 5) // clear allocated buffer after allocation
void *allocate_buffer(size_t size, uint32_t type);
void handleBootLoop(); // detect and handle bootloops
#ifndef ESP8266

View File

@@ -422,8 +422,8 @@ bool handleFileRead(AsyncWebServerRequest* request, String path){
DEBUGFS_PRINT(F("WS FileRead: ")); DEBUGFS_PRINTLN(path);
if(path.endsWith("/")) path += "index.htm";
if(path.indexOf(F("sec")) > -1) return false;
#ifdef ARDUINO_ARCH_ESP32
if (psramSafe && psramFound() && path.endsWith(FPSTR(getPresetsFileName()))) {
#ifdef BOARD_HAS_PSRAM
if (path.endsWith(FPSTR(getPresetsFileName()))) {
size_t psize;
const uint8_t *presets = getPresetCache(psize);
if (presets) {
@@ -516,6 +516,7 @@ bool compareFiles(const char* path1, const char* path2) {
}
static const char s_backup_fmt[] PROGMEM = "/bkp.%s";
static const char s_user_backup_fmt[] PROGMEM = "/bku.%s";
bool backupFile(const char* filename) {
DEBUG_PRINTF("backup %s \n", filename);
@@ -572,6 +573,150 @@ bool validateJsonFile(const char* filename) {
return result;
}
bool userBackupFile(const char* filename) {
DEBUG_PRINTF("user backup %s \n", filename);
if (!validateJsonFile(filename)) {
DEBUG_PRINTLN(F("broken file"));
return false;
}
char backupname[32];
snprintf_P(backupname, sizeof(backupname), s_user_backup_fmt, filename + 1); // skip leading '/' in filename
if (copyFile(filename, backupname)) {
DEBUG_PRINTLN(F("user backup ok"));
return true;
}
DEBUG_PRINTLN(F("user backup failed"));
return false;
}
bool userRestoreFile(const char* filename) {
DEBUG_PRINTF("user restore %s \n", filename);
char backupname[32];
snprintf_P(backupname, sizeof(backupname), s_user_backup_fmt, filename + 1); // skip leading '/' in filename
if (!WLED_FS.exists(backupname)) {
DEBUG_PRINTLN(F("no user backup found"));
return false;
}
if (!validateJsonFile(backupname)) {
DEBUG_PRINTLN(F("broken user backup"));
return false;
}
if (copyFile(backupname, filename)) {
DEBUG_PRINTLN(F("user restore ok"));
return true;
}
DEBUG_PRINTLN(F("user restore failed"));
return false;
}
bool userBackupExists(const char* filename) {
char backupname[32];
snprintf_P(backupname, sizeof(backupname), s_user_backup_fmt, filename + 1); // skip leading '/' in filename
return WLED_FS.exists(backupname);
}
// User backup functions for different file types
bool userBackupConfig() {
return userBackupFile("/cfg.json");
}
bool userRestoreConfig() {
return userRestoreFile("/cfg.json");
}
bool userBackupConfigExists() {
return userBackupExists("/cfg.json");
}
bool userBackupPresets() {
return userBackupFile("/presets.json");
}
bool userRestorePresets() {
return userRestoreFile("/presets.json");
}
bool userBackupPresetsExists() {
return userBackupExists("/presets.json");
}
int userBackupPalettes() {
int count = 0;
for (int i = 0; i < 10; i++) {
char filename[32];
sprintf_P(filename, PSTR("/palette%d.json"), i);
if (WLED_FS.exists(filename)) {
if (userBackupFile(filename)) count++;
}
}
return count;
}
int userRestorePalettes() {
int count = 0;
for (int i = 0; i < 10; i++) {
char filename[32];
sprintf_P(filename, PSTR("/palette%d.json"), i);
if (userRestoreFile(filename)) count++;
}
return count;
}
bool userBackupPalettesExist() {
for (int i = 0; i < 10; i++) {
char filename[32];
sprintf_P(filename, PSTR("/palette%d.json"), i);
if (userBackupExists(filename)) return true;
}
return false;
}
int userBackupMappings() {
int count = 0;
// Backup ledmap files
for (int i = 1; i < WLED_MAX_LEDMAPS; i++) {
char filename[32];
sprintf_P(filename, PSTR("/ledmap%d.json"), i);
if (WLED_FS.exists(filename)) {
if (userBackupFile(filename)) count++;
}
}
// Backup 2D gaps file if it exists
if (WLED_FS.exists("/2d-gaps.json")) {
if (userBackupFile("/2d-gaps.json")) count++;
}
return count;
}
int userRestoreMappings() {
int count = 0;
// Restore ledmap files
for (int i = 1; i < WLED_MAX_LEDMAPS; i++) {
char filename[32];
sprintf_P(filename, PSTR("/ledmap%d.json"), i);
if (userRestoreFile(filename)) count++;
}
// Restore 2D gaps file if backup exists
if (userRestoreFile("/2d-gaps.json")) count++;
return count;
}
bool userBackupMappingsExist() {
// Check ledmap files
for (int i = 1; i < WLED_MAX_LEDMAPS; i++) {
char filename[32];
sprintf_P(filename, PSTR("/ledmap%d.json"), i);
if (userBackupExists(filename)) return true;
}
// Check 2D gaps file
if (userBackupExists("/2d-gaps.json")) return true;
return false;
}
// print contents of all files in root dir to Serial except wsec files
void dumpFilesToSerial() {
File rootdir = WLED_FS.open("/", "r");

View File

@@ -812,7 +812,7 @@ void serializeInfo(JsonObject root)
root[F("clock")] = ESP.getCpuFreqMHz();
root[F("flash")] = (ESP.getFlashChipSize()/1024)/1024;
#ifdef WLED_DEBUG
root[F("maxalloc")] = ESP.getMaxAllocHeap();
root[F("maxalloc")] = getContiguousFreeHeap();
root[F("resetReason0")] = (int)rtc_get_reset_reason(0);
root[F("resetReason1")] = (int)rtc_get_reset_reason(1);
#endif
@@ -823,15 +823,15 @@ void serializeInfo(JsonObject root)
root[F("clock")] = ESP.getCpuFreqMHz();
root[F("flash")] = (ESP.getFlashChipSize()/1024)/1024;
#ifdef WLED_DEBUG
root[F("maxalloc")] = ESP.getMaxFreeBlockSize();
root[F("maxalloc")] = getContiguousFreeHeap();
root[F("resetReason")] = (int)ESP.getResetInfoPtr()->reason;
#endif
root[F("lwip")] = LWIP_VERSION_MAJOR;
#endif
root[F("freeheap")] = ESP.getFreeHeap();
#if defined(ARDUINO_ARCH_ESP32)
if (psramFound()) root[F("psram")] = ESP.getFreePsram();
root[F("freeheap")] = getFreeHeapSize();
#if defined(BOARD_HAS_PSRAM)
root[F("psram")] = ESP.getFreePsram();
#endif
root[F("uptime")] = millis()/1000 + rolloverMillis*4294967;

View File

@@ -629,92 +629,186 @@ int32_t hw_random(int32_t lowerlimit, int32_t upperlimit) {
return hw_random(diff) + lowerlimit;
}
#if !defined(ESP8266) && !defined(CONFIG_IDF_TARGET_ESP32C3) // ESP8266 does not support PSRAM, ESP32-C3 does not have PSRAM
// p_x prefer PSRAM, d_x prefer DRAM
void *p_malloc(size_t size) {
int caps1 = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT;
int caps2 = MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT;
if (psramSafe) {
if (heap_caps_get_free_size(caps2) > 3*MIN_HEAP_SIZE && size < 512) std::swap(caps1, caps2); // use DRAM for small alloactions & when heap is plenty
return heap_caps_malloc_prefer(size, 2, caps1, caps2); // otherwise prefer PSRAM if it exists
}
return heap_caps_malloc(size, caps2);
}
// PSRAM compile time checks to provide info for misconfigured env
#if defined(BOARD_HAS_PSRAM)
#if defined(IDF_TARGET_ESP32C3) || defined(ESP8266)
#error "ESP32-C3 and ESP8266 with PSRAM is not supported, please remove BOARD_HAS_PSRAM definition"
#else
// BOARD_HAS_PSRAM also means that compiler flag "-mfix-esp32-psram-cache-issue" has to be used
#warning "BOARD_HAS_PSRAM defined, make sure to use -mfix-esp32-psram-cache-issue to prevent issues on rev.1 ESP32 boards \
see https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/external-ram.html#esp32-rev-v1-0"
#endif
#else
#if !defined(IDF_TARGET_ESP32C3) && !defined(ESP8266)
#pragma message("BOARD_HAS_PSRAM not defined, not using PSRAM.")
#endif
#endif
void *p_realloc(void *ptr, size_t size) {
int caps1 = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT;
int caps2 = MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT;
if (psramSafe) {
if (heap_caps_get_free_size(caps2) > 3*MIN_HEAP_SIZE && size < 512) std::swap(caps1, caps2); // use DRAM for small alloactions & when heap is plenty
return heap_caps_realloc_prefer(ptr, size, 2, caps1, caps2); // otherwise prefer PSRAM if it exists
// memory allocation functions with minimum free heap size check
#ifdef ESP8266
static void *validateFreeHeap(void *buffer) {
// make sure there is enough free heap left if buffer was allocated in DRAM region, free it if not
if (getContiguousFreeHeap() < MIN_HEAP_SIZE) {
free(buffer);
return nullptr;
}
return heap_caps_realloc(ptr, size, caps2);
}
// realloc with malloc fallback, original buffer is freed if realloc fails but not copied!
void *p_realloc_malloc(void *ptr, size_t size) {
void *newbuf = p_realloc(ptr, size); // try realloc first
if (newbuf) return newbuf; // realloc successful
p_free(ptr); // free old buffer if realloc failed
return p_malloc(size); // fallback to malloc
}
void *p_calloc(size_t count, size_t size) {
int caps1 = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT;
int caps2 = MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT;
if (psramSafe) {
if (heap_caps_get_free_size(caps2) > 3*MIN_HEAP_SIZE && size < 512) std::swap(caps1, caps2); // use DRAM for small alloactions & when heap is plenty
return heap_caps_calloc_prefer(count, size, 2, caps1, caps2); // otherwise prefer PSRAM if it exists
}
return heap_caps_calloc(count, size, caps2);
return buffer;
}
void *d_malloc(size_t size) {
int caps1 = MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT;
int caps2 = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT;
if (psramSafe) {
if (heap_caps_get_largest_free_block(caps1) < 3*MIN_HEAP_SIZE && size > MIN_HEAP_SIZE) std::swap(caps1, caps2); // prefer PSRAM for large alloactions & when DRAM is low
return heap_caps_malloc_prefer(size, 2, caps1, caps2); // otherwise prefer DRAM
}
return heap_caps_malloc(size, caps1);
// note: using "if (getContiguousFreeHeap() > MIN_HEAP_SIZE + size)" did perform worse in tests with regards to keeping heap healthy and UI working
void *buffer = malloc(size);
return validateFreeHeap(buffer);
}
void *d_realloc(void *ptr, size_t size) {
int caps1 = MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT;
int caps2 = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT;
if (psramSafe) {
if (heap_caps_get_largest_free_block(caps1) < 3*MIN_HEAP_SIZE && size > MIN_HEAP_SIZE) std::swap(caps1, caps2); // prefer PSRAM for large alloactions & when DRAM is low
return heap_caps_realloc_prefer(ptr, size, 2, caps1, caps2); // otherwise prefer DRAM
void *d_calloc(size_t count, size_t size) {
void *buffer = calloc(count, size);
return validateFreeHeap(buffer);
}
// realloc with malloc fallback, note: on ESPS8266 there is no safe way to ensure MIN_HEAP_SIZE during realloc()s, free buffer and allocate new one
void *d_realloc_malloc(void *ptr, size_t size) {
//void *buffer = realloc(ptr, size);
//buffer = validateFreeHeap(buffer);
//if (buffer) return buffer; // realloc successful
//d_free(ptr); // free old buffer if realloc failed (or min heap was exceeded)
//return d_malloc(size); // fallback to malloc
free(ptr);
return d_malloc(size);
}
#else
static void *validateFreeHeap(void *buffer) {
// make sure there is enough free heap left if buffer was allocated in DRAM region, free it if not
// TODO: between allocate and free, heap can run low (async web access), only IDF V5 allows for a pre-allocation-check of all free blocks
if ((uintptr_t)buffer > SOC_DRAM_LOW && (uintptr_t)buffer < SOC_DRAM_HIGH && getContiguousFreeHeap() < MIN_HEAP_SIZE) {
free(buffer);
return nullptr;
}
return heap_caps_realloc(ptr, size, caps1);
return buffer;
}
void *d_malloc(size_t size) {
void *buffer;
#if defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3)
// the newer ESP32 variants have byte-accessible fast RTC memory that can be used as heap, access speed is on-par with DRAM
// the system does prefer normal DRAM until full, since free RTC memory is ~7.5k only, its below the minimum heap threshold and needs to be allocated explicitly
// use RTC RAM for small allocations to improve fragmentation or if DRAM is running low
if (size < 256 || getContiguousFreeHeap() < 2*MIN_HEAP_SIZE + size)
buffer = heap_caps_malloc_prefer(size, 2, MALLOC_CAP_RTCRAM, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
else
#endif
buffer = heap_caps_malloc(size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); // allocate in any available heap memory
buffer = validateFreeHeap(buffer); // make sure there is enough free heap left
#ifdef BOARD_HAS_PSRAM
if (!buffer)
return heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); // DRAM failed, use PSRAM if available
#endif
return buffer;
}
void *d_calloc(size_t count, size_t size) {
void *buffer = d_malloc(count * size);
if (buffer) memset(buffer, 0, count * size); // clear allocated buffer
return buffer;
}
// realloc with malloc fallback, original buffer is freed if realloc fails but not copied!
void *d_realloc_malloc(void *ptr, size_t size) {
void *newbuf = d_realloc(ptr, size); // try realloc first
if (newbuf) return newbuf; // realloc successful
d_free(ptr); // free old buffer if realloc failed
void *buffer = heap_caps_realloc(ptr, size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
buffer = validateFreeHeap(buffer);
if (buffer) return buffer; // realloc successful
d_free(ptr); // free old buffer if realloc failed (or min heap was exceeded)
return d_malloc(size); // fallback to malloc
}
void *d_calloc(size_t count, size_t size) {
int caps1 = MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT;
int caps2 = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT;
if (psramSafe) {
if (size > MIN_HEAP_SIZE) std::swap(caps1, caps2); // prefer PSRAM for large alloactions
return heap_caps_calloc_prefer(count, size, 2, caps1, caps2); // otherwise prefer DRAM
}
return heap_caps_calloc(count, size, caps1);
#ifdef BOARD_HAS_PSRAM
// p_xalloc: prefer PSRAM, use DRAM as fallback
void *p_malloc(size_t size) {
void *buffer = heap_caps_malloc_prefer(size, 2, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
return validateFreeHeap(buffer);
}
#else // ESP8266 & ESP32-C3
void *p_calloc(size_t count, size_t size) {
void *buffer = p_malloc(count * size);
if (buffer) memset(buffer, 0, count * size); // clear allocated buffer
return buffer;
}
// realloc with malloc fallback, original buffer is freed if realloc fails but not copied!
void *realloc_malloc(void *ptr, size_t size) {
void *newbuf = realloc(ptr, size); // try realloc first
if (newbuf) return newbuf; // realloc successful
free(ptr); // free old buffer if realloc failed
return malloc(size); // fallback to malloc
void *p_realloc_malloc(void *ptr, size_t size) {
void *buffer = heap_caps_realloc(ptr, size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
if (buffer) return buffer; // realloc successful
p_free(ptr); // free old buffer if realloc failed
return p_malloc(size); // fallback to malloc
}
#endif
#endif
// allocation function for buffers like pixel-buffers and segment data
// optimises the use of memory types to balance speed and heap availability, always favours DRAM if possible
// if multiple conflicting types are defined, the lowest bits of "type" take priority (see fcn_declare.h for types)
void *allocate_buffer(size_t size, uint32_t type) {
void *buffer = nullptr;
#ifdef CONFIG_IDF_TARGET_ESP32
// only classic ESP32 has "32bit accessible only" aka IRAM type. Using it frees up normal DRAM for other purposes
// this memory region is used for IRAM_ATTR functions, whatever is left is unused and can be used for pixel buffers
// prefer this type over PSRAM as it is slightly faster, except for _pixels where it is on-par as PSRAM-caching does a good job for mostly sequential access
if (type & BFRALLOC_NOBYTEACCESS) {
// prefer 32bit region, then PSRAM, fallback to any heap. Note: if adding "INTERNAL"-flag this wont work
buffer = heap_caps_malloc_prefer(size, 3, MALLOC_CAP_32BIT, MALLOC_CAP_SPIRAM, MALLOC_CAP_8BIT);
buffer = validateFreeHeap(buffer);
}
else
#endif
#if !defined(BOARD_HAS_PSRAM)
buffer = d_malloc(size);
#else
if (type & BFRALLOC_PREFER_DRAM) {
if (getContiguousFreeHeap() < 3*(MIN_HEAP_SIZE/2) + size && size > PSRAM_THRESHOLD)
buffer = p_malloc(size); // prefer PSRAM for large allocations & when DRAM is low
else
buffer = d_malloc(size); // allocate in DRAM if enough free heap is available, PSRAM as fallback
}
else if (type & BFRALLOC_ENFORCE_DRAM)
buffer = heap_caps_malloc(size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); // use DRAM only, otherwise return nullptr
else if (type & BFRALLOC_PREFER_PSRAM) {
// if DRAM is plenty, prefer it over PSRAM for speed, reserve enough DRAM for segment data: if MAX_SEGMENT_DATA is exceeded, always uses PSRAM
if (getContiguousFreeHeap() > 4*MIN_HEAP_SIZE + size + ((uint32_t)(MAX_SEGMENT_DATA - Segment::getUsedSegmentData())))
buffer = d_malloc(size);
else
buffer = p_malloc(size); // prefer PSRAM
}
else if (type & BFRALLOC_ENFORCE_PSRAM)
buffer = heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); // use PSRAM only, otherwise return nullptr
buffer = validateFreeHeap(buffer);
#endif
if (buffer && (type & BFRALLOC_CLEAR))
memset(buffer, 0, size); // clear allocated buffer
/*
#if !defined(ESP8266) && defined(WLED_DEBUG)
if (buffer) {
DEBUG_PRINTF_P(PSTR("*Buffer allocated: size:%d, address:%p"), size, (uintptr_t)buffer);
if ((uintptr_t)buffer > SOC_DRAM_LOW && (uintptr_t)buffer < SOC_DRAM_HIGH)
DEBUG_PRINTLN(F(" in DRAM"));
#ifndef CONFIG_IDF_TARGET_ESP32C3
else if ((uintptr_t)buffer > SOC_EXTRAM_DATA_LOW && (uintptr_t)buffer < SOC_EXTRAM_DATA_HIGH)
DEBUG_PRINTLN(F(" in PSRAM"));
#endif
#ifdef CONFIG_IDF_TARGET_ESP32
else if ((uintptr_t)buffer > SOC_IRAM_LOW && (uintptr_t)buffer < SOC_IRAM_HIGH)
DEBUG_PRINTLN(F(" in IRAM")); // only used on ESP32 (MALLOC_CAP_32BIT)
#else
else if ((uintptr_t)buffer > SOC_RTC_DRAM_LOW && (uintptr_t)buffer < SOC_RTC_DRAM_HIGH)
DEBUG_PRINTLN(F(" in RTCRAM")); // not available on ESP32
#endif
else
DEBUG_PRINTLN(F(" in ???")); // unknown (check soc.h for other memory regions)
} else
DEBUG_PRINTF_P(PSTR("Buffer allocation failed: size:%d\n"), size);
#endif
*/
return buffer;
}
// bootloop detection and handling
// checks if the ESP reboots multiple times due to a crash or watchdog timeout

View File

@@ -171,7 +171,7 @@ void WLED::loop()
// reconnect WiFi to clear stale allocations if heap gets too low
if (millis() - heapTime > 15000) {
uint32_t heap = ESP.getFreeHeap();
uint32_t heap = getFreeHeapSize();
if (heap < MIN_HEAP_SIZE && lastHeap < MIN_HEAP_SIZE) {
DEBUG_PRINTF_P(PSTR("Heap too low! %u\n"), heap);
forceReconnect = true;
@@ -241,13 +241,37 @@ void WLED::loop()
DEBUG_PRINTLN(F("---DEBUG INFO---"));
DEBUG_PRINTF_P(PSTR("Runtime: %lu\n"), millis());
DEBUG_PRINTF_P(PSTR("Unix time: %u,%03u\n"), toki.getTime().sec, toki.getTime().ms);
DEBUG_PRINTF_P(PSTR("Free heap: %u\n"), ESP.getFreeHeap());
#if defined(ARDUINO_ARCH_ESP32)
DEBUG_PRINTLN(F("=== Memory Info ==="));
// Internal DRAM (standard 8-bit accessible heap)
size_t dram_free = heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
size_t dram_largest = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
DEBUG_PRINTF_P(PSTR("DRAM 8-bit: Free: %7u bytes | Largest block: %7u bytes\n"), dram_free, dram_largest);
#ifdef BOARD_HAS_PSRAM
size_t psram_free = heap_caps_get_free_size(MALLOC_CAP_SPIRAM);
size_t psram_largest = heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM);
DEBUG_PRINTF_P(PSTR("PSRAM: Free: %7u bytes | Largest block: %6u bytes\n"), psram_free, psram_largest);
#endif
#if defined(CONFIG_IDF_TARGET_ESP32)
// 32-bit DRAM (not byte accessible, only available on ESP32)
size_t dram32_free = heap_caps_get_free_size(MALLOC_CAP_32BIT | MALLOC_CAP_INTERNAL) - dram_free; // returns all 32bit DRAM, subtract 8bit DRAM
//size_t dram32_largest = heap_caps_get_largest_free_block(MALLOC_CAP_32BIT | MALLOC_CAP_INTERNAL); // returns largest DRAM block -> not useful
DEBUG_PRINTF_P(PSTR("DRAM 32-bit: Free: %7u bytes | Largest block: N/A\n"), dram32_free);
#else
// Fast RTC Memory (not available on ESP32)
size_t rtcram_free = heap_caps_get_free_size(MALLOC_CAP_RTCRAM);
size_t rtcram_largest = heap_caps_get_largest_free_block(MALLOC_CAP_RTCRAM);
DEBUG_PRINTF_P(PSTR("RTC RAM: Free: %7u bytes | Largest block: %7u bytes\n"), rtcram_free, rtcram_largest);
#endif
if (psramFound()) {
DEBUG_PRINTF_P(PSTR("PSRAM: %dkB/%dkB\n"), ESP.getFreePsram()/1024, ESP.getPsramSize()/1024);
if (!psramSafe) DEBUG_PRINTLN(F("Not using PSRAM."));
#ifndef BOARD_HAS_PSRAM
DEBUG_PRINTLN(F("BOARD_HAS_PSRAM not defined, not using PSRAM."));
#endif
}
DEBUG_PRINTF_P(PSTR("TX power: %d/%d\n"), WiFi.getTxPower(), txPower);
#else // ESP8266
DEBUG_PRINTF_P(PSTR("Free heap/contiguous: %u/%u\n"), getFreeHeapSize(), getContiguousFreeHeap());
#endif
DEBUG_PRINTF_P(PSTR("Wifi state: %d\n"), WiFi.status());
#ifndef WLED_DISABLE_ESPNOW
@@ -367,20 +391,16 @@ void WLED::setup()
DEBUG_PRINTF_P(PSTR("esp8266 @ %u MHz.\nCore: %s\n"), ESP.getCpuFreqMHz(), ESP.getCoreVersion());
DEBUG_PRINTF_P(PSTR("FLASH: %u MB\n"), (ESP.getFlashChipSize()/1024)/1024);
#endif
DEBUG_PRINTF_P(PSTR("heap %u\n"), ESP.getFreeHeap());
DEBUG_PRINTF_P(PSTR("heap %u\n"), getFreeHeapSize());
#if defined(BOARD_HAS_PSRAM)
// if JSON buffer allocation fails requestJsonBufferLock() will always return false preventing crashes
pDoc = new PSRAMDynamicJsonDocument(2 * JSON_BUFFER_SIZE);
DEBUG_PRINTF_P(PSTR("JSON buffer size: %ubytes\n"), (2 * JSON_BUFFER_SIZE));
DEBUG_PRINTF_P(PSTR("PSRAM: %dkB/%dkB\n"), ESP.getFreePsram()/1024, ESP.getPsramSize()/1024);
#endif
#if defined(ARDUINO_ARCH_ESP32)
// BOARD_HAS_PSRAM also means that a compiler flag "-mfix-esp32-psram-cache-issue" was used and so PSRAM is safe to use on rev.1 ESP32
#if !defined(BOARD_HAS_PSRAM) && !(defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3))
if (psramFound() && ESP.getChipRevision() < 3) psramSafe = false;
if (!psramSafe) DEBUG_PRINTLN(F("Not using PSRAM."));
#endif
pDoc = new PSRAMDynamicJsonDocument((psramSafe && psramFound() ? 2 : 1)*JSON_BUFFER_SIZE);
DEBUG_PRINTF_P(PSTR("JSON buffer allocated: %u\n"), (psramSafe && psramFound() ? 2 : 1)*JSON_BUFFER_SIZE);
// if the above fails requestJsonBufferLock() will always return false preventing crashes
if (psramFound()) {
DEBUG_PRINTF_P(PSTR("PSRAM: %dkB/%dkB\n"), ESP.getFreePsram()/1024, ESP.getPsramSize()/1024);
}
DEBUG_PRINTF_P(PSTR("TX power: %d/%d\n"), WiFi.getTxPower(), txPower);
#endif
@@ -395,7 +415,7 @@ void WLED::setup()
PinManager::allocatePin(2, true, PinOwner::DMX);
#endif
DEBUG_PRINTF_P(PSTR("heap %u\n"), ESP.getFreeHeap());
DEBUG_PRINTF_P(PSTR("heap %u\n"), getFreeHeapSize());
bool fsinit = false;
DEBUGFS_PRINTLN(F("Mount FS"));
@@ -433,7 +453,7 @@ void WLED::setup()
}
DEBUG_PRINTLN(F("Reading config"));
bool needsCfgSave = deserializeConfigFromFS();
DEBUG_PRINTF_P(PSTR("heap %u\n"), ESP.getFreeHeap());
DEBUG_PRINTF_P(PSTR("heap %u\n"), getFreeHeapSize());
#if defined(STATUSLED) && STATUSLED>=0
if (!PinManager::isPinAllocated(STATUSLED)) {
@@ -445,12 +465,12 @@ void WLED::setup()
DEBUG_PRINTLN(F("Initializing strip"));
beginStrip();
DEBUG_PRINTF_P(PSTR("heap %u\n"), ESP.getFreeHeap());
DEBUG_PRINTF_P(PSTR("heap %u\n"), getFreeHeapSize());
DEBUG_PRINTLN(F("Usermods setup"));
userSetup();
UsermodManager::setup();
DEBUG_PRINTF_P(PSTR("heap %u\n"), ESP.getFreeHeap());
DEBUG_PRINTF_P(PSTR("heap %u\n"), getFreeHeapSize());
if (needsCfgSave) serializeConfigToFS(); // usermods required new parameters; need to wait for strip to be initialised #4752
@@ -515,13 +535,13 @@ void WLED::setup()
// HTTP server page init
DEBUG_PRINTLN(F("initServer"));
initServer();
DEBUG_PRINTF_P(PSTR("heap %u\n"), ESP.getFreeHeap());
DEBUG_PRINTF_P(PSTR("heap %u\n"), getFreeHeapSize());
#ifndef WLED_DISABLE_INFRARED
// init IR
DEBUG_PRINTLN(F("initIR"));
initIR();
DEBUG_PRINTF_P(PSTR("heap %u\n"), ESP.getFreeHeap());
DEBUG_PRINTF_P(PSTR("heap %u\n"), getFreeHeapSize());
#endif
// Seed FastLED random functions with an esp random value, which already works properly at this point.

View File

@@ -167,16 +167,13 @@
// The following is a construct to enable code to compile without it.
// There is a code that will still not use PSRAM though:
// AsyncJsonResponse is a derived class that implements DynamicJsonDocument (AsyncJson-v6.h)
#if defined(ARDUINO_ARCH_ESP32)
extern bool psramSafe;
#if defined(BOARD_HAS_PSRAM)
struct PSRAM_Allocator {
void* allocate(size_t size) {
if (psramSafe && psramFound()) return ps_malloc(size); // use PSRAM if it exists
else return malloc(size); // fallback
return ps_malloc(size); // use PSRAM
}
void* reallocate(void* ptr, size_t new_size) {
if (psramSafe && psramFound()) return ps_realloc(ptr, new_size); // use PSRAM if it exists
else return realloc(ptr, new_size); // fallback
return ps_realloc(ptr, new_size); // use PSRAM
}
void deallocate(void* pointer) {
free(pointer);
@@ -894,8 +891,6 @@ WLED_GLOBAL byte optionType;
WLED_GLOBAL bool configNeedsWrite _INIT(false); // flag to initiate saving of config
WLED_GLOBAL bool doReboot _INIT(false); // flag to initiate reboot from async handlers
WLED_GLOBAL bool psramSafe _INIT(true); // is it safe to use PSRAM (on ESP32 rev.1; compiler fix used "-mfix-esp32-psram-cache-issue")
// status led
#if defined(STATUSLED)
WLED_GLOBAL unsigned long ledStatusLastMillis _INIT(0);
@@ -969,8 +964,11 @@ WLED_GLOBAL int8_t spi_sclk _INIT(SPISCLKPIN);
// global ArduinoJson buffer
#if defined(ARDUINO_ARCH_ESP32)
WLED_GLOBAL JsonDocument *pDoc _INIT(nullptr);
WLED_GLOBAL SemaphoreHandle_t jsonBufferLockMutex _INIT(xSemaphoreCreateRecursiveMutex());
#endif
#ifdef BOARD_HAS_PSRAM
// if board has PSRAM, use it for JSON document (allocated in setup())
WLED_GLOBAL JsonDocument *pDoc _INIT(nullptr);
#else
WLED_GLOBAL StaticJsonDocument<JSON_BUFFER_SIZE> gDoc;
WLED_GLOBAL JsonDocument *pDoc _INIT(&gDoc);

View File

@@ -349,8 +349,13 @@ void initServer()
if (verboseResponse) {
if (!isConfig) {
lastInterfaceUpdate = millis(); // prevent WS update until cooldown
interfaceUpdateCallMode = CALL_MODE_WS_SEND; // schedule WS update
serveJson(request); return; //if JSON contains "v"
interfaceUpdateCallMode = CALL_MODE_WS_SEND; // override call mode & schedule WS update
#ifndef WLED_DISABLE_MQTT
// publish state to MQTT as requested in wled#4643 even if only WS response selected
publishMqtt();
#endif
serveJson(request);
return; //if JSON contains "v"
} else {
configNeedsWrite = true; //Save new settings to FS
}
@@ -368,7 +373,111 @@ void initServer()
});
server.on(F("/freeheap"), HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, FPSTR(CONTENT_TYPE_PLAIN), (String)ESP.getFreeHeap());
request->send(200, FPSTR(CONTENT_TYPE_PLAIN), (String)getFreeHeapSize());
});
// User backup endpoints
server.on(F("/backup/config"), HTTP_POST, [](AsyncWebServerRequest *request){
if (!correctPIN) {
serveMessage(request, 401, FPSTR(s_accessdenied), FPSTR(s_unlock_cfg), 254);
return;
}
bool success = userBackupConfig();
request->send(200, FPSTR(CONTENT_TYPE_PLAIN), success ? F("Config backup created") : F("Config backup failed"));
});
server.on(F("/restore/config"), HTTP_POST, [](AsyncWebServerRequest *request){
if (!correctPIN) {
serveMessage(request, 401, FPSTR(s_accessdenied), FPSTR(s_unlock_cfg), 254);
return;
}
bool success = userRestoreConfig();
if (success) {
serveMessage(request, 200, F("Configuration restored."), F("Rebooting..."), 131);
doReboot = true;
} else {
request->send(400, FPSTR(CONTENT_TYPE_PLAIN), F("Config restore failed or no backup found"));
}
});
server.on(F("/backup/presets"), HTTP_POST, [](AsyncWebServerRequest *request){
if (!correctPIN) {
serveMessage(request, 401, FPSTR(s_accessdenied), FPSTR(s_unlock_cfg), 254);
return;
}
bool success = userBackupPresets();
request->send(200, FPSTR(CONTENT_TYPE_PLAIN), success ? F("Presets backup created") : F("Presets backup failed"));
});
server.on(F("/restore/presets"), HTTP_POST, [](AsyncWebServerRequest *request){
if (!correctPIN) {
serveMessage(request, 401, FPSTR(s_accessdenied), FPSTR(s_unlock_cfg), 254);
return;
}
bool success = userRestorePresets();
request->send(200, FPSTR(CONTENT_TYPE_PLAIN), success ? F("Presets restored") : F("Presets restore failed or no backup found"));
});
server.on(F("/backup/palettes"), HTTP_POST, [](AsyncWebServerRequest *request){
if (!correctPIN) {
serveMessage(request, 401, FPSTR(s_accessdenied), FPSTR(s_unlock_cfg), 254);
return;
}
int count = userBackupPalettes();
String response = F("Palettes backup created: ");
response += count;
response += F(" files");
request->send(200, FPSTR(CONTENT_TYPE_PLAIN), response);
});
server.on(F("/restore/palettes"), HTTP_POST, [](AsyncWebServerRequest *request){
if (!correctPIN) {
serveMessage(request, 401, FPSTR(s_accessdenied), FPSTR(s_unlock_cfg), 254);
return;
}
int count = userRestorePalettes();
String response = F("Palettes restored: ");
response += count;
response += F(" files");
request->send(200, FPSTR(CONTENT_TYPE_PLAIN), response);
});
server.on(F("/backup/mappings"), HTTP_POST, [](AsyncWebServerRequest *request){
if (!correctPIN) {
serveMessage(request, 401, FPSTR(s_accessdenied), FPSTR(s_unlock_cfg), 254);
return;
}
int count = userBackupMappings();
String response = F("Mappings backup created: ");
response += count;
response += F(" files");
request->send(200, FPSTR(CONTENT_TYPE_PLAIN), response);
});
server.on(F("/restore/mappings"), HTTP_POST, [](AsyncWebServerRequest *request){
if (!correctPIN) {
serveMessage(request, 401, FPSTR(s_accessdenied), FPSTR(s_unlock_cfg), 254);
return;
}
int count = userRestoreMappings();
String response = F("Mappings restored: ");
response += count;
response += F(" files");
request->send(200, FPSTR(CONTENT_TYPE_PLAIN), response);
});
// Check backup status endpoint
server.on(F("/backup/status"), HTTP_GET, [](AsyncWebServerRequest *request){
String response = F("{\"config\":");
response += userBackupConfigExists() ? F("true") : F("false");
response += F(",\"presets\":");
response += userBackupPresetsExists() ? F("true") : F("false");
response += F(",\"palettes\":");
response += userBackupPalettesExist() ? F("true") : F("false");
response += F(",\"mappings\":");
response += userBackupMappingsExist() ? F("true") : F("false");
response += F("}");
request->send(200, F("application/json"), response);
});
#ifdef WLED_ENABLE_USERMOD_PAGE

View File

@@ -59,6 +59,10 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp
if (!interfaceUpdateCallMode) { // individual client response only needed if no WS broadcast soon
if (verboseResponse) {
#ifndef WLED_DISABLE_MQTT
// publish state to MQTT as requested in wled#4643 even if only WS response selected
publishMqtt();
#endif
sendDataWs(client);
} else {
// we have to send something back otherwise WS connection closes
@@ -124,8 +128,8 @@ void sendDataWs(AsyncWebSocketClient * client)
DEBUG_PRINTF_P(PSTR("JSON buffer size: %u for WS request (%u).\n"), pDoc->memoryUsage(), len);
// the following may no longer be necessary as heap management has been fixed by @willmmiles in AWS
size_t heap1 = ESP.getFreeHeap();
DEBUG_PRINTF_P(PSTR("heap %u\n"), ESP.getFreeHeap());
size_t heap1 = getFreeHeapSize();
DEBUG_PRINTF_P(PSTR("heap %u\n"), getFreeHeapSize());
#ifdef ESP8266
if (len>heap1) {
DEBUG_PRINTLN(F("Out of memory (WS)!"));
@@ -134,8 +138,8 @@ void sendDataWs(AsyncWebSocketClient * client)
#endif
AsyncWebSocketBuffer buffer(len);
#ifdef ESP8266
size_t heap2 = ESP.getFreeHeap();
DEBUG_PRINTF_P(PSTR("heap %u\n"), ESP.getFreeHeap());
size_t heap2 = getFreeHeapSize();
DEBUG_PRINTF_P(PSTR("heap %u\n"), getFreeHeapSize());
#else
size_t heap2 = 0; // ESP32 variants do not have the same issue and will work without checking heap allocation
#endif