Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions usermods/audioreactive/audio_reactive.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -224,8 +224,8 @@ void FFTcode(void * parameter)
DEBUGSR_PRINT("FFT started on core: "); DEBUGSR_PRINTLN(xPortGetCoreID());

// allocate FFT buffers on first call
if (vReal == nullptr) vReal = (float*) calloc(sizeof(float), samplesFFT);
if (vImag == nullptr) vImag = (float*) calloc(sizeof(float), samplesFFT);
if (vReal == nullptr) vReal = (float*) calloc(samplesFFT, sizeof(float));
if (vImag == nullptr) vImag = (float*) calloc(samplesFFT, sizeof(float));
if ((vReal == nullptr) || (vImag == nullptr)) {
// something went wrong
if (vReal) free(vReal); vReal = nullptr;
Expand Down
17 changes: 8 additions & 9 deletions wled00/FX.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,23 +88,22 @@ extern byte realtimeMode; // used in getMappedPixelIndex()
#endif
#define FPS_CALC_SHIFT 7 // bit shift for fixed point math

/* each segment uses 82 bytes of SRAM memory, so if you're application fails because of
insufficient memory, decreasing MAX_NUM_SEGMENTS may help */
// heap memory limit for effects data, pixel buffers try to reserve it if PSRAM is available
#ifdef ESP8266
#define MAX_NUM_SEGMENTS 16
/* How much data bytes all segments combined may allocate */
#define MAX_SEGMENT_DATA 5120
#define MAX_SEGMENT_DATA (6*1024) // 6k by default
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
#define MAX_NUM_SEGMENTS 20
#define MAX_SEGMENT_DATA (MAX_NUM_SEGMENTS*512) // 10k by default (S2 is short on free RAM)
#define MAX_SEGMENT_DATA (20*1024) // 20k by default (S2 is short on free RAM), limit does not apply if PSRAM is available
#else
#define MAX_NUM_SEGMENTS 32 // warning: going beyond 32 may consume too much RAM for stable operation
#define MAX_SEGMENT_DATA (MAX_NUM_SEGMENTS*1280) // 40k by default
#define MAX_SEGMENT_DATA (64*1024) // 64k by default, limit does not apply if PSRAM is available
#endif

/* How much data bytes each segment should max allocate to leave enough space for other segments,
assuming each segment uses the same amount of data. 256 for ESP8266, 640 for ESP32. */
#define FAIR_DATA_PER_SEG (MAX_SEGMENT_DATA / WS2812FX::getMaxSegments())
#define FAIR_DATA_PER_SEG (MAX_SEGMENT_DATA / MAX_NUM_SEGMENTS)

#define MIN_SHOW_DELAY (_frametime < 16 ? 8 : 15)

Expand Down Expand Up @@ -533,7 +532,6 @@ class Segment {

protected:

inline static unsigned getUsedSegmentData() { return Segment::_usedSegmentData; }
inline static void addUsedSegmentData(int len) { Segment::_usedSegmentData += len; }

inline uint32_t *getPixels() const { return pixels; }
Expand Down Expand Up @@ -600,8 +598,8 @@ class Segment {
, _t(nullptr)
{
DEBUGFX_PRINTF_P(PSTR("-- Creating segment: %p [%d,%d:%d,%d]\n"), this, (int)start, (int)stop, (int)startY, (int)stopY);
// allocate render buffer (always entire segment)
pixels = static_cast<uint32_t*>(d_calloc(sizeof(uint32_t), length())); // error handling is also done in isActive()
// allocate render buffer (always entire segment), prefer PSRAM if DRAM is running low. Note: impact on FPS with PSRAM buffer is low (<2% with QSPI PSRAM)
pixels = static_cast<uint32_t*>(allocate_buffer(length() * sizeof(uint32_t), BFRALLOC_PREFER_PSRAM | BFRALLOC_NOBYTEACCESS | BFRALLOC_CLEAR));
if (!pixels) {
DEBUGFX_PRINTLN(F("!!! Not enough RAM for pixel buffer !!!"));
extern byte errorFlag;
Expand Down Expand Up @@ -672,6 +670,7 @@ class Segment {
inline uint16_t dataSize() const { return _dataLen; }
bool allocateData(size_t len); // allocates effect data buffer in heap and clears it
void deallocateData(); // deallocates (frees) effect data buffer from heap
inline static unsigned getUsedSegmentData() { return Segment::_usedSegmentData; }
/**
* Flags that before the next effect is calculated,
* the internal segment state should be reset.
Expand Down
87 changes: 51 additions & 36 deletions wled00/FX_fcn.cpp
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,10 @@ Segment::Segment(const Segment &orig) {
if (!stop) return; // nothing to do if segment is inactive/invalid
if (orig.pixels) {
// allocate pixel buffer: prefer IRAM/PSRAM
pixels = static_cast<uint32_t*>(d_malloc(sizeof(uint32_t) * orig.length()));
pixels = static_cast<uint32_t*>(allocate_buffer(orig.length() * sizeof(uint32_t), BFRALLOC_PREFER_PSRAM | BFRALLOC_NOBYTEACCESS));
if (pixels) {
memcpy(pixels, orig.pixels, sizeof(uint32_t) * orig.length());
if (orig.name) { name = static_cast<char*>(d_malloc(strlen(orig.name)+1)); if (name) strcpy(name, orig.name); }
if (orig.name) { name = static_cast<char*>(allocate_buffer(strlen(orig.name)+1, BFRALLOC_PREFER_PSRAM)); if (name) strcpy(name, orig.name); }
if (orig.data) { if (allocateData(orig._dataLen)) memcpy(data, orig.data, orig._dataLen); }
} else {
DEBUGFX_PRINTLN(F("!!! Not enough RAM for pixel buffer !!!"));
Expand All @@ -97,10 +97,10 @@ Segment& Segment::operator= (const Segment &orig) {
//DEBUG_PRINTF_P(PSTR("-- Copying segment: %p -> %p\n"), &orig, this);
if (this != &orig) {
// clean destination
if (name) { d_free(name); name = nullptr; }
if (name) { p_free(name); name = nullptr; }
if (_t) stopTransition(); // also erases _t
deallocateData();
d_free(pixels);
p_free(pixels);
// copy source
memcpy((void*)this, (void*)&orig, sizeof(Segment));
// erase pointers to allocated data
Expand All @@ -111,10 +111,10 @@ Segment& Segment::operator= (const Segment &orig) {
// copy source data
if (orig.pixels) {
// allocate pixel buffer: prefer IRAM/PSRAM
pixels = static_cast<uint32_t*>(d_malloc(sizeof(uint32_t) * orig.length()));
pixels = static_cast<uint32_t*>(allocate_buffer(orig.length() * sizeof(uint32_t), BFRALLOC_PREFER_PSRAM | BFRALLOC_NOBYTEACCESS));
if (pixels) {
memcpy(pixels, orig.pixels, sizeof(uint32_t) * orig.length());
if (orig.name) { name = static_cast<char*>(d_malloc(strlen(orig.name)+1)); if (name) strcpy(name, orig.name); }
if (orig.name) { name = static_cast<char*>(allocate_buffer(strlen(orig.name)+1, BFRALLOC_PREFER_PSRAM)); if (name) strcpy(name, orig.name); }
if (orig.data) { if (allocateData(orig._dataLen)) memcpy(data, orig.data, orig._dataLen); }
} else {
DEBUG_PRINTLN(F("!!! Not enough RAM for pixel buffer !!!"));
Expand All @@ -130,10 +130,10 @@ Segment& Segment::operator= (const Segment &orig) {
Segment& Segment::operator= (Segment &&orig) noexcept {
//DEBUG_PRINTF_P(PSTR("-- Moving segment: %p -> %p\n"), &orig, this);
if (this != &orig) {
if (name) { d_free(name); name = nullptr; } // free old name
if (name) { p_free(name); name = nullptr; } // free old name
if (_t) stopTransition(); // also erases _t
deallocateData(); // free old runtime data
d_free(pixels); // free old pixel buffer
p_free(pixels); // free old pixel buffer
// move source data
memcpy((void*)this, (void*)&orig, sizeof(Segment));
orig.name = nullptr;
Expand All @@ -147,35 +147,38 @@ Segment& Segment::operator= (Segment &&orig) noexcept {

// allocates effect data buffer on heap and initialises (erases) it
bool Segment::allocateData(size_t len) {
if (len == 0) return false; // nothing to do
if (data && _dataLen >= len) { // already allocated enough (reduce fragmentation)
if (len == 0) return false; // nothing to do
if (data && _dataLen >= len) { // already allocated enough (reduce fragmentation)
if (call == 0) {
//DEBUG_PRINTF_P(PSTR("-- Clearing data (%d): %p\n"), len, this);
memset(data, 0, len); // erase buffer if called during effect initialisation
if (_dataLen < FAIR_DATA_PER_SEG) { // segment data is small
//DEBUG_PRINTF_P(PSTR("-- Clearing data (%d): %p\n"), len, this);
memset(data, 0, len); // erase buffer if called during effect initialisation
return true; // no need to reallocate
}
}
return true;
else
return true;
}
//DEBUG_PRINTF_P(PSTR("-- Allocating data (%d): %p\n"), len, this);
// limit to MAX_SEGMENT_DATA if there is no PSRAM, otherwise prefer functionality over speed
#ifndef BOARD_HAS_PSRAM
if (Segment::getUsedSegmentData() + len - _dataLen > MAX_SEGMENT_DATA) {
// not enough memory
DEBUG_PRINTF_P(PSTR("!!! Not enough RAM: %d/%d !!!\n"), len, Segment::getUsedSegmentData());
DEBUG_PRINTF_P(PSTR("SegmentData limit reached: %d/%d\n"), len, Segment::getUsedSegmentData());
errorFlag = ERR_NORAM;
return false;
}
// prefer DRAM over SPI RAM on ESP32 since it is slow
#endif

if (data) {
data = (byte*)d_realloc_malloc(data, len); // realloc with malloc fallback
if (!data) {
data = nullptr;
Segment::addUsedSegmentData(-_dataLen); // subtract original buffer size
_dataLen = 0; // reset data length
}
d_free(data); // free data and try to allocate again (segment buffer may be blocking contiguous heap)
Segment::addUsedSegmentData(-_dataLen); // subtract buffer size
}
else data = (byte*)d_malloc(len);

data = static_cast<byte*>(allocate_buffer(len, BFRALLOC_PREFER_DRAM | BFRALLOC_CLEAR)); // prefer DRAM over PSRAM for speed

if (data) {
memset(data, 0, len); // erase buffer
Segment::addUsedSegmentData(len - _dataLen);
Segment::addUsedSegmentData(len);
_dataLen = len;
//DEBUG_PRINTF_P(PSTR("--- Allocated data (%p): %d/%d -> %p\n"), this, len, Segment::getUsedSegmentData(), data);
return true;
Expand Down Expand Up @@ -209,7 +212,11 @@ void Segment::deallocateData() {
void Segment::resetIfRequired() {
if (!reset || !isActive()) return;
//DEBUG_PRINTF_P(PSTR("-- Segment reset: %p\n"), this);
if (data && _dataLen > 0) memset(data, 0, _dataLen); // prevent heap fragmentation (just erase buffer instead of deallocateData())
if (data && _dataLen > 0) {
if (_dataLen > FAIR_DATA_PER_SEG) deallocateData(); // do not keep large allocations
else memset(data, 0, _dataLen); // can prevent heap fragmentation
DEBUG_PRINTF_P(PSTR("-- Segment %p reset, data cleared\n"), this);
}
if (pixels) for (size_t i = 0; i < length(); i++) pixels[i] = BLACK; // clear pixel buffer
next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0;
reset = false;
Expand Down Expand Up @@ -466,7 +473,7 @@ void Segment::setGeometry(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, ui
if (length() != oldLength) {
// allocate render buffer (always entire segment), prefer IRAM/PSRAM. Note: impact on FPS with PSRAM buffer is low (<2% with QSPI PSRAM) on S2/S3
p_free(pixels);
pixels = static_cast<uint32_t*>(d_malloc(sizeof(uint32_t) * length()));
pixels = static_cast<uint32_t*>(allocate_buffer(length() * sizeof(uint32_t), BFRALLOC_PREFER_PSRAM | BFRALLOC_NOBYTEACCESS));
if (!pixels) {
DEBUGFX_PRINTLN(F("!!! Not enough RAM for pixel buffer !!!"));
deallocateData();
Expand Down Expand Up @@ -581,8 +588,8 @@ Segment &Segment::setName(const char *newName) {
if (newName) {
const int newLen = min(strlen(newName), (size_t)WLED_MAX_SEGNAME_LEN);
if (newLen) {
if (name) d_free(name); // free old name
name = static_cast<char*>(d_malloc(newLen+1));
if (name) p_free(name); // free old name
name = static_cast<char*>(allocate_buffer(newLen+1, BFRALLOC_PREFER_PSRAM));
if (mode == FX_MODE_2DSCROLLTEXT) startTransition(strip.getTransition(), true); // if the name changes in scrolling text mode, we need to copy the segment for blending
if (name) strlcpy(name, newName, newLen+1);
return *this;
Expand Down Expand Up @@ -1177,7 +1184,10 @@ void WS2812FX::finalizeInit() {
mem += bus.memUsage(Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) ? digitalCount++ : 0); // includes global buffer
if (mem <= MAX_LED_MEMORY) {
if (BusManager::add(bus) == -1) break;
} else DEBUG_PRINTF_P(PSTR("Out of LED memory! Bus %d (%d) #%u not created."), (int)bus.type, (int)bus.count, digitalCount);
} else {
errorFlag = ERR_NORAM_PX; // alert UI
DEBUG_PRINTF_P(PSTR("Out of LED memory! Bus %d (%d) #%u not created."), (int)bus.type, (int)bus.count, digitalCount);
}
}
Comment on lines +1187 to 1191
Copy link
Contributor

@coderabbitai coderabbitai bot Aug 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

finalizeInit(): enforce-PSRAM pixel buffer needs graceful fallback

If PSRAM is defined but unavailable at runtime (or temporarily exhausted), ENFORCE_PSRAM fails and the build becomes unusable. Attempt DRAM/IRAM as a fallback before erroring out.

-  _pixels = static_cast<uint32_t*>(allocate_buffer(getLengthTotal() * sizeof(uint32_t), BFRALLOC_ENFORCE_PSRAM | BFRALLOC_NOBYTEACCESS | BFRALLOC_CLEAR));
+  _pixels = static_cast<uint32_t*>(allocate_buffer(getLengthTotal() * sizeof(uint32_t),
+               BFRALLOC_ENFORCE_PSRAM | BFRALLOC_NOBYTEACCESS | BFRALLOC_CLEAR));
+  if (!_pixels) {
+    // fallback to prefer PSRAM and then DRAM to keep device functional
+    _pixels = static_cast<uint32_t*>(allocate_buffer(getLengthTotal() * sizeof(uint32_t),
+               BFRALLOC_PREFER_PSRAM | BFRALLOC_NOBYTEACCESS | BFRALLOC_CLEAR));
+  }

Also applies to: 1221-1226

🤖 Prompt for AI Agents
In wled00/FX_fcn.cpp around lines 1187-1191 (and similarly 1221-1226), the code
currently treats PSRAM allocation failure as fatal when ENFORCE_PSRAM is
defined; instead, attempt a graceful fallback: if allocation in PSRAM fails, try
allocating the pixel buffer in normal DRAM (or IRAM if available) before setting
errorFlag and logging failure. Update the allocation logic to try
ps_malloc/malloc in order, set a buffer-location flag accordingly, and only set
errorFlag/ERR_NORAM_PX and the "Out of LED memory" debug message after all
fallback attempts have failed, ensuring any successful fallback logs which
memory was used.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the allocate_buffer() function already takes care of a fallback if PSRAM is not available. If PSRAM is available, this will not fail as it is done at setup() stage and available PSRAM at that point is vast.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Let's examine the allocate_buffer function implementation to understand its fallback behavior
echo "=== Searching for allocate_buffer function implementation ==="
ast-grep --pattern 'void *allocate_buffer($_, $_) {
  $$$
}'

echo -e "\n=== Also searching with rg for allocate_buffer function ==="
rg -n -A 20 -B 5 "void \*allocate_buffer" --type=cpp

echo -e "\n=== Looking for BFRALLOC_ENFORCE_PSRAM flag handling ==="
rg -n -A 10 -B 5 "BFRALLOC_ENFORCE_PSRAM" --type=cpp

echo -e "\n=== Examining how PSRAM flags are processed in the allocator ==="
rg -n -A 15 -B 5 "ENFORCE_PSRAM.*PREFER_PSRAM|PREFER_PSRAM.*ENFORCE_PSRAM" --type=cpp

Length of output: 8856


🏁 Script executed:

#!/bin/bash
# Let's get the complete allocate_buffer implementation to see the fallback logic
echo "=== Complete allocate_buffer function implementation ==="
rg -n -A 50 -B 2 "void \*allocate_buffer" wled00/util.cpp

echo -e "\n=== Look for validateFreeHeap function to understand its behavior ==="
rg -n -A 10 -B 5 "validateFreeHeap" wled00/util.cpp

echo -e "\n=== Check BFRALLOC_ENFORCE_PSRAM comment in header ==="
rg -n -A 2 -B 2 "use PSRAM if available.*fall back" wled00/fcn_declare.h

Length of output: 9403


@DedeHai Thank you for the correction! You're absolutely right. I can now see from the allocate_buffer() implementation that my concern was misplaced.

Looking at the code, I can see that:

  1. BFRALLOC_ENFORCE_PSRAM already has fallback logic: The comment in fcn_declare.h clearly states "use PSRAM if available, otherwise fall back to DRAM"

  2. validateFreeHeap() provides additional safety: Even when BFRALLOC_ENFORCE_PSRAM calls heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT) directly (line 777), the result still goes through validateFreeHeap() which will free the buffer and return nullptr if minimum heap requirements aren't met

  3. ESP32 classic has even more sophisticated fallback: On CONFIG_IDF_TARGET_ESP32 with BFRALLOC_NOBYTEACCESS, it uses heap_caps_malloc_prefer() with a 3-tier preference: 32BIT → SPIRAM → 8BIT, providing automatic fallback

  4. Setup timing: As you mentioned, during the setup() stage when finalizeInit() is called, PSRAM has vast available memory, making failures highly unlikely

The allocate_buffer() function is indeed well-designed with multiple fallback mechanisms, and my suggestion for manual fallback logic was unnecessary. The current implementation already handles PSRAM unavailability gracefully.

🐰
/| /|
( . .)
) (
( v )
^^ ^ ^^


✏️ Learnings added
Learnt from: DedeHai
PR: wled/WLED#4791
File: wled00/FX_fcn.cpp:1187-1191
Timestamp: 2025-08-29T15:38:46.172Z
Learning: In WLED's allocate_buffer() function, BFRALLOC_ENFORCE_PSRAM already includes fallback logic to DRAM if PSRAM is not available, as documented in the comment "use PSRAM if available, otherwise fall back to DRAM". The function also uses validateFreeHeap() for additional safety checks. During setup() when finalizeInit() runs, PSRAM has vast available memory making failures unlikely.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: blazoncek
PR: wled/WLED#4667
File: usermods/user_fx/user_fx.cpp:27-30
Timestamp: 2025-04-30T05:41:03.633Z
Learning: In WLED, the Segment::allocateData() method already includes optimization to check if data is allocated and sufficiently sized, handling buffer reuse to reduce memory fragmentation. Adding an external check like `if (SEGENV.data == nullptr && !SEGENV.allocateData(dataSize))` is unnecessary and could be problematic, as it bypasses proper size verification.

Learnt from: willmmiles
PR: wled/WLED#4712
File: wled00/FX_fcn.cpp:992-993
Timestamp: 2025-06-07T15:58:42.579Z
Learning: In WLED segments, the backing pixel buffer is always allocated to the full physical segment size, but effect rendering only uses a subset when mirror or grouping are enabled. For 2D segments, this subset is vWidth() * vHeight(), and for 1D segments it's vLength(). Color processing operations like fade_out should iterate over this rendering subset, not the full allocated buffer.

Learnt from: DedeHai
PR: wled/WLED#4798
File: wled00/FX.cpp:7531-7533
Timestamp: 2025-08-26T11:51:21.817Z
Learning: In WLED PR #4798, DedeHai confirmed that certain gamma-related calls in FX.cpp/FX_fcn.cpp/particle systems are intentional for effect-level shaping (e.g., brightness curves, TV sim, Pride 2015 pre-mix), distinct from final output gamma. Do not flag or remove these in future reviews; add comments when feasible to clarify intent.

busConfigs.clear();
busConfigs.shrink_to_fit();
Expand All @@ -1196,7 +1206,7 @@ void WS2812FX::finalizeInit() {
bus->begin();
bus->setBrightness(bri);
}
DEBUG_PRINTF_P(PSTR("Heap after buses: %d\n"), ESP.getFreeHeap());
DEBUG_PRINTF_P(PSTR("Heap after buses: %d\n"), getFreeHeapSize());

Segment::maxWidth = _length;
Segment::maxHeight = 1;
Expand All @@ -1208,10 +1218,11 @@ void WS2812FX::finalizeInit() {
deserializeMap(); // (re)load default ledmap (will also setUpMatrix() if ledmap does not exist)

// allocate frame buffer after matrix has been set up (gaps!)
d_free(_pixels); // using realloc on large buffers can cause additional fragmentation instead of reducing it
_pixels = static_cast<uint32_t*>(d_malloc(getLengthTotal() * sizeof(uint32_t)));
p_free(_pixels); // using realloc on large buffers can cause additional fragmentation instead of reducing it
// use PSRAM if available: there is no measurable perfomance impact between PSRAM and DRAM on S2/S3 with QSPI PSRAM for this buffer
_pixels = static_cast<uint32_t*>(allocate_buffer(getLengthTotal() * sizeof(uint32_t), BFRALLOC_ENFORCE_PSRAM | BFRALLOC_NOBYTEACCESS | BFRALLOC_CLEAR));
DEBUG_PRINTF_P(PSTR("strip buffer size: %uB\n"), getLengthTotal() * sizeof(uint32_t));
DEBUG_PRINTF_P(PSTR("Heap after strip init: %uB\n"), ESP.getFreeHeap());
DEBUG_PRINTF_P(PSTR("Heap after strip init: %uB\n"), getFreeHeapSize());
}

void WS2812FX::service() {
Expand Down Expand Up @@ -1611,7 +1622,11 @@ static uint8_t estimateCurrentAndLimitBri(uint8_t brightness, uint32_t *pixels)
}

void WS2812FX::show() {
if (!_pixels) return; // no pixels allocated, nothing to show
if (!_pixels) {
DEBUGFX_PRINTLN(F("Error: no _pixels!"));
errorFlag = ERR_NORAM;
return; // no pixels allocated, nothing to show
}

unsigned long showNow = millis();
size_t diff = showNow - _lastShow;
Expand All @@ -1621,7 +1636,7 @@ void WS2812FX::show() {
// we need to keep track of each pixel's CCT when blending segments (if CCT is present)
// and then set appropriate CCT from that pixel during paint (see below).
if ((hasCCTBus() || correctWB) && !cctFromRgb)
_pixelCCT = static_cast<uint8_t*>(d_malloc(totalLen * sizeof(uint8_t))); // allocate CCT buffer if necessary
_pixelCCT = static_cast<uint8_t*>(allocate_buffer(totalLen * sizeof(uint8_t), BFRALLOC_PREFER_PSRAM)); // allocate CCT buffer if necessary, prefer PSRAM
if (_pixelCCT) memset(_pixelCCT, 127, totalLen); // set neutral (50:50) CCT

if (realtimeMode == REALTIME_MODE_INACTIVE || useMainSegmentOnly || realtimeOverride > REALTIME_OVERRIDE_NONE) {
Expand Down Expand Up @@ -1655,7 +1670,7 @@ void WS2812FX::show() {
}
Bus::setCCT(oldCCT); // restore old CCT for ABL adjustments

d_free(_pixelCCT);
p_free(_pixelCCT);
_pixelCCT = nullptr;

// some buses send asynchronously and this method will return before
Expand Down
46 changes: 20 additions & 26 deletions wled00/bus_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,35 +38,29 @@ uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb);
uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, const byte *buffer, uint8_t bri=255, bool isRGBW=false);

//util.cpp
// PSRAM allocation wrappers
#if !defined(ESP8266) && !defined(CONFIG_IDF_TARGET_ESP32C3)
// memory allocation wrappers
extern "C" {
void *p_malloc(size_t); // prefer PSRAM over DRAM
void *p_calloc(size_t, size_t); // prefer PSRAM over DRAM
void *p_realloc(void *, size_t); // prefer PSRAM over DRAM
void *p_realloc_malloc(void *ptr, size_t size); // realloc with malloc fallback, prefer PSRAM over DRAM
inline void p_free(void *ptr) { heap_caps_free(ptr); }
void *d_malloc(size_t); // prefer DRAM over PSRAM
void *d_calloc(size_t, size_t); // prefer DRAM over PSRAM
void *d_realloc(void *, size_t); // prefer DRAM over PSRAM
void *d_realloc_malloc(void *ptr, size_t size); // realloc with malloc fallback, prefer DRAM over PSRAM
// prefer DRAM over PSRAM (if available) in d_ alloc functions
void *d_malloc(size_t);
void *d_calloc(size_t, size_t);
void *d_realloc_malloc(void *ptr, size_t size);
#ifndef ESP8266
inline void d_free(void *ptr) { heap_caps_free(ptr); }
#else
inline void d_free(void *ptr) { free(ptr); }
#endif
#if defined(BOARD_HAS_PSRAM)
// prefer PSRAM over DRAM in p_ alloc functions
void *p_malloc(size_t);
void *p_calloc(size_t, size_t);
void *p_realloc_malloc(void *ptr, size_t size);
inline void p_free(void *ptr) { heap_caps_free(ptr); }
#else
#define p_malloc d_malloc
#define p_calloc d_calloc
#define p_free d_free
#endif
}
Comment on lines +41 to 63
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Don’t redeclare alloc wrappers here; fix missing p_realloc mapping.

This duplicates fcn_declare.h and risks drift. Also, the non-PSRAM path lacks p_realloc mapping.

Apply:

-//util.cpp
-// memory allocation wrappers
-extern "C" {
-  // prefer DRAM over PSRAM (if available) in d_ alloc functions
-  void *d_malloc(size_t);
-  void *d_calloc(size_t, size_t);
-  void *d_realloc_malloc(void *ptr, size_t size);
-  #ifndef ESP8266
-  inline void d_free(void *ptr) { heap_caps_free(ptr); }
-  #else
-  inline void d_free(void *ptr) { free(ptr); }
-  #endif
-  #if defined(BOARD_HAS_PSRAM)
-  // prefer PSRAM over DRAM in p_ alloc functions
-  void *p_malloc(size_t);
-  void *p_calloc(size_t, size_t);
-  void *p_realloc_malloc(void *ptr, size_t size);
-  inline void p_free(void *ptr) { heap_caps_free(ptr); }
-  #else
-  #define p_malloc d_malloc
-  #define p_calloc d_calloc
-  #define p_free d_free
-  #endif
-}
+#include "fcn_declare.h" // use canonical allocator declarations
+// (If keeping local fallbacks, ensure:)
+// #define p_realloc_malloc d_realloc_malloc

Committable suggestion skipped: line range outside the PR's diff.

#else
extern "C" {
void *realloc_malloc(void *ptr, size_t size);
}
#define p_malloc malloc
#define p_calloc calloc
#define p_realloc realloc
#define p_realloc_malloc realloc_malloc
#define p_free free
#define d_malloc malloc
#define d_calloc calloc
#define d_realloc realloc
#define d_realloc_malloc realloc_malloc
#define d_free free
#endif

//color mangling macros
#define RGBW32(r,g,b,w) (uint32_t((byte(w) << 24) | (byte(r) << 16) | (byte(g) << 8) | (byte(b))))
Expand Down
2 changes: 1 addition & 1 deletion wled00/cfg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
}
#endif

DEBUG_PRINTF_P(PSTR("Heap before buses: %d\n"), ESP.getFreeHeap());
DEBUG_PRINTF_P(PSTR("Heap before buses: %d\n"), getFreeHeapSize());
JsonArray ins = hw_led["ins"];
if (!ins.isNull()) {
int s = 0; // bus iterator
Expand Down
17 changes: 15 additions & 2 deletions wled00/const.h
Original file line number Diff line number Diff line change
Expand Up @@ -546,8 +546,21 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit");
#endif
#endif

// minimum heap size required to process web requests
#define MIN_HEAP_SIZE 8192
// minimum heap size required to process web requests: try to keep free heap above this value
#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
// 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
#ifdef ESP8266
Expand Down
3 changes: 1 addition & 2 deletions wled00/data/settings_leds.htm
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,6 @@
if (isAna(t)) return 5; // analog
let len = parseInt(d.getElementsByName("LC"+n)[0].value);
len += parseInt(d.getElementsByName("SL"+n)[0].value); // skipped LEDs are allocated too
let dbl = 0;
let ch = 3*hasRGB(t) + hasW(t) + hasCCT(t);
let mul = 1;
if (isDig(t)) {
Expand All @@ -207,7 +206,7 @@
mul = 2;
}
}
return len * ch * mul + dbl;
return len * ch * mul + len * 4; // add 4 bytes per LED for segment buffer (TODO: how to account for global buffer?)
}

function UI(change=false)
Expand Down
Loading