From 93bf23940647e64313837bc892cbf475607cadbf Mon Sep 17 00:00:00 2001 From: Martin Dobrev Date: Sat, 5 Sep 2026 17:36:12 +0100 Subject: [PATCH] tasks: the card poller and the radio were given 8 KB because nobody had measured MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both were the number you write when you do not know. The console now reports what each task has actually had left, so they can be sized from evidence — with the margin the consequence deserves rather than the margin the observation would allow, because a high-water mark is "worst seen" and never "worst possible". The radio goes to 6 KB. Worth saying what does not set it: the transmit frame is a member of LoRaRadio rather than a local, so the size of a LoRa packet never lands on this stack — what does is call depth through RadioLib, the SPI transaction under it, and the receive path pushing into the ring. That is not derivable the way a buffer is, so the figure is empirical, and it is the stack that moves: it read 1.7 KB used one morning and 2.6 KB the same evening on the same firmware. 6 KB leaves more spare than the whole of what has ever been used. The card poller goes to 6 KB and not the 4 the numbers alone would allow, because of a line sitting directly above where the task is created: at 4 KB the FAT layer tripped the canary on core 0, on mount probes and the rename that log rotation does. The probe path has been rewritten since and today's figure is 1.6 KB, but "it crashed at 4 KB once" outranks "it uses 1.6 KB this week" — one is a bound and the other is a sample. Its deepest path is a format, which runs on this task and which nothing has exercised while anybody was watching; FatFS takes that work buffer from the heap, which limits the exposure but does not remove it. What it buys, measured on the T-Deck: 18940 bytes of free internal RAM before and 24808 after, the low-water mark from 14748 to 20400, and the largest contiguous block from 7156 to 14324. That last figure is the one that matters for the portal, and it doubled. Which settles something that was an either/or this morning. With the speaker switched on, the portal answered in 1.6 s where the same node with the same setting used to time out entirely. Audio and the web interface fit on that board now. Verified on both: radio online on the SX1262 and the LR1110 with packets moving, the card still polling, and neither trimmed task below 4.5 KB of headroom. --- src/Config.h | 43 +++++++++++++++++++++++++++++++++++++++++++ src/main.cpp | 2 +- src/sys/SdCard.cpp | 8 +++++--- 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/Config.h b/src/Config.h index ad8daca..3b1b2ad 100644 --- a/src/Config.h +++ b/src/Config.h @@ -598,6 +598,49 @@ // and a DMA ring, about 5.4 KB of internal RAM, which on the board that has one // is a quarter of what it has spare and enough to stop the portal serving. That // ships off, and turning it on is a choice made knowing the trade. +// Task stacks that were guessed generously and have now been measured. +// +// Both were 8 KB, which is what you write when you do not know. What the +// console's STACKS command reports is the least each has ever had left, and on +// two boards over a day's use they never came close — but a high-water mark is +// "worst seen", never "worst possible", so these are cut with the margin the +// consequence deserves rather than to the observation. +// +// The card's poller: 1.6 KB used of 8 KB, and the biggest thing on its stack is +// the 512-byte sector it reads to notice a card being pulled. Its deepest path +// is not that, though — it is a format, which runs on this task and which +// nothing has exercised while anybody was measuring. FatFS takes its format +// work buffer from the heap (ff_memalloc, ffconf.h) so that part is not stack, +// but the call depth beneath it is unmeasured. +// +// And this stack has a history, recorded where it was raised: at 4 KB the FAT +// layer tripped the canary on core 0 — mount probes and a rename on log +// rotation, which are not what idles here and are exactly what a measurement +// taken while nothing is happening will miss. The probe path has been rewritten +// since and the figure today is 1.6 KB, but "it crashed at 4 KB once" outranks +// "it uses 1.6 KB this week": one is a bound, the other is a sample. +// +// So 6 KB, not the 4 the measurement alone would allow. Half as much again as +// the size that is known to have failed, nearly four times what has been seen, +// and it still gives 2 KB back. Getting this wrong costs somebody a corrupted +// card in the middle of formatting it. +#ifndef SD_TASK_STACK + #define SD_TASK_STACK 6144 +#endif +// The radio: 2.6 KB used of 8 KB. Worth knowing that the packet does not set +// this — the transmit frame is a member of LoRaRadio, not a local, so the size +// of a LoRa frame never lands on this stack. What lands on it is call depth: +// RadioLib, the SPI transaction under it, and the receive path pushing into the +// ring. That is not derivable the way a buffer is, so the margin is empirical: +// 6 KB leaves more spare than the whole of what has ever been used. +// +// It is also the stack that has moved. It read 1.7 KB used this morning and +// 2.6 KB by the evening on the same firmware, which is the argument for +// watching STACKS over a soak rather than trusting one afternoon's figure. +#ifndef RADIO_TASK_STACK + #define RADIO_TASK_STACK 6144 +#endif + #ifndef SOUND_ENABLED_DEFAULT #if BUZZER_KIND == BUZZER_KIND_I2S #define SOUND_ENABLED_DEFAULT 0 diff --git a/src/main.cpp b/src/main.cpp index e07cc1d..a5e3c8c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -352,7 +352,7 @@ void setup() { #endif // ---- Task layout (see the diagram above) ------------------------------- - Diag::startTask(LoRaRadio::radioTask, "radio", 8192, &loraRadio, 5, 1); + Diag::startTask(LoRaRadio::radioTask, "radio", RADIO_TASK_STACK, &loraRadio, 5, 1); // Before the panel, and deliberately. This task used to ask last, after the // web server and — on a GUI board — a 16 KB display stack had already taken diff --git a/src/sys/SdCard.cpp b/src/sys/SdCard.cpp index 92c865f..966401c 100644 --- a/src/sys/SdCard.cpp +++ b/src/sys/SdCard.cpp @@ -83,9 +83,11 @@ void SdCard::begin() { // a flag checked at the top of poll() only ever stopped the poll after the one // that mattered. void SdCard::startPolling() { - // 8 KB: the FAT layer (mount probes, rename on log rotation) left under - // 1 KB of a 4 KB stack at idle and tripped the stack canary on core 0. - Diag::startTask(task, "sdcard", 8192, this, 1, 0); + // Sized in Config.h, and read the reasoning there before lowering it: this + // task tripped the canary on core 0 at 4 KB, when the FAT layer's mount + // probes and the rename on log rotation went deeper than anything an idle + // measurement shows. + Diag::startTask(task, "sdcard", SD_TASK_STACK, this, 1, 0); } void SdCard::reserve(bool on) {