Skip to content

fix(mem): reclaim internal DRAM — PSRAM LAN RX buffer, PSRAM-only WiFi/tinfl - #140

Merged
jonasniesner merged 1 commit into
OpenDisplay:mainfrom
davelee98:feat/psram-dram-reclaim
Aug 3, 2026
Merged

fix(mem): reclaim internal DRAM — PSRAM LAN RX buffer, PSRAM-only WiFi/tinfl#140
jonasniesner merged 1 commit into
OpenDisplay:mainfrom
davelee98:feat/psram-dram-reclaim

Conversation

@davelee98

@davelee98 davelee98 commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Summary

  • ESP32-S3 was repeatedly PANIC-resetting after a BLE pipe-write due to internal-DRAM exhaustion (bottoming out at 48–264 B free internal DRAM on wake, per ESP.getMinFreeHeap() which is MALLOC_CAP_INTERNAL-only).
  • tcpReceiveBuffer (the 16 KB LAN RX reassembly buffer) moves from a static .bss array to a runtime allocation that prefers PSRAM via odLanReserveRxBuffer(), falling back to internal DRAM with a logged warning if PSRAM is absent/dead. startLanServer() now refuses to listen if reservation failed rather than accepting a socket the parser can't serve.
  • -DOPENDISPLAY_ENABLE_WIFI is removed from esp32-c3-N4, esp32-c6-N4, and esp32-c3-N16 (no PSRAM to relocate anything into) — this also drops OPENDISPLAY_USE_TINFL's ~15 KB of tables on those parts, since both gates share the same flag. Those three targets become BLE-only (no LAN push, mDNS, or TLS-PSK) and fall back to uzlib for inflate, same as esp32-N4/nrf52840custom already do.

Measured .dram0 static:

  • esp32-s3-N16R8: .bss 111,960 → 95,592 (−16,368 B)
  • esp32-c3-N4: .bss 89,120 → 60,712, .data 15,641 → 11,505 (−32,544 B total)
  • nrf52840custom: ELF byte-identical

All 12 targets in .github/firmware-targets.json build; verified all 16 platformio.ini envs (including both debug variants and esp32-wrover-e-N4R8) build clean.

Test plan

  • pio run across all 16 environments — all SUCCESS
  • Hardware verification: min-heap on a wake cycle with a BLE pipe-write (expect ~16 KB where it was 48–264 B)
  • Hardware verification: uzlib inflate throughput on C3/C6 with WiFi/tinfl removed

…i/tinfl

ESP32-S3 was panicking repeatedly (six identical PANIC resets, same Saved PC,
each following a BLE pipe-write). Cause is internal-DRAM exhaustion, not a
pointer fault: ESP.getMinFreeHeap() is MALLOC_CAP_INTERNAL only, and on every
wake cycle that ran a BLE pipe-write it bottomed out at 48-264 bytes. Wake boots
stack NimBLE (~65 KB) plus WiFi/LWIP/mDNS and the FastEPD cold bring-up (~49 KB)
against the ~98 KB left after the mbedTLS record slots are reserved at boot.

Two changes, one theme: the DRAM-expensive features are now built only for parts
that have the DRAM.

1. tcpReceiveBuffer moves to PSRAM. It was a 16 KB permanently-resident .bss
   array and the largest app-owned static buffer, yet the link-owner rule leaves
   it idle during every BLE transfer. odLanReserveRxBuffer() reserves it once at
   boot, PSRAM first, internal DRAM as a fallback (a board with dead PSRAM boots
   silently under CONFIG_SPIRAM_IGNORE_NOTFOUND=1, so the fallback logs a
   warning). Never freed. If reservation fails, startLanServer() refuses to
   listen rather than accepting a socket the parser cannot serve.

   The declaration moves to wifi_service.h with OD_LAN_RX_BUFFER_SIZE. It was
   defined in main.h and re-declared `extern uint8_t[16384]` in wifi_service.cpp
   with the size literal duplicated: C++ does not mangle namespace-scope variable
   names, so converting one side to a pointer and not the other would have linked
   cleanly and then read the buffer's first bytes as a pointer. Both
   sizeof(tcpReceiveBuffer) sites become the constant in the same change --
   sizeof on a pointer yields 8 and would have collapsed LAN reads to a few bytes
   per tick, reading as a network fault rather than a code bug.

2. -DOPENDISPLAY_ENABLE_WIFI is removed from esp32-c3-N4, esp32-c6-N4 and
   esp32-c3-N16. That flag has exactly two consumers -- OPENDISPLAY_HAS_WIFI
   (wifi_service.h) and OPENDISPLAY_USE_TINFL (od_inflate_tinfl.h) -- so one edit
   per env drops both the LAN transport and tinfl's tables on the parts with no
   PSRAM to relocate anything into. Neither gate derivation changes; the flag is
   the single control point, and the invariant "set it only on -DBOARD_HAS_PSRAM
   envs" is documented in wifi_service.h and per-env in platformio.ini. Nothing
   in code enforces it.

   BREAKING for those three targets: they become BLE-only (no LAN push, mDNS or
   TLS-PSK) and fall back to uzlib for inflate on every compressed path,
   including BLE. The uzlib path already ships on esp32-N4 and nrf52840custom.

Measured, .dram0 static:
  esp32-s3-N16R8   .bss  111,960 -> 95,592          (-16,368 B)
  esp32-c3-N4      .bss   89,120 -> 60,712,
                   .data  15,641 -> 11,505          (-32,544 B total)
  nrf52840custom   ELF byte-identical to before

All 12 targets in .github/firmware-targets.json build.

Not yet verified on hardware: the min-heap figure on a wake cycle with a BLE
pipe-write (expect ~16 KB where it was 48-264 B), and uzlib throughput on C3/C6.
@davelee98
davelee98 requested a review from jonasniesner as a code owner August 3, 2026 19:33
@jonasniesner
jonasniesner merged commit fdace2a into OpenDisplay:main Aug 3, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants