Files
firmware/src
Jonathan BennettandClaude Opus 5 1c366c2d05 Radio: end a CAD handoff whose RX hits a damaged header (#12144)
* Radio: end a CAD handoff whose RX hits a damaged header

When CAD hands off to RX and that RX ends on a damaged header (CRC_ERR or
HEADER_ERR with no RX_DONE), the RX readout task cleared those flags and
returned. Nothing was notified and cadHandoffRxStart stayed set, so the
radio thread never re-armed. Where the chip had stopped listening, the node
stayed deaf until the main loop's backstop logged "CAD>RX timeout" and
re-armed: 200 ms to over a second later. Before the readout task, the same
edge went to handleReceiveInterrupt(), which read the error, cleared the
flags and re-armed.

The bench saw this on an LR2021 (X1) running develop: 5 "CAD>RX timeout"
lines across 3 runs, where pre-#11983 develop logged none in 4 runs.

Now the task hands every frameless end of a handoff's RX (TIMEOUT, CRC_ERR,
HEADER_ERR) to the radio thread, which clears the flags through
handleReceiveInterrupt() and re-arms, as it already did for TIMEOUT.

Co-Authored-By: Jonathan Bennett <jbennett@incomsystems.biz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01REkPVFh6kvG4AZJ5A8AtM9

* Radio: don't strand a damaged header's flags when its notification is dropped

Three follow-ups to the handoff end, from review of the first commit.

The readout task's notify(ISR_RX, !rxArmedBeforeTxDone) deliberately does not
overwrite a pending TX notification, so the RX notification can be dropped.
That is pre-existing for the TIMEOUT case, and the ISR_TX path does put the
chip back into RX, but the first commit moved the clearing of CRC_ERR /
HEADER_ERR to the radio thread, so in the dropped case nothing clears them
until checkCadHandoffTimeout(). notify() returns false exactly when it
dropped, so clear them at that site in that case, as the code did before, and
leave the open handoff to the backstop.

handleReceiveInterrupt() logged "CAD>RX pkt" for a frameless end, and read a
length that is not a frame's. A damaged header now takes the same exit as an
empty window, with its own log line, so a capture says which of the two
happened and getPacketLength() is not asked about a packet that never
arrived. rxBad is unchanged either way: neither case counted before.

An empty window inside a handoff can carry a damaged header's flags alongside
the timeout, both routed to DIO1, so that exit clears them together. Plain RX
is untouched there and still leaves HEADER_ERR to checkStaleRxFlags().

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTiifXrVc4YdonmLRCeWhw

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:02:48 +00:00
..
…
2026-10-14 15:18:24 +00:00
2026-10-14 15:18:24 +00:00
…
…
2026-10-14 15:18:24 +00:00
…
…
2026-10-14 15:18:24 +00:00
…
2026-10-13 19:50:53 +00:00
…
…
…