fix(ui-lvgl): 16 KB UI stack (screenshots reset the device); USB popup after the splash

The UI runs on Arduino's loop task, whose stock 8 KB stack had 340 B left
at its deepest point; a screenshot's file write went past it and reset the
device. The loop stack is now 16 KB (measured peak ~7.8 KB) and the
screenshot's line buffer is static. Diagnostics names the row
"UI stack free (lowest)".

With a cable in at boot, the USB popup waited for nothing and showed over
the splash; it now comes once the splash has faded.

Adds docs/development/plan-next.md, the plan up to the merge into dev.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-09-28 21:30:46 +02:00
co-authored by Claude Opus 5.5
parent 7a21e58c54
commit a11df8431b
5 changed files with 120 additions and 2 deletions
@@ -66,7 +66,7 @@ static const Name NAMES[] = {
{ "Noise floor", SEC_RADIO, "Noise floor", false }, { "RSSI/SNR", SEC_RADIO, "Last packet", false },
{ "Queue", SEC_RADIO, "Send queue", false }, { "Errors", SEC_RADIO, "Errors", false },
{ "RXPS wd s/h", SEC_RADIO, "RX watchdog soft / hard", false },
{ "Heap free", SEC_MEMORY, "Heap free", false }, { "Stack free", SEC_MEMORY, "Stack free", false },
{ "Heap free", SEC_MEMORY, "Heap free", false }, { "Stack free", SEC_MEMORY, "UI stack free (lowest)", false },
{ "Pool free", SEC_MEMORY, "Packet pool free", false },
};
static const Name* nameOf(const char* core) {
@@ -92,6 +92,7 @@ static void dismiss() {
anim::run(s_root, anim::setOpa, LV_OPA_COVER, LV_OPA_TRANSP, 300, freeAll);
}
static void onTimer(lv_timer_t* t) { (void)t; dismiss(); }
static bool up() { return s_root != nullptr; } // until its fade-out has finished
static void onTap(lv_event_t* e) { (void)e; dismiss(); }
// One character of the lettering: its width, and whether (x, y) is inked.
@@ -504,10 +504,13 @@ static void usbPanel(const char* title, const char* text, bool buttons) {
}
}
namespace splash { static bool up(); } // Splash.h
void UITask::usbPoll() {
using namespace usbview;
static uint32_t next = 0;
if ((int32_t)(millis() - next) < 0) return;
if (splash::up()) return; // a cable in at boot: the popup comes once the splash is gone
next = millis() + 250;
if (lvport::usbDriveOn()) {
if (!lvport::usbDriveDone()) return;
+6 -1
View File
@@ -4,6 +4,11 @@
#include "target.h"
#if defined(ESP32)
#include <esp_heap_caps.h>
// The UI runs on Arduino's loop task: LVGL's renderer, FAT on the card and
// the map's decoders all nest on its stack, and the stock 8 KB left a few
// hundred bytes at the deepest point (Diagnostics > Stack free, the lowest
// ever) -- a screenshot's file write went past it and reset the device.
SET_LOOP_TASK_STACK_SIZE(16 * 1024);
#endif
#include <new>
#include <stdarg.h>
@@ -1496,7 +1501,7 @@ void UITask::takeScreenshot() {
put32(2, size); put32(10, 54); put32(14, 40); put32(18, W); put32(22, H);
hdr[26] = 1; hdr[28] = 24; put32(34, row * H);
ok = fwrite(hdr, 1, 54, f) == 54;
uint8_t line[320 * 3];
static uint8_t line[320 * 3]; // not on the loop task's stack
for (int32_t y = H - 1; ok && y >= 0; y--) { // bottom-up, BGR
const uint16_t* s = px + y * W;
for (int32_t x = 0; x < W && x < 320; x++) {