SX126x: keep a staged-over frame's RX_DONE across the resume

A CAD skipped because a frame was in flight set keepRxIrqsAtResume only
when RX_DONE or CRC_ERR was already up. With a header but no RX_DONE yet,
the resume cleared RX_DONE, CRC_ERR, HEADER_ERR and TIMEOUT - and on a
CH341 host the flag read in stageTxInRx() and that clear are a USB round
trip apart, so a frame completing in between lost its RX_DONE. The poll
then had no edge to deliver and the frame waited for checkStaleRxFlags()
to retire the latched HEADER_VALID, one max packet later.

Keep RX_DONE and CRC_ERR for every busy verdict, so the resume's
checkRxDoneIrqFlag() drives the readout. HEADER_ERR and TIMEOUT are still
cleared unconditionally: no readout waits on them, and a stale one costs
checkStaleRxFlags() a re-arm. Only RX_DONE is mapped to DIO1, so leaving
the other terminal flag latched holds no interrupt line.

Found by CodeRabbit on #12016; the blanket keep it proposed would have left
a stale HEADER_ERR or TIMEOUT latched instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0112S3QXU8WUGSCcMQHnwRKZ
This commit is contained in:
Jonathan BennettandClaude Opus 5 committed 2026-10-06 22:57:19 +00:00
1 parent 754975b97e
commit 50f8391918
2 files changed
+12 -8

No files matched your search

+11 -7
View File
@@ -543,12 +543,15 @@ template <typename T> bool SX126xInterface<T>::resumeRunningReceive()
#endif
if (!rxArmedContinuous)
return false;
// readData() clears these, but handleReceiveInterrupt()'s early outs do not, and a latched one would hold DIO1
// high past the re-arm. PREAMBLE/HEADER_VALID stay: they may belong to the next frame, already arriving. A scan
// skipped for an unread frame keeps them all, for its readout.
// readData() clears these, but handleReceiveInterrupt()'s early outs do not. PREAMBLE/HEADER_VALID stay: they
// may belong to the next frame, already arriving. A scan skipped for a frame in flight or unread keeps that
// frame's RX_DONE (the only flag on DIO1) and CRC_ERR for its readout, which checkRxDoneIrqFlag() below drives -
// including an RX_DONE that landed in the USB round trip since the scan read the flags. A stale HEADER_ERR or
// TIMEOUT has no readout waiting and would cost checkStaleRxFlags() a re-arm, so it goes either way.
uint32_t clearMask = RADIOLIB_SX126X_IRQ_HEADER_ERR | RADIOLIB_SX126X_IRQ_TIMEOUT;
if (!keepRxIrqs)
lora.clearIrqFlags(RADIOLIB_SX126X_IRQ_RX_DONE | RADIOLIB_SX126X_IRQ_CRC_ERR | RADIOLIB_SX126X_IRQ_HEADER_ERR |
RADIOLIB_SX126X_IRQ_TIMEOUT);
clearMask |= RADIOLIB_SX126X_IRQ_RX_DONE | RADIOLIB_SX126X_IRQ_CRC_ERR;
lora.clearIrqFlags(clearMask);
activeReceiveStart = 0; // as the standby this replaces would
RadioLibInterface::startReceive();
enableInterrupt(isrRxLevel0);
@@ -779,8 +782,9 @@ template <typename T> bool SX126xInterface<T>::stageTxInRx()
// Its readout checks whether our bytes can lie across it
if (irq & (RADIOLIB_SX126X_IRQ_HEADER_VALID | doneIrqs))
noteStagedOverFrame(base, numbytes);
// The busy verdict's rearmReceive() resumes the RX: it must leave a finished frame's flags for its readout
keepRxIrqsAtResume = (irq & doneIrqs) != 0;
// The busy verdict's rearmReceive() resumes the RX: it must leave the frame's terminal flags for its
// readout, whether it had already finished or finishes while we get there
keepRxIrqsAtResume = true;
LOG_DEBUG("TX stage in RX: frame arriving or unread (irq 0x%04x), scan skipped", (unsigned)irq);
return true;
}
+1 -1
View File
@@ -120,7 +120,7 @@ template <class T> class SX126xInterface : public RadioLibInterface
uint8_t rxClobberBase = 0;
size_t rxClobberLen = 0;
uint8_t rxClobberBytes[256];
/** That frame had finished: the next resumeRunningReceive() must not clear its IRQ flags before it is read */
/** A frame was in flight or unread: the next resumeRunningReceive() keeps RX_DONE/CRC_ERR for its readout */
bool keepRxIrqsAtResume = false;
/** A payload written during its backoff: its length (0 if none), packet id, offset and bytes */
size_t earlyStagedLen = 0;