From b986f49e1a97c7ce9d6686dcb8a3124dd2b990a2 Mon Sep 17 00:00:00 2001 From: Kaj Schittecat Date: Fri, 26 Jun 2026 19:11:13 +0200 Subject: [PATCH] =?UTF-8?q?core:=20move=20the=5Fmesh=20(~42=20KB)=20to=20P?= =?UTF-8?q?SRAM=20=E2=80=94=20reclaim=20internal=20DRAM?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- src/MyMesh.h | 4 ++++ src/main.cpp | 24 ++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/MyMesh.h b/src/MyMesh.h index 74502f0..44b6752 100644 --- a/src/MyMesh.h +++ b/src/MyMesh.h @@ -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 diff --git a/src/main.cpp b/src/main.cpp index 02e342d..335efa1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,10 @@ #include // needed for PlatformIO #include #include "MyMesh.h" +#if defined(ESP32_PLATFORM) + #include // 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 #include @@ -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 */