Skip to content

ESP32 PSRAM offload, 2026-10-27

V4, G2 Full and T-Deck Full each free 36,792 bytes of internal RAM by moving their scope tables and advert history to PSRAM. MQTT buffers can save another 2,050 bytes per initialized client. Features, capacities and saved formats remain unchanged. No board was flashed during these checks.

The measured evidence binds source hashes, matched builds, capabilities and the two existing packaging failures. The paired measurements isolate these PSRAM changes on baseline 74c03cb30. Concurrent Companion PIN and startup-screen changes were integrated afterward; a further G2 Full build at 6a5a04b1b passes with the same RAM result and 15/15 capability checks. It fits its OTA slots at 2,125,736 application bytes.

Enabled by default

ESP32-S3 repeater builds with BOARD_HAS_PSRAM move complete scope tables and advert history to PSRAM. Existing explicit RTC/IRAM advert backends retain priority. This covers these configured board families and their repeater aliases:

Board families PSRAM interface
Station G2/G3, T-Deck, T-ETH Elite, T-Beam 1W OPI
V4 R8, RC32, E213, E290, T190 OPI
W12, Wio Tracker L2, XIAO ESP32-S3/WIO, ThinkNode M7/M9 OPI
Original SenseCAP Indicator OPI
Standard V4, T3-S3 SX1262/SX1276, EoRa-S3 QSPI
T-Beam S3 Supreme, Waveshare S3 LR1121, Indicator N16R2 QSPI

The IDF 4 WebSocket wrapper also prefers PSRAM on all PSRAM-enabled ESP32 builds that initialize MQTT clients, including companions, room servers and classic T-Beam. It retains both 1,025-byte padded buffers per initialized client, including plain MQTT clients. IDF 5 remains a passthrough.

Classic T-Beam keeps its established table placement. Its qualified Full MQTT profiles already store all 17,920 advert-history bytes in separate word-accessible IRAM; only 3,162 scope-table bytes remain. This extension retains that backend and its cache-workaround flags. Boards without compiled PSRAM support retain existing allocation paths.

Startup and fallback

QSPI PSRAM initializes in initArduino(), after global constructors. The tables now allocate in MyMesh::begin, before routing and persistence start, on both QSPI and OPI boards. Constructors retain only pointers. Getters never allocate or move storage, and return complete array references so sizeof, saved bytes, rollback and const access remain correct.

Internal fallback must preserve the 16 KiB DMA reserve and hold each complete table; otherwise startup reports failure and stops. The RAM check resolves the actual board MCU and retains both full fallback allowances. Packet pools, task stacks, ISR state and DMA buffers retain their existing placement. MESH_REPEATER_PSRAM_TABLES=0 restores inline table storage for custom builds.

Matched Full builds

Target Internal heap before Internal heap after Gain Capability checks
V4 203,456 240,248 36,792 16/16
G2 200,328 237,120 36,792 15/15
T-Deck 223,960 260,752 36,792 14/14

All three pass compilation, RAM admission, dual-slot OTA fit and artifact checks with identical capability lists and no declared reductions. The RAM model still reserves 36,828 bytes for complete internal fallback, so its conservative admission margins remain similar. These are linked capacity measurements, not running free-heap or maximum broker-load measurements.

Two packaging failures were reproduced before and after these changes:

  • G2 standard exceeds its old 1,310,720-byte slot: linked program size falls from 1,375,509 to 1,374,093 bytes but still fails. G2 Full passes separately.
  • Classic T-Beam Full MQTT compiles and passes RAM admission, but fails its existing dual-slot OTA fit check. This image was not qualified or flashed.

Builds used build.sh build-firmware TARGET --full-exact with --firmware-version v1.18.0.2-psram-test --profile cascade --radio-preset usa-cascade-fixed --require-ota; G2 legacy testing substituted --standard. PlatformIO processes ran one at a time.

Software coverage

  • All 65 RAM-policy tests pass, including real-MCU selection and unchanged fallback, reserve and contiguous-block thresholds.
  • Scope storage, advert storage and existing capacity/RTC/IRAM tests pass (23 methods), including constructor-no-allocation, QSPI startup, full persisted-byte comparisons, rollback, lifetime and allocation failures.
  • WebSocket tests pass three methods and 32 scenarios over seven platform configurations, with padding, cleanup, reserve and IDF 4/5 coverage.

Relevant native storage/transport tests run under ASan/UBSan. Physical boot, RF timing, persistence and sustained MQTT load still need hardware validation.