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

@@ -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"));
}
}