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:
@@ -2562,14 +2562,24 @@ public:
|
||||
|
||||
//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 @@ public:
|
||||
#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 @@ public:
|
||||
#endif
|
||||
}
|
||||
#if ARTI_PLATFORM == ARTI_ARDUINO //not on windows as cause crash???
|
||||
d_free(programText);
|
||||
#else
|
||||
free(programText);
|
||||
#endif
|
||||
programText = nullptr;
|
||||
|
||||
if (stages >= 3)
|
||||
{
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -151,9 +151,9 @@ private:
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user