core: move the_mesh (~42 KB) to PSRAM — reclaim internal DRAM

the_mesh was the single biggest static internal-DRAM consumer at 42 KB (dominated
by the MAX_CONTACTS=100 ContactInfo array inside it). Internal DRAM (~320 KB) is the
scarce resource on the ESP32-S3 — Wi-Fi/BLE DMA + heap fight over it, and the V4 was
hitting ~99% on map-tile load (issue #47b). PSRAM (8 MB) is plentiful.

Place the whole the_mesh object in PSRAM via placement-new into a heap_caps_malloc
(MALLOC_CAP_SPIRAM) block and bind a reference, so every the_mesh.foo() call site is
unchanged. The constructor still runs at the SAME static-init point as the old direct
global (PSRAM is already up then), so timing and behaviour are identical; only the
address moves off internal DRAM. Falls back to internal RAM if PSRAM is absent.
ESP32-only (STM32/RP2040 keep the plain global). The contacts array rides along
inside the object — no vendored-core change.

Measured on heltec_v4 (.dram0.bss): 124160 -> 82000 B = ~41 KB internal DRAM freed.
Verified on the T-Deck: clean boot, [BOOT] mesh ok, reason=0, zero panic markers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kaj Schittecat
2026-06-26 19:11:13 +02:00
parent f6192aa6e5
commit b986f49e1a
2 changed files with 28 additions and 0 deletions
+4
View File
@@ -951,4 +951,8 @@ private:
uint32_t _ui_trace_ping_tag = 0;
};
#if defined(ESP32_PLATFORM)
extern MyMesh& the_mesh; // PSRAM-resident (placement-new'd in main.cpp); reference keeps call sites unchanged
#else
extern MyMesh the_mesh;
#endif
+24
View File
@@ -1,6 +1,10 @@
#include <Arduino.h> // needed for PlatformIO
#include <Mesh.h>
#include "MyMesh.h"
#if defined(ESP32_PLATFORM)
#include <new> // placement-new for the PSRAM-resident the_mesh
#include "esp_heap_caps.h" // heap_caps_malloc(MALLOC_CAP_SPIRAM)
#endif
#if defined(ESP32_PLATFORM) && defined(HAS_TOUCH_UI)
#include <Preferences.h>
#include <esp_system.h>
@@ -127,11 +131,31 @@ static uint32_t _atoi(const char* sp) {
StdRNG fast_rng;
SimpleMeshTables tables;
#if defined(ESP32_PLATFORM)
// the_mesh is ~42 KB (dominated by the MAX_CONTACTS ContactInfo array) and was the
// single biggest static internal-DRAM consumer. Place the whole object in PSRAM —
// the contacts array rides along inside it — and bind a reference so every
// `the_mesh.foo()` call site is unchanged. The constructor still runs HERE at
// static-init (PSRAM is already up; the UITask psAlloc statics rely on the same),
// so timing/behaviour are identical to the old direct global — only the address
// moves off internal DRAM. heap_caps falls back to internal RAM if PSRAM is absent.
static MyMesh& makeTheMesh() {
void* mem = heap_caps_malloc(sizeof(MyMesh), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
if (!mem) mem = malloc(sizeof(MyMesh)); // no-PSRAM fallback: behaves as before
return *new (mem) MyMesh(radio_driver, fast_rng, rtc_clock, tables, store
#ifdef DISPLAY_CLASS
, &ui_task
#endif
);
}
MyMesh& the_mesh = makeTheMesh();
#else
MyMesh the_mesh(radio_driver, fast_rng, rtc_clock, tables, store
#ifdef DISPLAY_CLASS
, &ui_task
#endif
);
#endif
/* END GLOBAL OBJECTS */