fix: recover from "LoRa radio not detected" on the first boot after an update

Reported on a T-Deck straight after an OTA: the boot dead-ended on the fatal
"LoRa radio not detected" screen, and a manual reboot cleared it.

On boards with a peripheral power gate the LoRa module has no power until that
pin is driven high in Board::begin() — GPIO10 on the T-Deck — a few milliseconds
before the radio is probed. A cold start survives that because the rail was
already settled. A SOFTWARE reset does not, and an OTA ends in exactly one:
ESP.restart() releases the pin, the rail collapses, the app re-drives it and
probes the SX1262 while it is still powering up. The existing 150 ms retry does
not help because it never touches the rail — the module is latched in a bad
state, not merely slow, which is why only a real power cycle cleared it.

So do what the error screen would otherwise ask the user to do: bounce the rail
(LOW, drain, HIGH, wait out the power-on reset and crystal startup) and probe
once more. Guarded on PIN_PERF_POWERON, so boards without a gate are untouched,
and only reached when the alternative is halting on the fatal screen — a board
that probes normally never executes any of it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kaj Schittecat
2026-08-13 18:46:28 +02:00
co-authored by Claude Opus 5
parent 17643435ea
commit fee132425c
+26
View File
@@ -740,6 +740,32 @@ void setup() {
bool radio_ok = radio_init();
if (!radio_ok) { delay(150); radio_ok = radio_init(); } // one retry: transient SPI/reset flakes
#if defined(PIN_PERF_POWERON)
// Last resort: do what the error screen would otherwise ask the USER to do —
// power-cycle the module — by bouncing the rail that feeds it.
//
// On boards with a peripheral power gate (T-Deck, GPIO10) the LoRa module is only
// powered once that pin is driven high in Board::begin(), a few milliseconds before
// it gets probed. A cold start survives that because the rail was already settled;
// a SOFTWARE reset does not. An OTA update ends in ESP.restart(), which releases the
// pin, collapses the rail, and re-drives it immediately — so the radio is probed
// while it is still coming up, and the boot dead-ends on "LoRa radio not detected"
// until the user power-cycles by hand (reported on a T-Deck straight after an
// update; a reboot cleared it). A brief, deliberate LOW gives the module a clean
// power-on reset regardless of how this boot was reached.
//
// Only reached when the alternative is halting with the fatal screen, so it cannot
// regress a healthy boot: a board that probes fine never runs any of this.
if (!radio_ok) {
Serial.println("[BOOT] radio not detected — power-cycling the peripheral rail and retrying");
digitalWrite(PIN_PERF_POWERON, LOW);
delay(120); // long enough for the rail to actually drain
digitalWrite(PIN_PERF_POWERON, HIGH);
delay(250); // POR + crystal startup before the module answers SPI
radio_ok = radio_init();
if (radio_ok) Serial.println("[BOOT] radio came up after the power-cycle");
}
#endif
if (!radio_ok) {
// A dead or absent LoRa module used to halt behind the frozen boot logo,
// with the only clue on serial (#244). Say it on the panel instead.