From a9f64be3ae4891136ea0da4fe1f9317edca3e206 Mon Sep 17 00:00:00 2001 From: wormuz Date: Thu, 27 Aug 2026 19:07:55 +0300 Subject: [PATCH] feat(sx126x): optional CAD timeout so a stuck scan cannot hang the radio thread RadioLib's scanChannel() waits on DIO1 in a while(!digitalRead(irq)) loop with no timeout. If the chip never raises CADDone, isChannelActive() never returns and the radio thread is stuck forever: the queued packet stays in the TX queue, no error is logged and the trace simply stops after the channel-activity check. Boards that hit this can now define SX126X_CAD_TIMEOUT_MS in variant.h. The scan is then driven manually and a timeout is treated as a free channel - the same verdict RadioLib gives for RADIOLIB_CHANNEL_FREE - and RX is restarted because the aborted scan leaves the chip out of receive. Default behaviour is unchanged: without the macro the original blocking scanChannel() is used. Measured on a custom RP2350 + E22-400M33S board over a direct SPI tunnel: CAD never completed even after 200 ms, and the radio thread hung at exactly that point on every transmission attempt. Built for rak4631 (nRF52) and a custom rp2350 variant. --- src/mesh/SX126xInterface.cpp | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index 2400a8e03f6..0eab9c6d00a 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -410,7 +410,30 @@ template bool SX126xInterface::isChannelActive() int16_t result; setTransmitEnable(false); setStandby(); + +#ifdef SX126X_CAD_TIMEOUT_MS + // RadioLib's scanChannel() waits on DIO1 in a `while(!digitalRead(irq))` loop + // with no timeout (SX126x.cpp). If the chip never raises CADDone the radio + // thread is stuck there forever: nothing is transmitted and the stall is + // invisible, because the caller simply never returns. Boards that hit this + // can define SX126X_CAD_TIMEOUT_MS to drive the scan manually and treat a + // timeout as a free channel - the same verdict as RADIOLIB_CHANNEL_FREE. + result = lora.startChannelScan(cfg); + if (result == RADIOLIB_ERR_NONE) { + uint32_t started = millis(); + while (!digitalRead(SX126X_DIO1)) { + if (millis() - started > SX126X_CAD_TIMEOUT_MS) { + LOG_WARN("SX126X CAD did not complete in %u ms, treating channel as free", (unsigned)SX126X_CAD_TIMEOUT_MS); + startReceive(); // the scan left the chip out of RX; put it back + return false; + } + yield(); + } + result = lora.getChannelScanResult(); + } +#else result = lora.scanChannel(cfg); +#endif if (result == RADIOLIB_LORA_DETECTED) return true; if (result != RADIOLIB_CHANNEL_FREE)