Skip to content

Commit cce3c0b

Browse files
authored
psram-aware malloc functions (backport of upstream wled#4895) (#342)
Introduces new memory allocation functions, based on wled#4895 by @DedeHai * keep a minimum of 15KB RAM available to UI - improves stability * S3/S2/C3 automatically use "fast RTC Ram" for small data (reduces fragmentation) * auto-detects PSRAM (or no PSRAM) when firmware was built with -D BOARD_HAS_PSRAM * d_malloc() and d_calloc() prefer DRAM if possible (faster), but fall back to PSRAM when free RAM gets low.
1 parent 50bb4d2 commit cce3c0b

19 files changed

Lines changed: 697 additions & 192 deletions

File tree

usermods/artifx/arti.h

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2562,14 +2562,24 @@ class ARTI {
25622562

25632563
//open programFile
25642564
char * programText = nullptr;
2565-
uint16_t programFileSize;
2565+
size_t programFileSize;
25662566
#if ARTI_PLATFORM == ARTI_ARDUINO
25672567
programFileSize = programFile.size();
2568-
programText = (char *)malloc(programFileSize+1);
2568+
programText = (char *)d_malloc(programFileSize+1);
2569+
if (programText == nullptr) {
2570+
ERROR_ARTI("ARTI-FX: Failed to allocate memory for program file (%u bytes)\n", programFileSize+1);
2571+
programFile.close();
2572+
return false;
2573+
}
25692574
programFile.read((byte *)programText, programFileSize);
25702575
programText[programFileSize] = '\0';
25712576
#else
2572-
programText = (char *)malloc(programTextSize);
2577+
programText = (char *)malloc(programTextSize+1);
2578+
if (programText == nullptr) {
2579+
ERROR_ARTI("ARTI-FX: Failed to allocate memory for program file (%u bytes)\n", programTextSize);
2580+
programFile.close();
2581+
return false;
2582+
}
25732583
programFile.read(programText, programTextSize);
25742584
DEBUG_ARTI("programFile size %lu bytes\n", programFile.gcount());
25752585
programText[programFile.gcount()] = '\0';
@@ -2607,7 +2617,13 @@ class ARTI {
26072617
#endif
26082618

26092619
if (stages < 1) {
2610-
if (nullptr != programText) free(programText); // softhack007 prevent memory leak
2620+
// softhack007 prevent memory leak
2621+
#if ARTI_PLATFORM == ARTI_ARDUINO
2622+
if (nullptr != programText) d_free(programText);
2623+
#else
2624+
if (nullptr != programText) free(programText);
2625+
#endif
2626+
programText = nullptr;
26112627
close();
26122628
return true;
26132629
}
@@ -2666,8 +2682,11 @@ class ARTI {
26662682
#endif
26672683
}
26682684
#if ARTI_PLATFORM == ARTI_ARDUINO //not on windows as cause crash???
2685+
d_free(programText);
2686+
#else
26692687
free(programText);
26702688
#endif
2689+
programText = nullptr;
26712690

26722691
if (stages >= 3)
26732692
{

usermods/audioreactive/audio_reactive.h

Lines changed: 62 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -131,9 +131,10 @@ static volatile bool disableSoundProcessing = false; // if true, sound proc
131131
static uint8_t audioSyncEnabled = AUDIOSYNC_NONE; // bit field: bit 0 - send, bit 1 - receive, bit 2 - use local if not receiving
132132
static bool audioSyncSequence = true; // if true, the receiver will drop out-of-sequence packets
133133
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)
134-
static bool udpSyncConnected = false; // UDP connection status -> true if connected to multicast group
134+
static bool udpSyncConnected = false; // UDP connection status -> true if connected to multicast group
135+
static volatile bool isOOM = false; // FFTask: not enough memory for buffers (audio processing failed to start)
135136

136-
#define NUM_GEQ_CHANNELS 16 // number of frequency channels. Don't change !!
137+
#define NUM_GEQ_CHANNELS 16 // number of frequency channels. Don't change !!
137138

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

457-
if (vReal) free(vReal); // should not happen
458-
if (vImag) free(vImag); // should not happen
459-
if ((vReal = (float*) calloc(samplesFFT, sizeof(float))) == nullptr) return false; // calloc or die
460-
if ((vImag = (float*) calloc(samplesFFT, sizeof(float))) == nullptr) return false;
458+
if (vReal) d_free(vReal); vReal = nullptr; // should not happen
459+
if (vImag) d_free(vImag); vImag = nullptr; // should not happen
460+
if ((vReal = (float*) d_calloc(samplesFFT, sizeof(float))) == nullptr) return false; // calloc or die
461+
if ((vImag = (float*) d_calloc(samplesFFT, sizeof(float))) == nullptr) return false;
461462
#ifdef FFT_MAJORPEAK_HUMAN_EAR
462-
if (pinkFactors) free(pinkFactors);
463-
if ((pinkFactors = (float*) calloc(samplesFFT, sizeof(float))) == nullptr) return false;
463+
if (pinkFactors) p_free(pinkFactors);
464+
if ((pinkFactors = (float*) p_calloc(samplesFFT, sizeof(float))) == nullptr) return false;
464465
#endif
465466

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

476+
// de-allocate FFT sample buffers from heap
477+
static void destroyFFTBuffers(bool panicOOM) {
478+
#ifdef FFT_MAJORPEAK_HUMAN_EAR
479+
if (pinkFactors) p_free(pinkFactors); pinkFactors = nullptr;
480+
#endif
481+
if (vImag) d_free(vImag); vImag = nullptr;
482+
if (vReal) d_free(vReal); vReal = nullptr;
483+
484+
if (panicOOM && !isOOM) { // notify user
485+
isOOM = true;
486+
errorFlag = ERR_LOW_MEM;
487+
USER_PRINTLN("AR startup failed - out of memory!");
488+
}
489+
#ifdef SR_DEBUG
490+
USER_PRINTLN("\ndestroyFFTBuffers() completed successfully.");
491+
USER_PRINT(F("Free heap: ")); USER_PRINTLN(ESP.getFreeHeap());
492+
USER_FLUSH();
493+
#endif
494+
}
495+
496+
475497
// High-Pass "DC blocker" filter
476498
// see https://www.dsprelated.com/freebooks/filters/DC_Blocker.html
477499
static void runDCBlocker(uint_fast16_t numSamples, float *sampleBuffer) {
@@ -488,6 +510,22 @@ static void runDCBlocker(uint_fast16_t numSamples, float *sampleBuffer) {
488510
}
489511
}
490512

513+
//
514+
// FFT runner - "return" from a task function causes crash. This wrapper keeps the task alive
515+
//
516+
void runFFTcode(void * parameter) __attribute__((noreturn,used));
517+
void runFFTcode(void * parameter) {
518+
bool firstFail = true; // prevents flood of warnings
519+
do {
520+
if (!disableSoundProcessing) {
521+
FFTcode(parameter);
522+
if (firstFail) {USER_PRINTLN(F("warning: unexpected exit of FFT main task."));}
523+
firstFail = false;
524+
} else firstFail = true; // re-enable warning message
525+
vTaskDelay(1000); // if we arrive here, FFcode has returned due to OOM. Wait a bit, then try again.
526+
} while (true);
527+
}
528+
491529
//
492530
// FFT main task
493531
//
@@ -511,13 +549,18 @@ void FFTcode(void * parameter)
511549
static float* oldSamples = nullptr; // previous 50% of samples
512550
static bool haveOldSamples = false; // for sliding window FFT
513551
bool usingOldSamples = false;
514-
if (!oldSamples) oldSamples = (float*) calloc(samplesFFT_2, sizeof(float)); // allocate on first run
515-
if (!oldSamples) { disableSoundProcessing = true; return; } // no memory -> die
552+
if (!oldSamples) oldSamples = (float*) d_calloc(samplesFFT_2, sizeof(float)); // allocate on first run
553+
if (!oldSamples) { disableSoundProcessing = true; haveOldSamples = false; destroyFFTBuffers(true); return; } // no memory -> die
516554
#endif
517555

518556
bool success = true;
519557
if ((vReal == nullptr) || (vImag == nullptr)) success = alocateFFTBuffers(); // allocate sample buffers on first run
520-
if (success == false) { disableSoundProcessing = true; return; } // no memory -> die
558+
if (success == false) {
559+
// no memory -> clean up heap, then suspend
560+
disableSoundProcessing = true;
561+
destroyFFTBuffers(true);
562+
return;
563+
}
521564

522565
// create FFT object - we have to do if after allocating buffers
523566
#if defined(FFT_LIB_REV) && FFT_LIB_REV > 0x19
@@ -527,7 +570,8 @@ void FFTcode(void * parameter)
527570
// recommended version optimized by @softhack007 (API version 1.9)
528571
#if defined(WLED_ENABLE_HUB75MATRIX) && defined(CONFIG_IDF_TARGET_ESP32)
529572
static float* windowWeighingFactors = nullptr;
530-
if (!windowWeighingFactors) windowWeighingFactors = (float*) calloc(samplesFFT, sizeof(float)); // cache for FFT windowing factors - use heap
573+
if (!windowWeighingFactors) windowWeighingFactors = (float*) d_calloc(samplesFFT, sizeof(float)); // cache for FFT windowing factors - use heap
574+
if (!windowWeighingFactors) { disableSoundProcessing = true; haveOldSamples = false; destroyFFTBuffers(true); return; } // alloc failed
531575
#else
532576
static float windowWeighingFactors[samplesFFT] = {0.0f}; // cache for FFT windowing factors - use global RAM
533577
#endif
@@ -1921,6 +1965,7 @@ class AudioReactive : public Usermod {
19211965
void setup() override
19221966
{
19231967
disableSoundProcessing = true; // just to be sure
1968+
isOOM = false;
19241969
if (!initDone) {
19251970
// usermod exchangeable data
19261971
// we will assign all usermod exportable data here as pointers to original variables or arrays and allocate memory for pointers
@@ -2497,11 +2542,12 @@ class AudioReactive : public Usermod {
24972542
vTaskResume(FFT_Task);
24982543
connected(); // resume UDP
24992544
} else {
2545+
isOOM = false;
25002546
if (audioSource) // WLEDMM only create FFT task if we have a valid audio source
25012547
// xTaskCreatePinnedToCore(
25022548
// xTaskCreate( // no need to "pin" this task to core #0
25032549
xTaskCreateUniversal(
2504-
FFTcode, // Function to implement the task
2550+
runFFTcode, // Function to implement the task
25052551
"FFT", // Name of the task
25062552
3592, // Stack size in words // 3592 leaves 800-1024 bytes of task stack free
25072553
NULL, // Task input parameter
@@ -2646,7 +2692,7 @@ class AudioReactive : public Usermod {
26462692
#else // ESP32 only
26472693
} else {
26482694
// Analog or I2S digital input
2649-
if (audioSource && (audioSource->isInitialized())) {
2695+
if (audioSource && (audioSource->isInitialized()) && !isOOM) {
26502696
// audio source successfully configured
26512697
if (audioSource->getType() == AudioSource::Type_I2SAdc) {
26522698
infoArr.add(F("ADC analog"));
@@ -2667,7 +2713,8 @@ class AudioReactive : public Usermod {
26672713
} else {
26682714
// error during audio source setup
26692715
infoArr.add(F("not initialized"));
2670-
if (dmType < 254) infoArr.add(F(" - check pin settings"));
2716+
if (isOOM) infoArr.add(F(" - out of memory"));
2717+
else if (dmType < 254) infoArr.add(F(" - check pin settings"));
26712718
}
26722719
}
26732720

usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -151,9 +151,9 @@ class RotaryEncoderUIUsermod : public Usermod {
151151
unsigned char Enc_A_prev = 0;
152152

153153
bool currentEffectAndPaletteInitialized = false;
154-
uint8_t effectCurrentIndex = 0;
154+
uint16_t effectCurrentIndex = 0;
155155
uint8_t effectPaletteIndex = 0;
156-
uint8_t knownMode = 0;
156+
uint16_t knownMode = 0;
157157
uint8_t knownPalette = 0;
158158

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

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

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

354356
byte *RotaryEncoderUIUsermod::re_initIndexArray(int numModes) {
355-
byte *indexes = (byte *)malloc(sizeof(byte) * numModes);
356-
for (byte i = 0; i < numModes; i++) {
357-
indexes[i] = i;
357+
byte* indexes = (byte *)p_calloc(numModes, sizeof(byte));
358+
if (!indexes) return nullptr; // avoid OOM crash
359+
for (uint16_t i = 0; i < numModes; i++) {
360+
indexes[i] = min(i, uint16_t(255));
358361
}
359362
return indexes;
360363
}
@@ -364,8 +367,10 @@ byte *RotaryEncoderUIUsermod::re_initIndexArray(int numModes) {
364367
* They don't end in '\0', they end in '"'.
365368
*/
366369
const char **RotaryEncoderUIUsermod::re_findModeStrings(const char json[], int numModes) {
367-
const char **modeStrings = (const char **)malloc(sizeof(const char *) * numModes);
368-
uint8_t modeIndex = 0;
370+
const char** modeStrings = (const char **)p_calloc(numModes, sizeof(const char *));
371+
if (!modeStrings) return nullptr; // avoid OOM crash
372+
373+
uint16_t modeIndex = 0;
369374
bool insideQuotes = false;
370375
// advance past the mark for markLineNum that may exist.
371376
char singleJsonSymbol;
@@ -380,7 +385,7 @@ const char **RotaryEncoderUIUsermod::re_findModeStrings(const char json[], int n
380385
insideQuotes = !insideQuotes;
381386
if (insideQuotes) {
382387
// We have a new mode or palette
383-
modeStrings[modeIndex] = (char *)(json + i + 1);
388+
if (modeIndex < numModes) modeStrings[modeIndex] = (char *)(json + i + 1); //WLEDMM prevent array bounds violation
384389
}
385390
break;
386391
case '[':
@@ -630,7 +635,7 @@ void RotaryEncoderUIUsermod::displayNetworkInfo() {
630635
void RotaryEncoderUIUsermod::findCurrentEffectAndPalette() {
631636
if (modes_alpha_indexes == nullptr) return; // WLEDMM bugfix
632637
currentEffectAndPaletteInitialized = true;
633-
for (uint8_t i = 0; i < strip.getModeCount(); i++) {
638+
for (uint16_t i = 0; i < strip.getModeCount(); i++) {
634639
if (modes_alpha_indexes[i] == effectCurrent) {
635640
effectCurrentIndex = i;
636641
break;

wled00/FX.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ static uint16_t mode_oops(void) {
157157
strip._colors_t[0] = RED;
158158
strip._colors_t[1] = BLUE;
159159
strip._colors_t[2] = GREEN;
160-
errorFlag = ERR_NORAM_PX;
160+
//errorFlag = ERR_NORAM_PX;
161161
if (SEGLEN <= 1) return mode_static();
162162
const uint16_t width = SEGMENT.is2D() ? SEGMENT.virtualWidth() : SEGMENT.virtualLength();
163163
const uint16_t height = SEGMENT.virtualHeight();

wled00/FX.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -609,7 +609,7 @@ typedef struct Segment {
609609
endImagePlayback(this);
610610
#endif
611611

612-
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)
612+
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)
613613
if (name) { delete[] name; name = nullptr; }
614614
if (_t) { transitional = false; delete _t; _t = nullptr; }
615615
deallocateData();
@@ -1009,15 +1009,15 @@ class WS2812FX { // 96 bytes
10091009
#ifdef WLED_DEBUG
10101010
if (Serial) Serial.println(F("~WS2812FX destroying strip.")); // WLEDMM can't use DEBUG_PRINTLN here
10111011
#endif
1012-
if (customMappingTable) delete[] customMappingTable;
1012+
if (customMappingTable) d_free(customMappingTable); customMappingTable = nullptr;
10131013
_mode.clear();
10141014
_modeData.clear();
10151015
_segments.clear();
10161016
#ifndef WLED_DISABLE_2D
10171017
panel.clear();
10181018
#endif
10191019
customPalettes.clear();
1020-
if (useLedsArray && Segment::_globalLeds) free(Segment::_globalLeds);
1020+
if (useLedsArray && Segment::_globalLeds) d_free(Segment::_globalLeds);
10211021
}
10221022

10231023
static WS2812FX* getInstance(void) { return instance; }

wled00/FX_2Dfcn.cpp

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,12 @@ void WS2812FX::setUpMatrix() {
7676

7777
// don't use new / delete
7878
if ((size > 0) && (customMappingTable != nullptr)) { // resize
79-
customMappingTable = (uint16_t*) reallocf(customMappingTable, sizeof(uint16_t) * size); // reallocf will free memory if it cannot resize
79+
//customMappingTable = (uint16_t*) reallocf(customMappingTable, sizeof(uint16_t) * size); // reallocf will free memory if it cannot resize
80+
customMappingTable = (uint16_t*) d_realloc_malloc(customMappingTable, sizeof(uint16_t) * size); // will free memory if it cannot resize
8081
}
8182
if ((size > 0) && (customMappingTable == nullptr)) { // second try
8283
DEBUG_PRINTLN("setUpMatrix: trying to get fresh memory block.");
83-
customMappingTable = (uint16_t*) calloc(size, sizeof(uint16_t));
84+
customMappingTable = (uint16_t*) d_calloc(size, sizeof(uint16_t));
8485
if (customMappingTable == nullptr) {
8586
USER_PRINTLN("setUpMatrix: alloc failed");
8687
errorFlag = ERR_LOW_MEM; // WLEDMM raise errorflag
@@ -122,7 +123,7 @@ void WS2812FX::setUpMatrix() {
122123
JsonArray map = doc.as<JsonArray>();
123124
gapSize = map.size();
124125
if (!map.isNull() && (gapSize > 0) && gapSize >= customMappingSize) { // not an empty map //softhack also check gapSize>0
125-
gapTable = new(std::nothrow) int8_t[gapSize];
126+
gapTable = static_cast<int8_t*>(p_malloc(gapSize));
126127
if (gapTable) for (size_t i = 0; i < gapSize; i++) {
127128
gapTable[i] = constrain(map[i], -1, 1);
128129
}
@@ -152,7 +153,7 @@ void WS2812FX::setUpMatrix() {
152153
}
153154

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

157158
#ifdef WLED_DEBUG_MAPS
158159
DEBUG_PRINTF("Matrix ledmap: \n");
@@ -185,7 +186,7 @@ void WS2812FX::setUpMatrix() {
185186
if (customMappingTable[i] != (uint16_t)i ) isIdentity = false;
186187
}
187188
if (isIdentity) {
188-
free(customMappingTable); customMappingTable = nullptr;
189+
d_free(customMappingTable); customMappingTable = nullptr;
189190
USER_PRINTF("!setupmatrix: customMappingTable is not needed. Dropping %d bytes.\n", customMappingTableSize * sizeof(uint16_t));
190191
customMappingTableSize = 0;
191192
customMappingSize = 0;

0 commit comments

Comments
 (0)