diff --git a/usermods/artifx/arti.h b/usermods/artifx/arti.h index 21eb649fbb..3f05286bd8 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); + 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'; @@ -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..3a72bf8ed5 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -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 @@ -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 @@ -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) { @@ -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 // @@ -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 @@ -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 @@ -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 @@ -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 @@ -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")); @@ -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")); } } 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..255c241681 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 @@ -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; @@ -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? @@ -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; } @@ -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; @@ -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 '[': @@ -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; diff --git a/wled00/FX.cpp b/wled00/FX.cpp index e9f578504c..e6665eef7f 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -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(); 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..643a426b9a 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -57,6 +57,8 @@ uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, byte #define DEBUGOUT Serial #endif +#include "util.h" + #ifdef WLED_DEBUG #ifndef ESP8266 #include @@ -502,7 +504,7 @@ BusNetwork::BusNetwork(BusConfig &bc, const ColorOrderMap &com) : Bus(bc.type, b } _UDPchannels = _rgbw ? 4 : 3; #ifdef ESP32 - _data = (byte*) heap_caps_calloc_prefer((bc.count * _UDPchannels)+15, sizeof(byte), 3, MALLOC_CAP_DEFAULT, MALLOC_CAP_SPIRAM); + _data = (byte*) heap_caps_calloc_prefer((bc.count * _UDPchannels)+15, sizeof(byte), 3, MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT, MALLOC_CAP_DEFAULT|MALLOC_CAP_8BIT, MALLOC_CAP_SPIRAM|MALLOC_CAP_8BIT); #else _data = (byte*) calloc((bc.count * _UDPchannels)+15, sizeof(byte)); #endif @@ -592,7 +594,11 @@ uint8_t BusNetwork::getPins(uint8_t* pinArray) const { void BusNetwork::cleanup() { _type = I_NONE; _valid = false; - if (_data != nullptr) free(_data); + #ifdef ESP32 + if (_data != nullptr) heap_caps_free(_data); + #else + if (_data != nullptr) free(_data); + #endif _data = nullptr; _len = 0; } @@ -935,7 +941,8 @@ 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(); + lastHeap = ESP.getFreeHeap(); + DEBUG_PRINT(F("Free heap: ")); DEBUG_PRINTLN(lastHeap); // check if we can re-use the existing display driver if (activeDisplay) { @@ -1030,20 +1037,16 @@ BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWh display->clearScreen(); // initially clear the screen buffer USER_PRINTLN("MatrixPanel_I2S_DMA clear ok"); - if (_ledBuffer) free(_ledBuffer); // should not happen - if (_ledsDirty) free(_ledsDirty); // should not happen + if (_ledBuffer) p_free(_ledBuffer); // should not happen + if (_ledsDirty) d_free(_ledsDirty); // should not happen - _ledsDirty = (byte*) malloc(getBitArrayBytes(_len)); // create LEDs dirty bits + _ledsDirty = (byte*) d_malloc(getBitArrayBytes(_len)); // create LEDs dirty bits if (_ledsDirty) setBitArray(_ledsDirty, _len, false); // reset dirty bits - #if defined(CONFIG_IDF_TARGET_ESP32S3) && CONFIG_SPIRAM_MODE_OCT && defined(BOARD_HAS_PSRAM) && (defined(WLED_USE_PSRAM) || defined(WLED_USE_PSRAM_JSON)) - if (psramFound()) { - _ledBuffer = (CRGB*) ps_calloc(_len, sizeof(CRGB)); // create LEDs buffer (initialized to BLACK) - } else { - _ledBuffer = (CRGB*) calloc(_len, sizeof(CRGB)); // create LEDs buffer (initialized to BLACK) - } + #if defined(CONFIG_IDF_TARGET_ESP32S3) && CONFIG_SPIRAM_MODE_OCT && defined(BOARD_HAS_PSRAM) + _ledBuffer = (CRGB*) p_calloc(_len, sizeof(CRGB)); // create LEDs buffer (initialized to BLACK) #else - _ledBuffer = (CRGB*) calloc(_len, sizeof(CRGB)); // create LEDs buffer (initialized to BLACK) + _ledBuffer = (CRGB*) d_calloc(_len, sizeof(CRGB)); // create LEDs buffer (initialized to BLACK) #endif } @@ -1227,8 +1230,8 @@ void BusHub75Matrix::cleanup() { #endif if (instanceCount > 0) instanceCount--; - if (_ledBuffer != nullptr) free(_ledBuffer); _ledBuffer = nullptr; - if (_ledsDirty != nullptr) free(_ledsDirty); _ledsDirty = nullptr; + if (_ledBuffer != nullptr) p_free(_ledBuffer); _ledBuffer = nullptr; + if (_ledsDirty != nullptr) d_free(_ledsDirty); _ledsDirty = nullptr; } void BusHub75Matrix::deallocatePins() { 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..e96efd837b 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -425,71 +425,10 @@ void userConnected(); void userLoop(); //util.cpp -#define inoise8 perlin8 // fastled legacy alias -#define inoise16 perlin16 // fastled legacy alias -#define hex2int(a) (((a)>='0' && (a)<='9') ? (a)-'0' : ((a)>='A' && (a)<='F') ? (a)-'A'+10 : ((a)>='a' && (a)<='f') ? (a)-'a'+10 : 0) -int __attribute__((pure)) getNumVal(const String* req, uint16_t pos); -void parseNumber(const char* str, byte* val, byte minv=0, byte maxv=255); -bool getVal(JsonVariant elem, byte* val, byte minv=0, byte maxv=255); -bool updateVal(const char* req, const char* key, byte* val, byte minv=0, byte maxv=255); -void oappendUseDeflate(bool OnOff); // enable / disable string squeezing -bool oappend(const char* txt); // append new c string to temp buffer efficiently -bool oappendi(int i); // append new number to temp buffer efficiently -void sappend(char stype, const char* key, int val); -void sappends(char stype, const char* key, char* val); -void prepareHostname(char* hostname); -bool isAsterisksOnly(const char* str, byte maxLen) __attribute__((pure)); -bool requestJSONBufferLock(uint8_t module=255, unsigned timeoutMS = 1800); -void releaseJSONBufferLock(); -uint8_t extractModeName(uint8_t mode, const char *src, char *dest, uint8_t maxLen); -uint8_t extractModeSlider(uint8_t mode, uint8_t slider, char *dest, uint8_t maxLen, uint8_t *var = nullptr); -int16_t extractModeDefaults(uint8_t mode, const char *segVar); -void checkSettingsPIN(const char *pin); -uint16_t __attribute__((pure)) crc16(const unsigned char* data_p, size_t length); // WLEDMM: added attribute pure -String computeSHA1(const String& input); -String getDeviceId(); -uint16_t beatsin88_t(accum88 beats_per_minute_88, uint16_t lowest = 0, uint16_t highest = 65535, uint32_t timebase = 0, uint16_t phase_offset = 0); -uint16_t beatsin16_t(accum88 beats_per_minute, uint16_t lowest = 0, uint16_t highest = 65535, uint32_t timebase = 0, uint16_t phase_offset = 0); -uint8_t beatsin8_t(accum88 beats_per_minute, uint8_t lowest = 0, uint8_t highest = 255, uint32_t timebase = 0, uint8_t phase_offset = 0); - +#include "util.h" // WLEDMM um_data_t* simulateSound(uint8_t simulationId); // WLEDMM enumerateLedmaps(); moved to FX.h -uint8_t get_random_wheel_index(uint8_t pos); -CRGB getCRGBForBand(int x, uint8_t *fftResult, int pal); //WLEDMM netmindz ar palette -char *cleanUpName(char *in); // to clean up a name that was read from file -uint32_t hashInt(uint32_t s); -int32_t perlin1D_raw(uint32_t x, bool is16bit = false); -int32_t perlin2D_raw(uint32_t x, uint32_t y, bool is16bit = false); -int32_t perlin3D_raw(uint32_t x, uint32_t y, uint32_t z, bool is16bit = false); -uint16_t perlin16(uint32_t x); -uint16_t perlin16(uint32_t x, uint32_t y); -uint16_t perlin16(uint32_t x, uint32_t y, uint32_t z); -uint8_t perlin8(uint16_t x); -uint8_t perlin8(uint16_t x, uint16_t y); -uint8_t perlin8(uint16_t x, uint16_t y, uint16_t z); - -// fast (true) random numbers using hardware RNG, all functions return values in the range lowerlimit to upperlimit-1 -// note: for true random numbers with high entropy, do not call faster than every 200ns (5MHz) -// tests show it is still highly random reading it quickly in a loop (better than fastled PRNG) -// for 8bit and 16bit random functions: no limit check is done for best speed -// 32bit inputs are used for speed and code size, limits don't work if inverted or out of range -// inlining does save code size except for random(a,b) and 32bit random with limits -#ifdef ESP8266 -#define HW_RND_REGISTER RANDOM_REG32 -#else // ESP32 family -#include "soc/wdev_reg.h" -#define HW_RND_REGISTER REG_READ(WDEV_RND_REG) -#endif -#define random hw_random // replace arduino random() -inline uint32_t hw_random() { return HW_RND_REGISTER; }; -uint32_t hw_random(uint32_t upperlimit); // not inlined for code size -int32_t hw_random(int32_t lowerlimit, int32_t upperlimit); -inline uint16_t hw_random16() { return HW_RND_REGISTER; }; -inline uint16_t hw_random16(uint32_t upperlimit) { return (hw_random16() * upperlimit) >> 16; }; // input range 0-65535 (uint16_t) -inline int16_t hw_random16(int32_t lowerlimit, int32_t upperlimit) { int32_t range = upperlimit - lowerlimit; return lowerlimit + hw_random16(range); }; // signed limits, use int16_t ranges -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 + // RAII guard class for the JSON Buffer lock // Modeled after std::lock_guard 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..8fc0150649 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,6 +168,7 @@ class AsyncCallbackJsonWebHandler: public AsyncWebHandler { if (_onRequest) { _contentLength = total; if (total > 0 && request->_tempObject == NULL && (int)total < _maxContentLength) { + //request->_tempObject = d_malloc(total); // seems to cause instabilities on classic esp32 with PSRAM request->_tempObject = malloc(total); } if (request->_tempObject != NULL) { 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..39fee6d964 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -1,6 +1,8 @@ #include "wled.h" #include "fcn_declare.h" #include "const.h" +#include "util.h" + #ifdef ESP8266 #include "user_interface.h" // for bootloop detection #include // for SHA1 on ESP8266 @@ -8,6 +10,7 @@ #include "mbedtls/sha1.h" // for SHA1 on ESP32 #include "esp_efuse.h" #include "esp_adc_cal.h" +#include "esp_heap_caps.h" #endif //helper to get int value at a position in string @@ -515,6 +518,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); // might potentially fail with nullptr. We don't have a solution or fallback for this case. // initialize um_data pointer structure // NOTE!!! @@ -709,6 +713,307 @@ 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_realloc_malloc_nofree(void *ptr, size_t size) { + void *buffer = realloc(ptr, size); + //buffer = validateFreeHeap(buffer); violates contract + return buffer; // realloc done +} + + +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_realloc_malloc_nofree(void *ptr, size_t size) { return d_realloc_malloc_nofree(ptr, size); } +void p_free(void *ptr) { free(ptr); } + +#else + +static size_t lastHeap = 65535; +static size_t lastMinHeap = 65535; +WLED_create_spinlock(heapStatusMux); // to prevent race conditions on lastHeap and lastMinHeap + +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 + { + size_t newlastHeap = heap_caps_get_free_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + size_t newlastMinHeap = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + portENTER_CRITICAL(&heapStatusMux); // atomic operation + lastHeap = newlastHeap; + lastMinHeap = newlastMinHeap; + portEXIT_CRITICAL(&heapStatusMux); + } +} + +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 to avoid heap fragmentation: when PSRAM is available, we reject DRAM request if remaining heap possibly gets too low. +// This check is not exact - in case of strong heap fragmentation, there might be multiple chunks of similar sizes. +// However it still improves stability in low-heap situations (tested). +static inline bool isOkForDRAMHeap(size_t amount) { +#if defined(BOARD_HAS_PSRAM) || (ESP_IDF_VERSION_MAJOR > 3) + // if (!psramFound()) return true; // No PSRAM -> accept everything // disabled - increases fragmentation + size_t avail = d_measureContiguousFreeHeap(); + if ((amount < avail) && (avail - amount > MIN_HEAP_SIZE)) return true; + else { + DEBUG_PRINTF("* isOkForDRAMHeap() DRAM allocation rejected (%u bytes requested, %u available) !\n", amount, avail); + return(false); + } + #else + return true; // No PSRAM -> no other 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) { + size_t avail = d_measureContiguousFreeHeap(); + if (avail < MIN_HEAP_SIZE) { + heap_caps_free(buffer); + USER_PRINTF("* validateFreeHeap() DRAM allocation rejected - largest remaining chunk too small (%u bytes).\n", avail); + d_measureHeap(); // update statistics after free + 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"); + } + #endif + + 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 + 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 + } + #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"); + } + #endif + + 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 + 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 + } + #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 + void *bufferNew = buffer; + buffer = validateFreeHeap(buffer); + if (buffer) return buffer; // realloc successful + if (!bufferNew) d_free(ptr); // free old buffer if realloc failed; don't double-free an invalid pointer if min heap was exceeded + DEBUG_PRINTF("* d_realloc_malloc(): realloc failed (%u bytes), trying malloc.\n", size); + return d_malloc(size); // fallback to malloc +} + +// realloc without malloc fallback, original buffer not changed if realloc fails +void *d_realloc_malloc_nofree(void *ptr, size_t size) { + DEBUG_PRINTF("* d_realloc_malloc_nofree() realloc to %u bytes requested.\n", size); + void* buffer = nullptr; + #if (ESP_IDF_VERSION_MAJOR > 3) + // only basic sanity checks possible: prefer PSRAM if DRAM is low + size_t oldSize = ptr ? heap_caps_get_allocated_size(ptr) : 0; // heap_caps_get_allocated_size crashes on nullptr + size_t delta = (size > oldSize) ? (size - oldSize) : 0; + if ((delta == 0) || isOkForDRAMHeap(delta)) { // prefer DRAM + buffer = heap_caps_realloc_prefer(ptr, size, 3, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT, MALLOC_CAP_DEFAULT); + } else { // prefer PSRAM + buffer = heap_caps_realloc_prefer(ptr, size, 3, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT, MALLOC_CAP_DEFAULT); + } + #else + // V3 lacks heap_caps_get_allocated_size() -> no sanity check + buffer = heap_caps_realloc_prefer(ptr, size, 2, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT, MALLOC_CAP_DEFAULT); + #endif + //buffer = validateFreeHeap(buffer); // violates contract + if (!buffer) { USER_PRINTF("* d_realloc_malloc_nofree() failed (%u bytes) !\n", size); } + return buffer; +} + +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); + void *bufferNew = buffer; + buffer = validateFreeHeap(buffer); + if (buffer) return buffer; // realloc successful + if (!bufferNew) p_free(ptr); // free old buffer if realloc failed; don't double-free an invalid pointer if min heap was exceeded + return p_malloc(size); // fallback to malloc +} + +// realloc without malloc fallback, original buffer not changed if realloc fails +void *p_realloc_malloc_nofree(void *ptr, size_t size) { + if (!psramFound()) return d_realloc_malloc_nofree(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); + //buffer = validateFreeHeap(buffer); // violates contract + return buffer; +} + +#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); } +void *p_realloc_malloc_nofree(void *ptr, size_t size) { return d_realloc_malloc_nofree(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/util.h b/wled00/util.h new file mode 100644 index 0000000000..658adf3c10 --- /dev/null +++ b/wled00/util.h @@ -0,0 +1,132 @@ +#ifndef WLED_UTIL_H +#define WLED_UTIL_H + +// Library inclusions +#include +#define ARDUINOJSON_DECODE_UNICODE 0 +#include "src/dependencies/json/AsyncJson-v6.h" +#include "src/dependencies/json/ArduinoJson-v6.h" +#if !defined(FASTLED_VERSION) // pull in FastLED if we don't have it yet (we need the CRGB type) + #define USE_GET_MILLISECOND_TIMER + #define FASTLED_INTERNAL + #include +#endif + +/* + * functions exported by util.cpp + */ + +#define inoise8 perlin8 // fastled legacy alias +#define inoise16 perlin16 // fastled legacy alias +#define hex2int(a) (((a)>='0' && (a)<='9') ? (a)-'0' : ((a)>='A' && (a)<='F') ? (a)-'A'+10 : ((a)>='a' && (a)<='f') ? (a)-'a'+10 : 0) + +int __attribute__((pure)) getNumVal(const String* req, uint16_t pos); +void parseNumber(const char* str, byte* val, byte minv=0, byte maxv=255); +bool getVal(JsonVariant elem, byte* val, byte minv=0, byte maxv=255); +bool updateVal(const char* req, const char* key, byte* val, byte minv=0, byte maxv=255); +void oappendUseDeflate(bool OnOff); // enable / disable string squeezing +bool oappend(const char* txt); // append new c string to temp buffer efficiently +bool oappendi(int i); // append new number to temp buffer efficiently +void sappend(char stype, const char* key, int val); +void sappends(char stype, const char* key, char* val); +void prepareHostname(char* hostname); +bool isAsterisksOnly(const char* str, byte maxLen) __attribute__((pure)); + +bool requestJSONBufferLock(uint8_t module=255, unsigned timeoutMS = 1800); +void releaseJSONBufferLock(); + +uint8_t extractModeName(uint8_t mode, const char *src, char *dest, uint8_t maxLen); +uint8_t extractModeSlider(uint8_t mode, uint8_t slider, char *dest, uint8_t maxLen, uint8_t *var = nullptr); +int16_t extractModeDefaults(uint8_t mode, const char *segVar); + +void checkSettingsPIN(const char *pin); +uint16_t __attribute__((pure)) crc16(const unsigned char* data_p, size_t length); // WLEDMM: added attribute pure +String computeSHA1(const String& input); +String getDeviceId(); + +uint16_t beatsin88_t(accum88 beats_per_minute_88, uint16_t lowest = 0, uint16_t highest = 65535, uint32_t timebase = 0, uint16_t phase_offset = 0); +uint16_t beatsin16_t(accum88 beats_per_minute, uint16_t lowest = 0, uint16_t highest = 65535, uint32_t timebase = 0, uint16_t phase_offset = 0); +uint8_t beatsin8_t(accum88 beats_per_minute, uint8_t lowest = 0, uint8_t highest = 255, uint32_t timebase = 0, uint8_t phase_offset = 0); + +uint8_t get_random_wheel_index(uint8_t pos); +CRGB getCRGBForBand(int x, uint8_t *fftResult, int pal); //WLEDMM netmindz ar palette +char *cleanUpName(char *in); // to clean up a name that was read from file + +uint32_t hashInt(uint32_t s); +int32_t perlin1D_raw(uint32_t x, bool is16bit = false); +int32_t perlin2D_raw(uint32_t x, uint32_t y, bool is16bit = false); +int32_t perlin3D_raw(uint32_t x, uint32_t y, uint32_t z, bool is16bit = false); +uint16_t perlin16(uint32_t x); +uint16_t perlin16(uint32_t x, uint32_t y); +uint16_t perlin16(uint32_t x, uint32_t y, uint32_t z); +uint8_t perlin8(uint16_t x); +uint8_t perlin8(uint16_t x, uint16_t y); +uint8_t perlin8(uint16_t x, uint16_t y, uint16_t z); + +// fast (true) random numbers using hardware RNG, all functions return values in the range lowerlimit to upperlimit-1 +// note: for true random numbers with high entropy, do not call faster than every 200ns (5MHz) +// tests show it is still highly random reading it quickly in a loop (better than fastled PRNG) +// for 8bit and 16bit random functions: no limit check is done for best speed +// 32bit inputs are used for speed and code size, limits don't work if inverted or out of range +// inlining does save code size except for random(a,b) and 32bit random with limits +#ifdef ESP8266 +#define HW_RND_REGISTER RANDOM_REG32 +#else // ESP32 family +#include "soc/wdev_reg.h" +#define HW_RND_REGISTER REG_READ(WDEV_RND_REG) +#endif +#define random hw_random // replace arduino random() +inline uint32_t hw_random() { return HW_RND_REGISTER; }; +uint32_t hw_random(uint32_t upperlimit); // not inlined for code size +int32_t hw_random(int32_t lowerlimit, int32_t upperlimit); +inline uint16_t hw_random16() { return HW_RND_REGISTER; }; +inline uint16_t hw_random16(uint32_t upperlimit) { return (hw_random16() * upperlimit) >> 16; }; // input range 0-65535 (uint16_t) +inline int16_t hw_random16(int32_t lowerlimit, int32_t upperlimit) { int32_t range = upperlimit - lowerlimit; return lowerlimit + hw_random16(range); }; // signed limits, use int16_t ranges +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); // implements reallocf(): realloc with malloc fallback, original pointer is free'd if reallocation fails + 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); // implements reallocf(): realloc with malloc fallback, original pointer is free'd if reallocation fails + void p_free(void *ptr); + // realloc_malloc_nofree() implements the original realloc semantics: + // function returns a pointer to the newly allocated memory. This may be different from ptr, or NULL if the request fails. + // The contents will be unchanged in the range from the start of the region up to the minimum of the old and new sizes. + // If the new size is larger than the old size, the added memory will not be initialized. + // If realloc() fails the original block is left untouched; it is not freed or moved. + void *d_realloc_malloc_nofree(void *ptr, size_t size); + void *p_realloc_malloc_nofree(void *ptr, size_t size); +} +#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, and cannot store float +#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 only (if available) +#define BFRALLOC_CLEAR (1 << 5) // clear allocated buffer after allocation +void *allocate_buffer(size_t size, uint32_t type); +#endif + +#endif \ No newline at end of file diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 83eaee95c1..218b2f00d9 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,16 @@ 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 + #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..fd02d53529 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -204,17 +204,18 @@ #undef ALL_JSON_TO_PSRAM #define ALL_JSON_TO_PSRAM +// global WLED memory functions (util.cpp) +#include "util.h" + 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_nofree(ptr, new_size); // use PSRAM if it exists } void deallocate(void* pointer) { - free(pointer); + p_free(pointer); } }; using PSRAMDynamicJsonDocument = BasicJsonDocument;