psram-aware malloc functions (backport of upstream #4895) (#342)

Introduces new memory allocation functions, based on wled#4895 by @DedeHai

* keep a minimum of 15KB RAM available to UI - improves stability
* S3/S2/C3 automatically use "fast RTC Ram" for small data (reduces fragmentation)
* auto-detects PSRAM (or no PSRAM) when firmware was built with -D BOARD_HAS_PSRAM
* d_malloc() and d_calloc() prefer DRAM if possible (faster), but fall back to PSRAM when free RAM gets low.
This commit is contained in:
Frank Möhle
2026-02-18 17:57:47 +01:00
committed by GitHub
parent 50bb4d2910
commit cce3c0b5d3
19 changed files with 696 additions and 191 deletions

View File

@@ -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());
@@ -755,7 +769,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
@@ -805,12 +819,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;
@@ -874,7 +888,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
@@ -959,7 +973,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
@@ -1338,13 +1352,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