From fee132425c6b24a04f85b5b54ee27dc414acc45f Mon Sep 17 00:00:00 2001 From: Kaj Schittecat Date: Thu, 13 Aug 2026 18:46:28 +0200 Subject: [PATCH] fix: recover from "LoRa radio not detected" on the first boot after an update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/main.cpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 88ddd3e..71d7276 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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.