diff --git a/usermods/artifx/arti.h b/usermods/artifx/arti.h index 21eb649fbb..1e3cbd0391 100644 --- a/usermods/artifx/arti.h +++ b/usermods/artifx/arti.h @@ -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); + 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'; @@ -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; } @@ -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) { diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 90e1651fcf..2b3a13482f 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -454,13 +454,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); // should not happen + if (vImag) d_free(vImag); // 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 @@ -472,6 +472,21 @@ static bool alocateFFTBuffers(void) { return(true); // success } +// de-allocate FFT sample buffers from heap +static void destroyFFTBuffers(void) { + #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; + #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) { @@ -511,13 +526,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(); 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(); + return; + } // create FFT object - we have to do if after allocating buffers #if defined(FFT_LIB_REV) && FFT_LIB_REV > 0x19 @@ -527,7 +547,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(); return; } // alloc failed #else static float windowWeighingFactors[samplesFFT] = {0.0f}; // cache for FFT windowing factors - use global RAM #endif diff --git a/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h b/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h index 66a1401a1d..7990215bba 100644 --- a/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h +++ b/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h @@ -352,7 +352,8 @@ void RotaryEncoderUIUsermod::sortModesAndPalettes() { } byte *RotaryEncoderUIUsermod::re_initIndexArray(int numModes) { - byte *indexes = (byte *)malloc(sizeof(byte) * numModes); + byte* indexes = (byte *)p_calloc(numModes, sizeof(byte)); + if (!indexes) return nullptr; // avoid OOM crash for (byte i = 0; i < numModes; i++) { indexes[i] = i; } @@ -364,7 +365,9 @@ 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); + const char** modeStrings = (const char **)p_calloc(numModes, sizeof(const char *)); + if (!modeStrings) return nullptr; // avoid OOM crash + uint8_t modeIndex = 0; bool insideQuotes = false; // advance past the mark for markLineNum that may exist. @@ -380,7 +383,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 '[': diff --git a/wled00/FX.h b/wled00/FX.h index ddfa3c8777..3a03e6ccfa 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -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(); @@ -1009,7 +1009,7 @@ 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(); @@ -1017,7 +1017,7 @@ class WS2812FX { // 96 bytes 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; } diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index d12153d5ff..a350a651da 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -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 @@ -122,7 +123,7 @@ void WS2812FX::setUpMatrix() { JsonArray map = doc.as(); 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(p_malloc(gapSize)); if (gapTable) for (size_t i = 0; i < gapSize; i++) { gapTable[i] = constrain(map[i], -1, 1); } @@ -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"); @@ -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; diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 996bb57b60..ba80d0f374 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -111,7 +111,7 @@ void Segment::allocLeds() { DEBUG_PRINTF("allocLeds warning: size == %u !!\n", size); if (ledsrgb && (ledsrgbSize == 0)) { USER_PRINTLN("allocLeds warning: ledsrgbSize == 0 but ledsrgb!=NULL"); - free(ledsrgb); ledsrgb=nullptr; + d_free(ledsrgb); ledsrgb=nullptr; } // softhack007 clean up buffer } if ((size > 0) && (!ledsrgb || size > ledsrgbSize)) { //softhack dont allocate zero bytes @@ -124,8 +124,8 @@ void Segment::allocLeds() { ledsrgb = nullptr; portEXIT_CRITICAL(&ledsrgb_mux); - if (oldLedsRgb) free(oldLedsRgb); // we need a bigger buffer, so free the old one first - CRGB* newLedsRgb = (CRGB*)calloc(size, 1); // WLEDMM This is an OS call, so we should not wrap it in portEnterCRITICAL + if (oldLedsRgb) d_free(oldLedsRgb); // we need a bigger buffer, so free the old one first + CRGB* newLedsRgb = (CRGB*)d_calloc(size, 1); // WLEDMM This is an OS call, so we should not wrap it in portEnterCRITICAL portENTER_CRITICAL(&ledsrgb_mux); ledsrgb = newLedsRgb; @@ -175,7 +175,7 @@ Segment& Segment::operator= (const Segment &orig) { if (_t) delete _t; CRGB* oldLeds = ledsrgb; size_t oldLedsSize = ledsrgbSize; - if (ledsrgb && !Segment::_globalLeds) free(ledsrgb); + if (ledsrgb && !Segment::_globalLeds) d_free(ledsrgb); deallocateData(); // copy source memcpy((void*)this, (void*)&orig, sizeof(Segment)); @@ -212,7 +212,7 @@ Segment& Segment::operator= (Segment &&orig) noexcept { if (name) { delete[] name; name = nullptr; } // free old name deallocateData(); // free old runtime data if (_t) { delete _t; _t = nullptr; } - if (ledsrgb && !Segment::_globalLeds) free(ledsrgb); //WLEDMM: not needed anymore as we will use leds from copy. no need to nullify ledsrgb as it gets new value in memcpy + if (ledsrgb && !Segment::_globalLeds) d_free(ledsrgb); //WLEDMM: not needed anymore as we will use leds from copy. no need to nullify ledsrgb as it gets new value in memcpy // WLEDMM temporarily prevent any fast draw calls to old and new segment orig._isSimpleSegment = false; @@ -265,7 +265,7 @@ bool Segment::allocateData(size_t len, bool allowOverdraft) { // WLEDMM allowOv // data = (byte*) ps_malloc(len); //else //#endif - data = (byte*) malloc(len); + data = (byte*) d_malloc(len); if (!data) { _dataLen = 0; // WLEDMM reset dataLen if ((errorFlag != ERR_LOW_MEM) && (errorFlag != ERR_LOW_SEG_MEM)) { // spam filter @@ -292,7 +292,7 @@ void Segment::deallocateData() { _dataLen = 0; return; } // WLEDMM reset dataLen - free(data); + d_free(data); data = nullptr; DEBUG_PRINTF("Segment::deallocateData: free'd %d bytes.\n", _dataLen); Segment::addUsedSegmentData(-_dataLen); @@ -308,7 +308,7 @@ void Segment::deallocateData() { */ void Segment::resetIfRequired() { if (reset) { - if (ledsrgb && !Segment::_globalLeds) { free(ledsrgb); ledsrgb = nullptr; ledsrgbSize=0;} // WLEDMM segment has changed, so we need a fresh buffer. + if (ledsrgb && !Segment::_globalLeds) { d_free(ledsrgb); ledsrgb = nullptr; ledsrgbSize=0;} // WLEDMM segment has changed, so we need a fresh buffer. if (transitional && _t) { transitional = false; delete _t; _t = nullptr; } deallocateData(); next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0; @@ -1866,7 +1866,7 @@ void WS2812FX::finalizeInit(void) portENTER_CRITICAL(&ledsrgb_mux); Segment::_globalLeds = nullptr; portEXIT_CRITICAL(&ledsrgb_mux); - free(oldGLeds); + d_free(oldGLeds); purgeSegments(true); // WLEDMM moved here, because it seems to improve stability. } if (useLedsArray && getLengthTotal()>0) { // WLEDMM avoid malloc(0) @@ -1877,7 +1877,7 @@ void WS2812FX::finalizeInit(void) // Segment::_globalLeds = (CRGB*) ps_malloc(arrSize); //else //#endif - if (arrSize > 0) Segment::_globalLeds = (CRGB*) malloc(arrSize); // WLEDMM avoid malloc(0) + if (arrSize > 0) Segment::_globalLeds = (CRGB*) d_malloc(arrSize); // WLEDMM avoid malloc(0) if ((Segment::_globalLeds != nullptr) && (arrSize > 0)) memset(Segment::_globalLeds, 0, arrSize); // WLEDMM avoid dereferencing nullptr if ((Segment::_globalLeds == nullptr) && (arrSize > 0)) errorFlag = ERR_NORAM_PX; // WLEDMM raise errorflag } @@ -2703,11 +2703,12 @@ bool WS2812FX::deserializeMap(uint8_t n) { // don't use new / delete if ((size > 0) && (customMappingTable != nullptr)) { - 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("deserializeMap: 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) { DEBUG_PRINTLN("deserializeMap: alloc failed!"); errorFlag = ERR_LOW_MEM; // WLEDMM raise errorflag diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 62da0da86f..19d432b629 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -57,6 +57,22 @@ uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, byte #define DEBUGOUT Serial #endif +//util.cpp +// memory allocation wrappers +// forward declaration: memory functions (util.cpp) +extern "C" { + // 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); + void d_free(void *ptr); + // 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); + void p_free(void *ptr); +} + #ifdef WLED_DEBUG #ifndef ESP8266 #include @@ -668,7 +684,7 @@ BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWh MatrixPanel_I2S_DMA* display = nullptr; VirtualMatrixPanel* fourScanPanel = nullptr; HUB75_I2S_CFG mxconfig; - size_t lastHeap = ESP.getFreeHeap(); + size_t lastHeap = getFreeHeapSize(); _valid = false; _len = 0; @@ -935,7 +951,7 @@ BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWh USER_PRINTF("\tLAT = %2d, OE = %2d, CLK = %2d\n\n", mxconfig.gpio.lat, mxconfig.gpio.oe, mxconfig.gpio.clk); USER_FLUSH(); - DEBUG_PRINT(F("Free heap: ")); DEBUG_PRINTLN(ESP.getFreeHeap()); lastHeap = ESP.getFreeHeap(); + DEBUG_PRINT(F("Free heap: ")); DEBUG_PRINTLN(getFreeHeapSize()); lastHeap = getFreeHeapSize(); // check if we can re-use the existing display driver if (activeDisplay) { @@ -980,7 +996,7 @@ BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWh USER_PRINTLN("****** MatrixPanel_I2S_DMA !KABOOM! driver allocation failed ***********"); activeDisplay = nullptr; activeFourScanPanel = nullptr; - USER_PRINT(F("heap usage: ")); USER_PRINTLN(int(lastHeap - ESP.getFreeHeap())); + USER_PRINT(F("heap usage: ")); USER_PRINTLN(int(lastHeap - getFreeHeapSize())); return; } @@ -1011,11 +1027,11 @@ BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWh _bri = (last_bri > 0) ? last_bri : 25; // try to restore persistent brightness value delay(24); // experimental - DEBUG_PRINT(F("heap usage: ")); DEBUG_PRINTLN(int(lastHeap - ESP.getFreeHeap())); + DEBUG_PRINT(F("heap usage: ")); DEBUG_PRINTLN(int(lastHeap - getFreeHeapSize())); // Allocate memory and start DMA display if (newDisplay && (display->begin() == false)) { USER_PRINTLN("****** MatrixPanel_I2S_DMA !KABOOM! I2S memory allocation failed ***********"); - USER_PRINT(F("heap usage: ")); USER_PRINTLN(int(lastHeap - ESP.getFreeHeap())); + USER_PRINT(F("heap usage: ")); USER_PRINTLN(int(lastHeap - getFreeHeapSize())); _valid = false; return; } @@ -1023,7 +1039,7 @@ BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWh if (newDisplay) { USER_PRINTLN("MatrixPanel_I2S_DMA begin, started ok"); } else { USER_PRINTLN("MatrixPanel_I2S_DMA begin, using existing display."); } - USER_PRINT(F("heap usage: ")); USER_PRINTLN(int(lastHeap - ESP.getFreeHeap())); + USER_PRINT(F("heap usage: ")); USER_PRINTLN(int(lastHeap - getFreeHeapSize())); delay(18); // experiment - give the driver a moment (~ one full frame @ 60hz) to settle _valid = true; display->setBrightness8(_bri); // range is 0-255, 0 - 0%, 255 - 100% // [setBrightness()] Tried to set output brightness before begin() @@ -1053,7 +1069,7 @@ BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWh USER_PRINTLN(F("MatrixPanel_I2S_DMA not started - not enough memory for leds buffer!")); cleanup(); // free buffers, and deallocate pins _valid = false; - USER_PRINT(F("heap usage: ")); USER_PRINTLN(int(lastHeap - ESP.getFreeHeap())); + USER_PRINT(F("heap usage: ")); USER_PRINTLN(int(lastHeap - getFreeHeapSize())); return; // fail } @@ -1114,7 +1130,7 @@ BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWh #endif instanceCount++; - USER_PRINT(F("heap usage: ")); USER_PRINTLN(int(lastHeap - ESP.getFreeHeap())); + USER_PRINT(F("heap usage: ")); USER_PRINTLN(int(lastHeap - getFreeHeapSize())); } void __attribute__((hot)) IRAM_ATTR BusHub75Matrix::setPixelColor(uint16_t pix, uint32_t c) { diff --git a/wled00/const.h b/wled00/const.h index c81854dad0..1953d5ee36 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -528,7 +528,29 @@ #endif #endif -// Web server limits +// minimum heap size required to process web requests: try to keep free heap above this value +#if !defined(MIN_HEAP_SIZE) +#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 +#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 (8k for AsyncWebServer) +//#if !defined(MIN_HEAP_SIZE) +//#define MIN_HEAP_SIZE 8192 +//#endif + #ifdef ESP8266 // Minimum heap to consider handling a request #define WLED_REQUEST_MIN_HEAP (8*1024) @@ -545,11 +567,6 @@ // Websockets do not count against this limit. #define WLED_REQUEST_MAX_QUEUE 6 -//#define MIN_HEAP_SIZE (8k for AsyncWebServer) -#if !defined(MIN_HEAP_SIZE) -#define MIN_HEAP_SIZE 8192 -#endif - // Maximum size of node map (list of other WLED instances) #ifdef ESP8266 #define WLED_MAX_NODES 24 diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index a9f14d73c9..4908d10a97 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -491,6 +491,42 @@ 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 +// memory allocation wrappers (util.cpp) +extern "C" { + // 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); + void d_free(void *ptr); + // 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); + void p_free(void *ptr); +} +#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) // WLEDMM can cause LED glitches +//inline size_t getContiguousFreeHeap() { return heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); } // returns largest contiguous free block // WLEDMM may glitch, too + +extern size_t d_measureFreeHeap(void); +extern size_t d_measureContiguousFreeHeap(void); +inline size_t getFreeHeapSize() { return d_measureFreeHeap();} // total free heap - with flicker protection +inline size_t getContiguousFreeHeap() { return d_measureContiguousFreeHeap();} // largest free block - with flicker protection + +#else +inline size_t getFreeHeapSize() { return ESP.getFreeHeap(); } // returns free heap +inline size_t getContiguousFreeHeap() { return ESP.getMaxFreeBlockSize(); } // returns largest contiguous free block +#endif +#if 0 // WLEDMM not used yet +#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); +#endif + // RAII guard class for the JSON Buffer lock // Modeled after std::lock_guard class JSONBufferGuard { diff --git a/wled00/file.cpp b/wled00/file.cpp index 88e2d9389a..dfac394026 100644 --- a/wled00/file.cpp +++ b/wled00/file.cpp @@ -460,7 +460,7 @@ static const uint8_t *getPresetCache(size_t &size) { if ((presetsModifiedTime != presetsCachedTime) || (presetsCachedValidate != cacheInvalidate)) { if (presetsCached) { - free(presetsCached); + p_free(presetsCached); presetsCached = nullptr; } } @@ -475,7 +475,7 @@ static const uint8_t *getPresetCache(size_t &size) { presetsCachedTime = presetsModifiedTime; presetsCachedValidate = cacheInvalidate; presetsCachedSize = 0; - presetsCached = (uint8_t*)ps_malloc(file.size() + 1); + presetsCached = (uint8_t*)p_malloc(file.size() + 1); if (presetsCached) { presetsCachedSize = file.size(); file.read(presetsCached, presetsCachedSize); @@ -506,14 +506,16 @@ void invalidateFileNameCache() { // reset "file not found" cache haveICOFile = true; haveCpalFile = true; - #if defined(BOARD_HAS_PSRAM) && (defined(WLED_USE_PSRAM) || defined(WLED_USE_PSRAM_JSON)) + #if (defined(BOARD_HAS_PSRAM) || ESP_IDF_VERSION_MAJOR > 3) && (defined(WLED_USE_PSRAM) || defined(WLED_USE_PSRAM_JSON)) // WLEDMM hack to clear presets.json cache - size_t dummy; - unsigned long realpresetsTime = presetsModifiedTime; - presetsModifiedTime = toki.second(); // pretend we have changes - (void) getPresetCache(dummy); // clear presets.json cache - presetsModifiedTime = realpresetsTime; // restore correct value -#endif + if (psramFound()) { + size_t dummy; + unsigned long realpresetsTime = presetsModifiedTime; + presetsModifiedTime = toki.second(); // pretend we have changes + (void) getPresetCache(dummy); // clear presets.json cache + presetsModifiedTime = realpresetsTime; // restore correct value + } + #endif //USER_PRINTLN("WS FileRead cache cleared"); } diff --git a/wled00/json.cpp b/wled00/json.cpp index 1b8b0fea3f..f41c4e5c29 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -1133,6 +1133,8 @@ void serializeInfo(JsonObject root) #if defined(ARDUINO_ARCH_ESP32) root[F("freestack")] = uxTaskGetStackHighWaterMark(NULL); //WLEDMM root[F("minfreeheap")] = ESP.getMinFreeHeap(); + auto maxFreeBlock = getContiguousFreeHeap(); + root[F("maxalloc")] = maxFreeBlock; // for upstream WLED compatibility #endif #if defined(ARDUINO_ARCH_ESP32) #if defined(BOARD_HAS_PSRAM) || (ESP_IDF_VERSION_MAJOR > 3) // V4 can auto-detect PSRAM diff --git a/wled00/presets.cpp b/wled00/presets.cpp index e675b63b5f..c3fe1e5ccd 100644 --- a/wled00/presets.cpp +++ b/wled00/presets.cpp @@ -77,16 +77,11 @@ static void doSaveState() { */ #if defined(ARDUINO_ARCH_ESP32) if (!persist) { - if (tmpRAMbuffer!=nullptr) free(tmpRAMbuffer); + if (tmpRAMbuffer!=nullptr) p_free(tmpRAMbuffer); size_t len = measureJson(*fileDoc) + 1; DEBUG_PRINTLN(len); // if possible use SPI RAM on ESP32 - #if defined(BOARD_HAS_PSRAM) && (defined(WLED_USE_PSRAM) || defined(WLED_USE_PSRAM_JSON)) // WLEDMM - if (psramFound()) - tmpRAMbuffer = (char*) ps_malloc(len); - else - #endif - tmpRAMbuffer = (char*) malloc(len); + tmpRAMbuffer = (char*) p_malloc(len); if (tmpRAMbuffer!=nullptr) { serializeJson(*fileDoc, tmpRAMbuffer, len); } else { @@ -298,7 +293,7 @@ void handlePresets() #if defined(ARDUINO_ARCH_ESP32) //Aircoookie recommended not to delete buffer if (tmpPreset==255 && tmpRAMbuffer!=nullptr) { - free(tmpRAMbuffer); + p_free(tmpRAMbuffer); tmpRAMbuffer = nullptr; } #endif diff --git a/wled00/src/dependencies/json/AsyncJson-v6.h b/wled00/src/dependencies/json/AsyncJson-v6.h index 32ac546077..ca5086f7f8 100644 --- a/wled00/src/dependencies/json/AsyncJson-v6.h +++ b/wled00/src/dependencies/json/AsyncJson-v6.h @@ -15,6 +15,20 @@ #include "ArduinoJson-v6.h" #include +// global WLED memory functions (util.cpp) +extern "C" { + // 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); + void d_free(void *ptr); + // 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); + void p_free(void *ptr); +} + #ifdef ESP8266 #define DYNAMIC_JSON_DOCUMENT_SIZE 8192 #else @@ -154,7 +168,7 @@ class AsyncCallbackJsonWebHandler: public AsyncWebHandler { if (_onRequest) { _contentLength = total; if (total > 0 && request->_tempObject == NULL && (int)total < _maxContentLength) { - request->_tempObject = malloc(total); + request->_tempObject = d_malloc(total); } if (request->_tempObject != NULL) { memcpy((uint8_t*)(request->_tempObject) + index, data, len); diff --git a/wled00/src/dependencies/time/DateStrings.cpp b/wled00/src/dependencies/time/DateStrings.cpp index 3eccff3e75..f0e2aeda00 100644 --- a/wled00/src/dependencies/time/DateStrings.cpp +++ b/wled00/src/dependencies/time/DateStrings.cpp @@ -12,6 +12,9 @@ #if defined(__AVR__) #include #else +#if defined(ARDUINO_ARCH_ESP32) +#include +#else // for compatiblity with Arduino Due and Teensy 3.0 and maybe others? #define PROGMEM #define PGM_P const char * @@ -19,6 +22,7 @@ #define pgm_read_word(addr) (*(const unsigned char **)(addr)) #define strcpy_P(dest, src) strcpy((dest), (src)) #endif +#endif #include // for strcpy_P or strcpy #include "TimeLib.h" @@ -27,43 +31,43 @@ static char buffer[dt_MAX_STRING_LEN+1]; // must be big enough for longest string and the terminating null -const char monthStr0[] PROGMEM = ""; -const char monthStr1[] PROGMEM = "January"; -const char monthStr2[] PROGMEM = "February"; -const char monthStr3[] PROGMEM = "March"; -const char monthStr4[] PROGMEM = "April"; -const char monthStr5[] PROGMEM = "May"; -const char monthStr6[] PROGMEM = "June"; -const char monthStr7[] PROGMEM = "July"; -const char monthStr8[] PROGMEM = "August"; -const char monthStr9[] PROGMEM = "September"; -const char monthStr10[] PROGMEM = "October"; -const char monthStr11[] PROGMEM = "November"; -const char monthStr12[] PROGMEM = "December"; +static const char monthStr0[] PROGMEM = ""; +static const char monthStr1[] PROGMEM = "January"; +static const char monthStr2[] PROGMEM = "February"; +static const char monthStr3[] PROGMEM = "March"; +static const char monthStr4[] PROGMEM = "April"; +static const char monthStr5[] PROGMEM = "May"; +static const char monthStr6[] PROGMEM = "June"; +static const char monthStr7[] PROGMEM = "July"; +static const char monthStr8[] PROGMEM = "August"; +static const char monthStr9[] PROGMEM = "September"; +static const char monthStr10[] PROGMEM = "October"; +static const char monthStr11[] PROGMEM = "November"; +static const char monthStr12[] PROGMEM = "December"; -const PROGMEM char * const PROGMEM monthNames_P[] = +static const PROGMEM char * const PROGMEM monthNames_P[] = { monthStr0,monthStr1,monthStr2,monthStr3,monthStr4,monthStr5,monthStr6, monthStr7,monthStr8,monthStr9,monthStr10,monthStr11,monthStr12 }; -const char monthShortNames_P[] PROGMEM = "ErrJanFebMarAprMayJunJulAugSepOctNovDec"; +static const char monthShortNames_P[] PROGMEM = "ErrJanFebMarAprMayJunJulAugSepOctNovDec"; -const char dayStr0[] PROGMEM = "Err"; -const char dayStr1[] PROGMEM = "Sunday"; -const char dayStr2[] PROGMEM = "Monday"; -const char dayStr3[] PROGMEM = "Tuesday"; -const char dayStr4[] PROGMEM = "Wednesday"; -const char dayStr5[] PROGMEM = "Thursday"; -const char dayStr6[] PROGMEM = "Friday"; -const char dayStr7[] PROGMEM = "Saturday"; +static const char dayStr0[] PROGMEM = "Err"; +static const char dayStr1[] PROGMEM = "Sunday"; +static const char dayStr2[] PROGMEM = "Monday"; +static const char dayStr3[] PROGMEM = "Tuesday"; +static const char dayStr4[] PROGMEM = "Wednesday"; +static const char dayStr5[] PROGMEM = "Thursday"; +static const char dayStr6[] PROGMEM = "Friday"; +static const char dayStr7[] PROGMEM = "Saturday"; -const PROGMEM char * const PROGMEM dayNames_P[] = +static const PROGMEM char * const PROGMEM dayNames_P[] = { dayStr0,dayStr1,dayStr2,dayStr3,dayStr4,dayStr5,dayStr6,dayStr7 }; -const char dayShortNames_P[] PROGMEM = "ErrSunMonTueWedThuFriSat"; +static const char dayShortNames_P[] PROGMEM = "ErrSunMonTueWedThuFriSat"; /* functions to return date strings */ diff --git a/wled00/util.cpp b/wled00/util.cpp index 06e32dfa3c..db2bf99b1e 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -514,7 +514,7 @@ um_data_t* simulateSound(uint8_t simulationId) if (!um_data) { //claim storage for arrays - fftResult = (uint8_t *)malloc(sizeof(uint8_t) * 16); + fftResult = (uint8_t *)d_malloc(sizeof(uint8_t) * 16); // initialize um_data pointer structure // NOTE!!! @@ -709,6 +709,254 @@ int32_t hw_random(int32_t lowerlimit, int32_t upperlimit) { return hw_random(diff) + lowerlimit; } + +// 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 + // note: ESP826 needs very little contiguous heap for webserver, checking total free heap works better + if (getFreeHeapSize() < MIN_HEAP_SIZE) { + free(buffer); + return nullptr; + } + return buffer; +} + +void *d_malloc(size_t size) { + // 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_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); +} + +void d_free(void *ptr) { free(ptr); } + +void *p_malloc(size_t size) { return d_malloc(size); } +void *p_calloc(size_t count, size_t size) { return d_calloc(count, size); } +void *p_realloc_malloc(void *ptr, size_t size) { return d_realloc_malloc(ptr, size); } +void p_free(void *ptr) { free(ptr); } + +#else + +static size_t lastHeap = 65535; +static size_t lastMinHeap = 65535; +inline static void d_measureHeap(void) { +#ifdef WLEDMM_FILEWAIT // only when we don't use the RMTHI driver + if (!strip.isUpdating()) // skip measurement while sending out LEDs - prevents flickering +#endif + { + lastHeap = heap_caps_get_free_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + lastMinHeap = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + } +} + +size_t d_measureContiguousFreeHeap(void) { + d_measureHeap(); + return lastMinHeap; +} // returns largest contiguous free block // WLEDMM may glitch, too + +size_t d_measureFreeHeap(void) { + d_measureHeap(); + return lastHeap; +} // returns free heap (ESP.getFreeHeap() can include other memory types) // WLEDMM can cause LED glitches + +// early check: reject DRAM request if remaining heap possibly gets too low (avoids heap fragmentation) +static inline bool isOkForDRAMHeap(size_t amount) { +#if !defined(BOARD_HAS_PSRAM) || (ESP_IDF_VERSION_MAJOR > 3) + if (!psramFound()) return true; // No PSRAM -> no opther options, so let's try + size_t avail = getContiguousFreeHeap(); + if ((amount < avail) && (avail - amount > MIN_HEAP_SIZE)) return true; + else { + DEBUG_PRINTF("* isOkForDRAMHeap() rejected allocation (%u bytes, %u available) !\n", amount, avail); + return(false); + } + #else + return true; // No PSRAM -> no opther options + #endif +} + +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 (buffer == nullptr) return buffer; // early exit, nothing to check + if ((uintptr_t)buffer > SOC_DRAM_LOW && (uintptr_t)buffer < SOC_DRAM_HIGH && getContiguousFreeHeap() < MIN_HEAP_SIZE) { + free(buffer); + USER_PRINTLN("* validateFreeHeap() rejected allocation !"); + return nullptr; + } + return buffer; +} + +#ifdef BOARD_HAS_PSRAM +#define RTC_RAM_THRESHOLD 1024 // use RTC RAM for allocations smaller than this size +#else +#define RTC_RAM_THRESHOLD (psramFound() ? 1024 : 65535) // without PSRAM, allow any size into RTC RAM (useful especially on S2 without PSRAM) +#endif + +void *d_malloc(size_t size) { + void *buffer = nullptr; + #if !defined(CONFIG_IDF_TARGET_ESP32) + // 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 or if DRAM is running low to improve fragmentation + if (size <= RTC_RAM_THRESHOLD || getContiguousFreeHeap() < 2*MIN_HEAP_SIZE + size) { + //buffer = heap_caps_malloc_prefer(size, 2, MALLOC_CAP_RTCRAM, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + buffer = heap_caps_malloc(size, MALLOC_CAP_RTCRAM | MALLOC_CAP_8BIT); + DEBUG_PRINTF("* d_malloc() trying RTCRAM (%u bytes) - %s.\n", size, buffer?"success":"fail"); + } + if ((buffer == nullptr) && isOkForDRAMHeap(size)) // no RTC RAM allocation: use DRAM + buffer = heap_caps_malloc(size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); // allocate in any available heap memory + #else + buffer = heap_caps_malloc(size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); // allocate in any available heap memory + #endif + + buffer = validateFreeHeap(buffer); // make sure there is enough free heap left + #if defined(BOARD_HAS_PSRAM) || (ESP_IDF_VERSION_MAJOR > 3) // WLEDMM always try PSRAM (auto-detected) + if (!buffer && psramFound()) { + DEBUG_PRINTF("* d_malloc() using PSRAM(%u bytes).\n", size); + return heap_caps_malloc_prefer(size, 2, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT, MALLOC_CAP_DEFAULT); // DRAM failed,try PSRAM if available + } else + #endif + if (!buffer) { USER_PRINTF("* d_malloc() failed (%u bytes) !\n", size); } + return buffer; +} + +void *d_calloc(size_t count, size_t size) { + // similar to d_malloc but uses heap_caps_calloc + void *buffer = nullptr; + #if !defined(CONFIG_IDF_TARGET_ESP32) + if ((size * count) <= RTC_RAM_THRESHOLD || getContiguousFreeHeap() < 2*MIN_HEAP_SIZE + (size * count)) { + //buffer = heap_caps_calloc_prefer(count, size, 2, MALLOC_CAP_RTCRAM, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + buffer = heap_caps_calloc(count, size, MALLOC_CAP_RTCRAM | MALLOC_CAP_8BIT); + DEBUG_PRINTF("* d_calloc() trying RTCRAM (%u bytes) - %s.\n", size*count, buffer?"success":"fail"); + } + if ((buffer == nullptr) && isOkForDRAMHeap(size*count)) // no RTC RAM allocation: use DRAM + buffer = heap_caps_calloc(count, size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); // allocate in any available heap memory + #else + buffer = heap_caps_calloc(count, size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); // allocate in any available heap memory + #endif + + buffer = validateFreeHeap(buffer); // make sure there is enough free heap left + #if defined(BOARD_HAS_PSRAM) || (ESP_IDF_VERSION_MAJOR > 3) // WLEDMM always try PSRAM (auto-detected) + if (!buffer && psramFound()) { + DEBUG_PRINTF("* d_calloc() using PSRAM (%u bytes).\n", size*count); + return heap_caps_calloc_prefer(count, size, 2, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT, MALLOC_CAP_DEFAULT); // DRAM failed,try PSRAM if available + } else + #endif + if (!buffer) { USER_PRINTF("* d_calloc() failed (%u bytes) !\n", size*count); } + 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) { + #if defined(BOARD_HAS_PSRAM) || (ESP_IDF_VERSION_MAJOR > 3) // WLEDMM always try PSRAM (auto-detected) + void *buffer = heap_caps_realloc_prefer(ptr, size, 3, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT, MALLOC_CAP_DEFAULT); + #else + void *buffer = heap_caps_realloc(ptr, size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + #endif + 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_free(void *ptr) { heap_caps_free(ptr); } +void p_free(void *ptr) { heap_caps_free(ptr); } + +#if defined(BOARD_HAS_PSRAM) || (ESP_IDF_VERSION_MAJOR > 3) // V4 can auto-detect PSRAM +// p_xalloc: prefer PSRAM, use DRAM as fallback +void *p_malloc(size_t size) { + if (!psramFound()) return d_malloc(size); + void *buffer = heap_caps_malloc_prefer(size, 3, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT, MALLOC_CAP_DEFAULT); + return validateFreeHeap(buffer); +} + +void *p_calloc(size_t count, size_t size) { + // similar to p_malloc but uses heap_caps_calloc + if (!psramFound()) return d_calloc(count, size); + void *buffer = heap_caps_calloc_prefer(count, size, 3, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT, MALLOC_CAP_DEFAULT); + return validateFreeHeap(buffer); +} + +// realloc with malloc fallback, original buffer is freed if realloc fails but not copied! +void *p_realloc_malloc(void *ptr, size_t size) { + if (!psramFound()) return d_realloc_malloc(ptr, size); + void *buffer = heap_caps_realloc_prefer(ptr, size, 3, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT, MALLOC_CAP_DEFAULT); + if (buffer) return buffer; // realloc successful + p_free(ptr); // free old buffer if realloc failed + return p_malloc(size); // fallback to malloc +} + +#else // NO PSRAM support -> fall back to DRAM +void *p_malloc(size_t size) { return d_malloc(size); } +void *p_calloc(size_t count, size_t size) { return d_calloc(count, size); } +void *p_realloc_malloc(void *ptr, size_t size) { return d_realloc_malloc(ptr, size); } +#endif +#endif + + +#if 0 // WLEDMM not used yet +// 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; + #if 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 + + return buffer; +} +#endif + + // Platform-agnostic SHA1 computation from String input String computeSHA1(const String& input) { #ifdef ESP8266 diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 83eaee95c1..c3ee3e5c81 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -358,14 +358,28 @@ void WLED::loop() DEBUG_PRINT(F("Name: ")); DEBUG_PRINTLN(serverDescription); DEBUG_PRINT(F("Runtime: ")); DEBUG_PRINTLN(millis()); DEBUG_PRINT(F("Unix time: ")); toki.printTime(toki.getTime()); - DEBUG_PRINT(F("Free heap : ")); DEBUG_PRINTLN(ESP.getFreeHeap()); - DEBUG_PRINT(F("Free heap: ")); DEBUG_PRINTLN(ESP.getFreeHeap()); + DEBUG_PRINT(F("Free heap: ")); DEBUG_PRINTLN(getFreeHeapSize()); //WLEDMM #ifdef ARDUINO_ARCH_ESP32 DEBUG_PRINT(F("Avail heap: ")); DEBUG_PRINTLN(ESP.getMaxAllocHeap()); DEBUG_PRINTF("%s min free stack %d\n", pcTaskGetTaskName(NULL), uxTaskGetStackHighWaterMark(NULL)); //WLEDMM #endif #if defined(ARDUINO_ARCH_ESP32) + // 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); + #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 defined(BOARD_HAS_PSRAM) || (ESP_IDF_VERSION_MAJOR > 3) // V4 can auto-detect PSRAM if (psramFound()) { //DEBUG_PRINT(F("Total PSRAM: ")); DEBUG_PRINT(ESP.getPsramSize()/1024); DEBUG_PRINTLN("kB"); @@ -649,7 +663,7 @@ void WLED::setup() DEBUG_PRINT(F("esp8266 ")); DEBUG_PRINTLN(ESP.getCoreVersion()); #endif - DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(ESP.getFreeHeap()); + DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(getFreeHeapSize()); #ifdef ARDUINO_ARCH_ESP32 #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0) // unfortunately not available in older framework versions DEBUG_PRINT(F("\nArduino max stack ")); DEBUG_PRINTLN(getArduinoLoopTaskStackSize()); @@ -752,7 +766,7 @@ void WLED::setup() DEBUG_PRINTLN(F("Registering usermods ...")); registerUsermods(); - DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(ESP.getFreeHeap()); + DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(getFreeHeapSize()); #ifdef ARDUINO_ARCH_ESP32 DEBUG_PRINTF("%s min free stack %d\n", pcTaskGetTaskName(NULL), uxTaskGetStackHighWaterMark(NULL)); //WLEDMM #endif @@ -802,12 +816,12 @@ void WLED::setup() DEBUG_PRINTLN(F("Initializing strip")); beginStrip(); - DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(ESP.getFreeHeap()); + DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(getFreeHeapSize()); USER_PRINTLN(F("\nUsermods setup ...")); userSetup(); usermods.setup(); - DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(ESP.getFreeHeap()); + DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(getFreeHeapSize()); if (strcmp(clientSSID, DEFAULT_CLIENT_SSID) == 0) showWelcomePage = true; @@ -871,7 +885,7 @@ void WLED::setup() // HTTP server page init DEBUG_PRINTLN(F("initServer")); initServer(); - DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(ESP.getFreeHeap()); + DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(getFreeHeapSize()); #ifdef ARDUINO_ARCH_ESP32 DEBUG_PRINT(pcTaskGetTaskName(NULL)); DEBUG_PRINT(F(" free stack ")); DEBUG_PRINTLN(uxTaskGetStackHighWaterMark(NULL)); #endif @@ -956,7 +970,7 @@ void WLED::setup() USER_PRINTLN(F("\n")); #endif - USER_PRINT(F("Free heap ")); USER_PRINTLN(ESP.getFreeHeap());USER_PRINTLN(); + USER_PRINT(F("Free heap ")); USER_PRINTLN(getFreeHeapSize());USER_PRINTLN(); // WLEDMM force initial calculation of gamma correction LUT if ((gammaCorrectVal < 0.999f) || (gammaCorrectVal > 3.0f)) calcGammaTable(1.0f); // no gamma => create linear LUT @@ -1335,13 +1349,22 @@ void WLED::handleConnection() } static unsigned retryCount = 0; // WLEDMM - #ifdef ARDUINO_ARCH_ESP32 + #ifdef ARDUINO_ARCH_ESP32 // reconnect WiFi to clear stale allocations if heap gets too low - if ((!strip.isUpdating()) && (now - heapTime > 5000)) { // WLEDMM: updated with better logic for small heap available by block, not total. // WLEDMM trying to use a moment when the strip is idle + if ((now - heapTime > 5000) && !strip.isUpdating()) { // WLEDMM: updated with better logic for small heap available by block, not total. // WLEDMM trying to use a moment when the strip is idle + #ifdef WLEDMM_FILEWAIT // only when we don't use the RMTHI driver + // calling getContiguousFreeHeap() during led update causes glitches on C3 + // this can (probably) be removed once RMT driver for C3 is fixed + unsigned t0 = millis(); + while (strip.isUpdating() && (millis() - t0 < 15)) delay(1); // be nice, but not too nice. Waits up to 15ms + #endif + #if defined(ARDUINO_ARCH_ESP32S2) || defined(WLED_ENABLE_HUB75MATRIX) - uint32_t heap = ESP.getFreeHeap(); // WLEDMM works better on -S2 + //uint32_t heap = ESP.getFreeHeap(); // WLEDMM works better on -S2; also avoid too-early panic on HUB75 builds + uint32_t heap = getFreeHeapSize(); // WLEDMM works better on -S2 #else - uint32_t heap = heap_caps_get_largest_free_block(0x1800); // WLEDMM: This is a better metric for free heap. + //uint32_t heap = heap_caps_get_largest_free_block(0x1800); // WLEDMM: This is a better metric for free heap. + uint32_t heap = getContiguousFreeHeap(); // WLEDMM: This is a better metric for free heap. #endif if (heap < MIN_HEAP_SIZE && lastHeap < MIN_HEAP_SIZE) { if (retryCount < 5) { // WLEDMM avoid repeated disconnects diff --git a/wled00/wled.h b/wled00/wled.h index b53034e539..e4475e06d4 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -204,17 +204,29 @@ #undef ALL_JSON_TO_PSRAM #define ALL_JSON_TO_PSRAM +// global WLED memory functions (util.cpp) +extern "C" { + // 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); + void d_free(void *ptr); + // 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); + void p_free(void *ptr); +} + struct PSRAM_Allocator { void* allocate(size_t size) { - if (psramFound()) return ps_malloc(size); // use PSRAM if it exists - else return malloc(size); // fallback + return p_malloc(size); // use PSRAM if it exists } void* reallocate(void* ptr, size_t new_size) { - if (psramFound()) return ps_realloc(ptr, new_size); // use PSRAM if it exists - else return realloc(ptr, new_size); // fallback + return p_realloc_malloc(ptr, new_size); // use PSRAM if it exists } void deallocate(void* pointer) { - free(pointer); + p_free(pointer); } }; using PSRAMDynamicJsonDocument = BasicJsonDocument;