Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
15b6907
Improvements to heap-memory and PSRAM handling (#4791)
DedeHai Sep 16, 2025
c6a4e28
WLED-MM adaptations
softhack007 Feb 15, 2026
d273835
fix an ancient compiler warning in DateStrings.cpp
softhack007 Feb 15, 2026
a861a31
use new p_malloc functions with ArduinoJSON
softhack007 Feb 15, 2026
b78d5e6
use new memory allocator utilities instead of malloc() callloc() and …
softhack007 Feb 15, 2026
e714ede
p -> d in AsyncJSON
softhack007 Feb 15, 2026
98c9788
fallback if !psramFound()
softhack007 Feb 15, 2026
31e56ae
two bugfixes
softhack007 Feb 15, 2026
4c93146
correct alloc size in d_calloc
softhack007 Feb 15, 2026
9408564
Merge branch 'mdev' into dedehai_malloc
softhack007 Feb 15, 2026
735047f
bugfix: allow user override for MIN_HEAP_SIZE
softhack007 Feb 15, 2026
78a964c
add RTCRAM statistics to WLED_DEBUG output
softhack007 Feb 15, 2026
62e4cab
Merge branch 'mdev' into dedehai_malloc
softhack007 Feb 16, 2026
1eaad1b
work in progress
softhack007 Feb 16, 2026
60f8648
(experimental) glitch-free heap size measurement
softhack007 Feb 16, 2026
67c70a2
add missing DRAM debug code for classic esp32
softhack007 Feb 16, 2026
68dbd5e
re-enable more relaxed heap checks in HUB75 builds
softhack007 Feb 16, 2026
69946e4
add early heap capacity checking before allocating
softhack007 Feb 16, 2026
381ad56
revert unintended semantic change for "minfreeheap"
softhack007 Feb 16, 2026
2d71ab3
USER_PRINT memory alloc failure messages
softhack007 Feb 16, 2026
b62b40b
AR: better handling of alloc failure
softhack007 Feb 16, 2026
ffe718b
ARTIFX and rotary UM robustness improvements
softhack007 Feb 16, 2026
66847a3
printf format string correction
softhack007 Feb 16, 2026
ccc96af
bugfix for C3 and S2
softhack007 Feb 16, 2026
26a4abe
AR: centralize memory cleanup
softhack007 Feb 16, 2026
6f99aa0
typo
softhack007 Feb 16, 2026
75cbe6c
UI "free heap" was sometimes behind -> back to ESP.getFreeHeap()
softhack007 Feb 16, 2026
3b29475
AR: prevent crash on OOM, better status info for users
softhack007 Feb 17, 2026
8a2ecf3
added critical section for updating global memory status variables
softhack007 Feb 17, 2026
cbcdf6d
validateFreeHeap improvement
softhack007 Feb 17, 2026
3c1d8b4
AR: volatile isOOM
softhack007 Feb 17, 2026
ba715a4
avoid double-free on vImag
softhack007 Feb 17, 2026
db8f928
Validate buffer after realloc in p_realloc_malloc
softhack007 Feb 17, 2026
bb7f0ec
minor changes
softhack007 Feb 17, 2026
2508bff
Update usermod_v2_rotary_encoder_ui_ALT.h
softhack007 Feb 17, 2026
5b1b689
use d_malloc / p_malloc in busHUB75 and busNetwork
softhack007 Feb 17, 2026
3babb52
typo
softhack007 Feb 17, 2026
81de35f
bugfix
softhack007 Feb 17, 2026
5f03cf9
optimization
softhack007 Feb 17, 2026
336bce5
clarify documentation
softhack007 Feb 17, 2026
eacb663
fix wrong error status
softhack007 Feb 17, 2026
9a30d9e
(chores) create util.h, move util.cpp related parts of fcn_declare.h …
softhack007 Feb 17, 2026
f9347ff
add realloc_malloc_nofree(), use with ArduinoJSON
softhack007 Feb 17, 2026
c352f2a
typo
softhack007 Feb 17, 2026
ab7b2c8
bugfix: don't double-free an invalid pointer in realloc_malloc()
softhack007 Feb 17, 2026
2137434
minor updates
softhack007 Feb 18, 2026
a65cb40
d_realloc_malloc_nofree prefer PSRAM when heap is low
softhack007 Feb 18, 2026
2fd71e8
protect heap_caps_get_allocated_size(), don't exit on nullptr
softhack007 Feb 18, 2026
1dce8d7
typo
softhack007 Feb 18, 2026
6251120
improvements for esp32 with PSRAM
softhack007 Feb 18, 2026
8917e6d
typo
softhack007 Feb 18, 2026
6aa054b
remove misleading comments
softhack007 Feb 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions usermods/artifx/arti.h
Original file line number Diff line number Diff line change
Expand Up @@ -2562,14 +2562,24 @@ class ARTI {

//open programFile
char * programText = nullptr;
uint16_t programFileSize;
size_t programFileSize;
#if ARTI_PLATFORM == ARTI_ARDUINO
programFileSize = programFile.size();
programText = (char *)malloc(programFileSize+1);
programText = (char *)d_malloc(programFileSize+1);
if (programText == nullptr) {
ERROR_ARTI("ARTI-FX: Failed to allocate memory for program file (%u bytes)\n", programFileSize+1);
programFile.close();
return false;
}
programFile.read((byte *)programText, programFileSize);
programText[programFileSize] = '\0';
#else
programText = (char *)malloc(programTextSize);
programText = (char *)malloc(programTextSize+1);
if (programText == nullptr) {
ERROR_ARTI("ARTI-FX: Failed to allocate memory for program file (%u bytes)\n", programTextSize);
programFile.close();
return false;
}
programFile.read(programText, programTextSize);
DEBUG_ARTI("programFile size %lu bytes\n", programFile.gcount());
programText[programFile.gcount()] = '\0';
Expand Down Expand Up @@ -2607,7 +2617,13 @@ class ARTI {
#endif

if (stages < 1) {
if (nullptr != programText) free(programText); // softhack007 prevent memory leak
// softhack007 prevent memory leak
#if ARTI_PLATFORM == ARTI_ARDUINO
if (nullptr != programText) d_free(programText);
#else
if (nullptr != programText) free(programText);
#endif
programText = nullptr;
close();
return true;
}
Comment on lines 2619 to 2629
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

stages < 1 guard is dead code — the intended stages==1 leak remains.

The first stages < 1 check at line 2484 already returns before programText is ever allocated. By the time execution reaches line 2619, stages >= 1 is guaranteed, so programText is never freed here. The if (nullptr != programText) guard prevents a crash but the condition is always false.

The genuine concern is stages == 1: programText is allocated, the Lexer is created (line 2635), and the stages < 2 branch at line 2638 calls close() and returns — leaking both programText and lexer (which close() does not delete). Fixing that path requires deleting lexer inside close() before freeing programText, which is a separate refactor. The current guard is a no-op.

💡 Clarification — the dead guard vs. the real leak
stages value programText allocated? Freed by this block?
0 No (line 2484 returns first) Never reached
1 Yes No (stages < 1 is false) — leaked at stages < 2 path
5 (production) Yes Freed correctly at lines 2685–2688
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@usermods/artifx/arti.h` around lines 2619 - 2629, The guard checking "if
(stages < 1)" is dead and doesn't free programText for the stages==1 path;
update the logic so the stages==1 early-exit cleans up both programText and
lexer: either change the condition to "if (stages < 2)" and keep the existing
free/delete of programText, or explicitly delete the Lexer instance (lexer)
before calling close() and then free programText; also ensure close() either
deletes lexer or you delete lexer prior to calling close() so no leak remains
(references: programText, lexer, close(), stages).

Expand Down Expand Up @@ -2666,8 +2682,11 @@ class ARTI {
#endif
}
#if ARTI_PLATFORM == ARTI_ARDUINO //not on windows as cause crash???
d_free(programText);
#else
free(programText);
#endif
programText = nullptr;

if (stages >= 3)
{
Expand Down
77 changes: 62 additions & 15 deletions usermods/audioreactive/audio_reactive.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,10 @@ static volatile bool disableSoundProcessing = false; // if true, sound proc
static uint8_t audioSyncEnabled = AUDIOSYNC_NONE; // bit field: bit 0 - send, bit 1 - receive, bit 2 - use local if not receiving
static bool audioSyncSequence = true; // if true, the receiver will drop out-of-sequence packets
static uint8_t audioSyncPurge = 1; // 0: process each packet (don't purge); 1: auto-purge old packets; 2: only process latest received packet (always purge)
static bool udpSyncConnected = false; // UDP connection status -> true if connected to multicast group
static bool udpSyncConnected = false; // UDP connection status -> true if connected to multicast group
static volatile bool isOOM = false; // FFTask: not enough memory for buffers (audio processing failed to start)

#define NUM_GEQ_CHANNELS 16 // number of frequency channels. Don't change !!
#define NUM_GEQ_CHANNELS 16 // number of frequency channels. Don't change !!

// audioreactive variables
#ifdef ARDUINO_ARCH_ESP32
Expand Down Expand Up @@ -454,13 +455,13 @@ static bool alocateFFTBuffers(void) {
USER_PRINT(F("\nFree heap ")); USER_PRINTLN(ESP.getFreeHeap());
#endif

if (vReal) free(vReal); // should not happen
if (vImag) free(vImag); // should not happen
if ((vReal = (float*) calloc(samplesFFT, sizeof(float))) == nullptr) return false; // calloc or die
if ((vImag = (float*) calloc(samplesFFT, sizeof(float))) == nullptr) return false;
if (vReal) d_free(vReal); vReal = nullptr; // should not happen
if (vImag) d_free(vImag); vImag = nullptr; // should not happen
if ((vReal = (float*) d_calloc(samplesFFT, sizeof(float))) == nullptr) return false; // calloc or die
if ((vImag = (float*) d_calloc(samplesFFT, sizeof(float))) == nullptr) return false;
#ifdef FFT_MAJORPEAK_HUMAN_EAR
if (pinkFactors) free(pinkFactors);
if ((pinkFactors = (float*) calloc(samplesFFT, sizeof(float))) == nullptr) return false;
if (pinkFactors) p_free(pinkFactors);
if ((pinkFactors = (float*) p_calloc(samplesFFT, sizeof(float))) == nullptr) return false;
#endif

#ifdef SR_DEBUG
Expand All @@ -472,6 +473,27 @@ static bool alocateFFTBuffers(void) {
return(true); // success
}

// de-allocate FFT sample buffers from heap
static void destroyFFTBuffers(bool panicOOM) {
#ifdef FFT_MAJORPEAK_HUMAN_EAR
if (pinkFactors) p_free(pinkFactors); pinkFactors = nullptr;
#endif
if (vImag) d_free(vImag); vImag = nullptr;
if (vReal) d_free(vReal); vReal = nullptr;

if (panicOOM && !isOOM) { // notify user
isOOM = true;
errorFlag = ERR_LOW_MEM;
USER_PRINTLN("AR startup failed - out of memory!");
}
#ifdef SR_DEBUG
USER_PRINTLN("\ndestroyFFTBuffers() completed successfully.");
USER_PRINT(F("Free heap: ")); USER_PRINTLN(ESP.getFreeHeap());
USER_FLUSH();
#endif
}


// High-Pass "DC blocker" filter
// see https://www.dsprelated.com/freebooks/filters/DC_Blocker.html
static void runDCBlocker(uint_fast16_t numSamples, float *sampleBuffer) {
Expand All @@ -488,6 +510,22 @@ static void runDCBlocker(uint_fast16_t numSamples, float *sampleBuffer) {
}
}

//
// FFT runner - "return" from a task function causes crash. This wrapper keeps the task alive
//
void runFFTcode(void * parameter) __attribute__((noreturn,used));
void runFFTcode(void * parameter) {
bool firstFail = true; // prevents flood of warnings
do {
if (!disableSoundProcessing) {
FFTcode(parameter);
if (firstFail) {USER_PRINTLN(F("warning: unexpected exit of FFT main task."));}
firstFail = false;
} else firstFail = true; // re-enable warning message
vTaskDelay(1000); // if we arrive here, FFcode has returned due to OOM. Wait a bit, then try again.
} while (true);
}

//
// FFT main task
//
Expand All @@ -511,13 +549,18 @@ void FFTcode(void * parameter)
static float* oldSamples = nullptr; // previous 50% of samples
static bool haveOldSamples = false; // for sliding window FFT
bool usingOldSamples = false;
if (!oldSamples) oldSamples = (float*) calloc(samplesFFT_2, sizeof(float)); // allocate on first run
if (!oldSamples) { disableSoundProcessing = true; return; } // no memory -> die
if (!oldSamples) oldSamples = (float*) d_calloc(samplesFFT_2, sizeof(float)); // allocate on first run
if (!oldSamples) { disableSoundProcessing = true; haveOldSamples = false; destroyFFTBuffers(true); return; } // no memory -> die
#endif

bool success = true;
if ((vReal == nullptr) || (vImag == nullptr)) success = alocateFFTBuffers(); // allocate sample buffers on first run
if (success == false) { disableSoundProcessing = true; return; } // no memory -> die
if (success == false) {
// no memory -> clean up heap, then suspend
disableSoundProcessing = true;
destroyFFTBuffers(true);
return;
}

// create FFT object - we have to do if after allocating buffers
#if defined(FFT_LIB_REV) && FFT_LIB_REV > 0x19
Expand All @@ -527,7 +570,8 @@ void FFTcode(void * parameter)
// recommended version optimized by @softhack007 (API version 1.9)
#if defined(WLED_ENABLE_HUB75MATRIX) && defined(CONFIG_IDF_TARGET_ESP32)
static float* windowWeighingFactors = nullptr;
if (!windowWeighingFactors) windowWeighingFactors = (float*) calloc(samplesFFT, sizeof(float)); // cache for FFT windowing factors - use heap
if (!windowWeighingFactors) windowWeighingFactors = (float*) d_calloc(samplesFFT, sizeof(float)); // cache for FFT windowing factors - use heap
if (!windowWeighingFactors) { disableSoundProcessing = true; haveOldSamples = false; destroyFFTBuffers(true); return; } // alloc failed
#else
static float windowWeighingFactors[samplesFFT] = {0.0f}; // cache for FFT windowing factors - use global RAM
#endif
Expand Down Expand Up @@ -1921,6 +1965,7 @@ class AudioReactive : public Usermod {
void setup() override
{
disableSoundProcessing = true; // just to be sure
isOOM = false;
if (!initDone) {
// usermod exchangeable data
// we will assign all usermod exportable data here as pointers to original variables or arrays and allocate memory for pointers
Expand Down Expand Up @@ -2497,11 +2542,12 @@ class AudioReactive : public Usermod {
vTaskResume(FFT_Task);
connected(); // resume UDP
} else {
isOOM = false;
if (audioSource) // WLEDMM only create FFT task if we have a valid audio source
// xTaskCreatePinnedToCore(
// xTaskCreate( // no need to "pin" this task to core #0
xTaskCreateUniversal(
FFTcode, // Function to implement the task
runFFTcode, // Function to implement the task
"FFT", // Name of the task
3592, // Stack size in words // 3592 leaves 800-1024 bytes of task stack free
NULL, // Task input parameter
Expand Down Expand Up @@ -2646,7 +2692,7 @@ class AudioReactive : public Usermod {
#else // ESP32 only
} else {
// Analog or I2S digital input
if (audioSource && (audioSource->isInitialized())) {
if (audioSource && (audioSource->isInitialized()) && !isOOM) {
// audio source successfully configured
if (audioSource->getType() == AudioSource::Type_I2SAdc) {
infoArr.add(F("ADC analog"));
Expand All @@ -2667,7 +2713,8 @@ class AudioReactive : public Usermod {
} else {
// error during audio source setup
infoArr.add(F("not initialized"));
if (dmType < 254) infoArr.add(F(" - check pin settings"));
if (isOOM) infoArr.add(F(" - out of memory"));
else if (dmType < 254) infoArr.add(F(" - check pin settings"));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,9 @@ class RotaryEncoderUIUsermod : public Usermod {
unsigned char Enc_A_prev = 0;

bool currentEffectAndPaletteInitialized = false;
uint8_t effectCurrentIndex = 0;
uint16_t effectCurrentIndex = 0;
uint8_t effectPaletteIndex = 0;
uint8_t knownMode = 0;
uint16_t knownMode = 0;
uint8_t knownPalette = 0;

uint8_t currentCCT = 128;
Expand Down Expand Up @@ -339,9 +339,11 @@ void RotaryEncoderUIUsermod::sortModesAndPalettes() {
//modes_qstrings = re_findModeStrings(JSON_mode_names, strip.getModeCount());
modes_qstrings = strip.getModeDataSrc();
modes_alpha_indexes = re_initIndexArray(strip.getModeCount());
if (!modes_alpha_indexes) return; // avoid nullptr crash when allocation has failed (OOM)
re_sortModes(modes_qstrings, modes_alpha_indexes, strip.getModeCount(), MODE_SORT_SKIP_COUNT);

palettes_qstrings = re_findModeStrings(JSON_palette_names, strip.getPaletteCount());
if (!palettes_qstrings) return; // avoid nullptr crash when allocation has failed (OOM)
palettes_alpha_indexes = re_initIndexArray(strip.getPaletteCount()); // only use internal palettes

// How many palette names start with '*' and should not be sorted?
Expand All @@ -352,9 +354,10 @@ void RotaryEncoderUIUsermod::sortModesAndPalettes() {
}

byte *RotaryEncoderUIUsermod::re_initIndexArray(int numModes) {
byte *indexes = (byte *)malloc(sizeof(byte) * numModes);
for (byte i = 0; i < numModes; i++) {
indexes[i] = i;
byte* indexes = (byte *)p_calloc(numModes, sizeof(byte));
if (!indexes) return nullptr; // avoid OOM crash
for (uint16_t i = 0; i < numModes; i++) {
indexes[i] = min(i, uint16_t(255));
}
return indexes;
}
Expand All @@ -364,8 +367,10 @@ byte *RotaryEncoderUIUsermod::re_initIndexArray(int numModes) {
* They don't end in '\0', they end in '"'.
*/
const char **RotaryEncoderUIUsermod::re_findModeStrings(const char json[], int numModes) {
const char **modeStrings = (const char **)malloc(sizeof(const char *) * numModes);
uint8_t modeIndex = 0;
const char** modeStrings = (const char **)p_calloc(numModes, sizeof(const char *));
if (!modeStrings) return nullptr; // avoid OOM crash

uint16_t modeIndex = 0;
bool insideQuotes = false;
// advance past the mark for markLineNum that may exist.
char singleJsonSymbol;
Expand All @@ -380,7 +385,7 @@ const char **RotaryEncoderUIUsermod::re_findModeStrings(const char json[], int n
insideQuotes = !insideQuotes;
if (insideQuotes) {
// We have a new mode or palette
modeStrings[modeIndex] = (char *)(json + i + 1);
if (modeIndex < numModes) modeStrings[modeIndex] = (char *)(json + i + 1); //WLEDMM prevent array bounds violation
}
break;
case '[':
Expand Down Expand Up @@ -630,7 +635,7 @@ void RotaryEncoderUIUsermod::displayNetworkInfo() {
void RotaryEncoderUIUsermod::findCurrentEffectAndPalette() {
if (modes_alpha_indexes == nullptr) return; // WLEDMM bugfix
currentEffectAndPaletteInitialized = true;
for (uint8_t i = 0; i < strip.getModeCount(); i++) {
for (uint16_t i = 0; i < strip.getModeCount(); i++) {
if (modes_alpha_indexes[i] == effectCurrent) {
effectCurrentIndex = i;
break;
Expand Down
2 changes: 1 addition & 1 deletion wled00/FX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ static uint16_t mode_oops(void) {
strip._colors_t[0] = RED;
strip._colors_t[1] = BLUE;
strip._colors_t[2] = GREEN;
errorFlag = ERR_NORAM_PX;
//errorFlag = ERR_NORAM_PX;
if (SEGLEN <= 1) return mode_static();
const uint16_t width = SEGMENT.is2D() ? SEGMENT.virtualWidth() : SEGMENT.virtualLength();
const uint16_t height = SEGMENT.virtualHeight();
Expand Down
6 changes: 3 additions & 3 deletions wled00/FX.h
Original file line number Diff line number Diff line change
Expand Up @@ -609,7 +609,7 @@ typedef struct Segment {
endImagePlayback(this);
#endif

if ((Segment::_globalLeds == nullptr) && !strip_uses_global_leds() && (ledsrgb != nullptr)) {free(ledsrgb); ledsrgb = nullptr;} // WLEDMM we need "!strip_uses_global_leds()" to avoid crashes (#104)
if ((Segment::_globalLeds == nullptr) && !strip_uses_global_leds() && (ledsrgb != nullptr)) {d_free(ledsrgb); ledsrgb = nullptr;} // WLEDMM we need "!strip_uses_global_leds()" to avoid crashes (#104)
if (name) { delete[] name; name = nullptr; }
if (_t) { transitional = false; delete _t; _t = nullptr; }
deallocateData();
Expand Down Expand Up @@ -1009,15 +1009,15 @@ class WS2812FX { // 96 bytes
#ifdef WLED_DEBUG
if (Serial) Serial.println(F("~WS2812FX destroying strip.")); // WLEDMM can't use DEBUG_PRINTLN here
#endif
if (customMappingTable) delete[] customMappingTable;
if (customMappingTable) d_free(customMappingTable); customMappingTable = nullptr;
_mode.clear();
_modeData.clear();
_segments.clear();
#ifndef WLED_DISABLE_2D
panel.clear();
#endif
customPalettes.clear();
if (useLedsArray && Segment::_globalLeds) free(Segment::_globalLeds);
if (useLedsArray && Segment::_globalLeds) d_free(Segment::_globalLeds);
}

static WS2812FX* getInstance(void) { return instance; }
Expand Down
11 changes: 6 additions & 5 deletions wled00/FX_2Dfcn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,12 @@ void WS2812FX::setUpMatrix() {

// don't use new / delete
if ((size > 0) && (customMappingTable != nullptr)) { // resize
customMappingTable = (uint16_t*) reallocf(customMappingTable, sizeof(uint16_t) * size); // reallocf will free memory if it cannot resize
//customMappingTable = (uint16_t*) reallocf(customMappingTable, sizeof(uint16_t) * size); // reallocf will free memory if it cannot resize
customMappingTable = (uint16_t*) d_realloc_malloc(customMappingTable, sizeof(uint16_t) * size); // will free memory if it cannot resize
}
if ((size > 0) && (customMappingTable == nullptr)) { // second try
DEBUG_PRINTLN("setUpMatrix: trying to get fresh memory block.");
customMappingTable = (uint16_t*) calloc(size, sizeof(uint16_t));
customMappingTable = (uint16_t*) d_calloc(size, sizeof(uint16_t));
if (customMappingTable == nullptr) {
USER_PRINTLN("setUpMatrix: alloc failed");
errorFlag = ERR_LOW_MEM; // WLEDMM raise errorflag
Expand Down Expand Up @@ -122,7 +123,7 @@ void WS2812FX::setUpMatrix() {
JsonArray map = doc.as<JsonArray>();
gapSize = map.size();
if (!map.isNull() && (gapSize > 0) && gapSize >= customMappingSize) { // not an empty map //softhack also check gapSize>0
gapTable = new(std::nothrow) int8_t[gapSize];
gapTable = static_cast<int8_t*>(p_malloc(gapSize));
if (gapTable) for (size_t i = 0; i < gapSize; i++) {
gapTable[i] = constrain(map[i], -1, 1);
}
Expand Down Expand Up @@ -152,7 +153,7 @@ void WS2812FX::setUpMatrix() {
}

// delete gap array as we no longer need it
if (gapTable) {delete[] gapTable; gapTable=nullptr;} // softhack prevent dangling pointer
if (gapTable) {p_free(gapTable); gapTable=nullptr;} // softhack prevent dangling pointer

#ifdef WLED_DEBUG_MAPS
DEBUG_PRINTF("Matrix ledmap: \n");
Expand Down Expand Up @@ -185,7 +186,7 @@ void WS2812FX::setUpMatrix() {
if (customMappingTable[i] != (uint16_t)i ) isIdentity = false;
}
if (isIdentity) {
free(customMappingTable); customMappingTable = nullptr;
d_free(customMappingTable); customMappingTable = nullptr;
USER_PRINTF("!setupmatrix: customMappingTable is not needed. Dropping %d bytes.\n", customMappingTableSize * sizeof(uint16_t));
customMappingTableSize = 0;
customMappingSize = 0;
Expand Down
Loading
Loading