mirror of
https://github.com/meshtastic/firmware.git
synced 2026-10-31 04:19:58 -04:00
LoRa LBT: hold TX on a seen preamble instead of judging it by a deadline
receiveDetected() declared a bare PREAMBLE_DETECTED false once 2 * preambleTimeMsec passed without HEADER_VALID - about 8 ms at SF7/BW500. Nothing reads the flags on that cadence: they are latched status bits, never routed to DIO, and the only reader is the TX path, once per CSMA backoff (0-56 ms at SF7/BW500). The verdict was usually reached by a read older than its own deadline, and the queued TX went out over the frame. Reported in #11933 by @jp-bennett, whose two-node bench measured own TX a median 43 ms after the verdict, inside a ~102 ms frame. This branch's longer CAD slot widens it to LONG_FAST: 0-308 ms of backoff at CWmin against a 262 ms deadline. A read that sees a bare preamble now clears PREAMBLE_DETECTED and holds TX for one max-length airtime. The clear touches only the IRQ register (ClearIrqStatus/ClearIrq on SX126x, SX1280, LR11x0, LR20x0), so it never aborts a reception. Reads during the hold report busy, so the normal random backoff still spreads retries, and any RX interrupt ends the hold early. A preamble that fires again during the hold stays latched, and the first read after it holds again: a source that keeps triggering keeps deferring TX, one airtime at a time, while a single stale flag cannot block it. HEADER_VALID is never cleared here - RadioLib's readData() reads it at RX_DONE, and on LR2021 its absence reports the packet as header-damaged - and keeps its one-airtime ageing. The SX126x software IRQ poll routes its bare-preamble case through the same hold instead of clearing on every tick while a TX is queued, which would have released TX before the next read could see a refire. That poll read the private txQueue directly, so meshnology_w10 - the only env that defines LORA_DIO1_SOFTWARE_POLL - did not compile. hasQueuedTx() exposes the one fact it needs. #11937 addresses the same defect by deferring the verdict when the previous read was stale; the two conflict in receiveDetected().
This commit is contained in:
1 parent
45262f8257
commit
be3bf1f381
3 files changed
+54
-28
No files matched your search
@@ -119,31 +119,50 @@ bool RadioLibInterface::canSendImmediately()
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RadioLibInterface::preambleHoldActive()
|
||||
{
|
||||
// Whatever sent the cleared preamble is off the air one max packet later.
|
||||
if (preambleHoldStart && !Throttle::isWithinTimespanMs(
|
||||
preambleHoldStart, getPacketTime(meshtastic_Constants_DATA_PAYLOAD_LEN + sizeof(PacketHeader))))
|
||||
preambleHoldStart = 0;
|
||||
return preambleHoldStart != 0;
|
||||
}
|
||||
|
||||
void RadioLibInterface::holdOnPreamble()
|
||||
{
|
||||
// During a hold a refire stays latched, so the first look after it sees an external source and holds again.
|
||||
if (preambleHoldActive())
|
||||
return;
|
||||
iface->clearIrq(1UL << RADIOLIB_IRQ_PREAMBLE_DETECTED);
|
||||
preambleHoldStart = Time::skipZero(Time::getMillis());
|
||||
LOG_TRACE("Preamble seen, cleared, holding TX");
|
||||
}
|
||||
|
||||
bool RadioLibInterface::receiveDetected(uint16_t irq, unsigned long syncWordHeaderValidFlag, unsigned long preambleDetectedFlag)
|
||||
{
|
||||
bool detected = (irq & (syncWordHeaderValidFlag | preambleDetectedFlag));
|
||||
// Handle false detections
|
||||
if (detected) {
|
||||
if (preambleHoldActive())
|
||||
return true;
|
||||
|
||||
if (irq & syncWordHeaderValidFlag) {
|
||||
if (!activeReceiveStart) {
|
||||
activeReceiveStart = Time::skipZero(Time::getMillis());
|
||||
} else if (!Throttle::isWithinTimespanMs(activeReceiveStart, 2 * preambleTimeMsec)) {
|
||||
if (!(irq & syncWordHeaderValidFlag)) {
|
||||
// The HEADER_VALID flag should be set by now if it was really a packet, so ignore PREAMBLE_DETECTED flag
|
||||
activeReceiveStart = 0;
|
||||
LOG_TRACE("Ignore false preamble detection");
|
||||
return false;
|
||||
} else {
|
||||
uint32_t maxPacketTimeMsec = getPacketTime(meshtastic_Constants_DATA_PAYLOAD_LEN + sizeof(PacketHeader));
|
||||
if (!Throttle::isWithinTimespanMs(activeReceiveStart, maxPacketTimeMsec)) {
|
||||
// We should have gotten an RX_DONE IRQ by now if it was really a packet, so ignore HEADER_VALID flag
|
||||
activeReceiveStart = 0;
|
||||
LOG_TRACE("Ignore false header detection");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (!Throttle::isWithinTimespanMs(activeReceiveStart,
|
||||
getPacketTime(meshtastic_Constants_DATA_PAYLOAD_LEN + sizeof(PacketHeader)))) {
|
||||
// We should have gotten an RX_DONE IRQ by now if it was really a packet, so ignore HEADER_VALID flag
|
||||
activeReceiveStart = 0;
|
||||
LOG_TRACE("Ignore false header detection");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return detected;
|
||||
|
||||
if (irq & preambleDetectedFlag) {
|
||||
// Looks come once per CSMA backoff, too rarely to judge a preamble by symbol-time deadline (#11933).
|
||||
// Clear it so the next look sees only a fresh one, and hold TX meanwhile; a clear never aborts RX.
|
||||
holdOnPreamble();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Send a packet (possibly by enquing in a private fifo). This routine will
|
||||
@@ -616,6 +635,7 @@ void RadioLibInterface::handleReceiveInterrupt()
|
||||
// Condition?
|
||||
const bool wasCadHandoff = cadHandoffRxStart != 0;
|
||||
cadHandoffRxStart = 0; // this RX ends the wait either way; the outcome is logged below
|
||||
preambleHoldStart = 0; // likewise the reception a held preamble announced
|
||||
|
||||
if (!isReceiving) {
|
||||
LOG_ERROR("handleReceiveInterrupt called while not in rx mode");
|
||||
|
||||
@@ -352,6 +352,17 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
|
||||
|
||||
protected:
|
||||
uint32_t activeReceiveStart = 0;
|
||||
// Time::getMillis() when a look cleared PREAMBLE_DETECTED and began holding TX, or 0 if no hold.
|
||||
uint32_t preambleHoldStart = 0;
|
||||
|
||||
/** True while a cleared preamble still holds TX; ends the hold once one max packet has passed. */
|
||||
bool preambleHoldActive();
|
||||
|
||||
/** Clear a bare PREAMBLE_DETECTED and hold TX one max packet, unless a hold is already running. */
|
||||
void holdOnPreamble();
|
||||
|
||||
/** Whether a packet is waiting to transmit; txQueue itself stays private. */
|
||||
bool hasQueuedTx() { return !txQueue.empty(); }
|
||||
|
||||
bool receiveDetected(uint16_t irq, unsigned long syncWordHeaderValidFlag, unsigned long preambleDetectedFlag);
|
||||
|
||||
|
||||
@@ -372,16 +372,11 @@ template <typename T> void SX126xInterface<T>::handleSoftwareLoraIrqPoll()
|
||||
RADIOLIB_SX126X_IRQ_RX_DONE | RADIOLIB_SX126X_IRQ_TIMEOUT | RADIOLIB_SX126X_IRQ_CRC_ERR | RADIOLIB_SX126X_IRQ_HEADER_ERR;
|
||||
const uint16_t noisyRxMask = RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED | RADIOLIB_SX126X_IRQ_HEADER_VALID;
|
||||
|
||||
// A bare PREAMBLE (no terminal RX event yet) means the chip is mid-reception. Do NOT treat it as a
|
||||
// full RX event - that would repeatedly trigger readData(). The escape hatch (a noise preamble that
|
||||
// never completes must not permanently block our own TX) runs only while a TX is queued, and clears
|
||||
// ONLY the preamble. HEADER_VALID is evidence of a real inbound frame, so keep it latched: clearing
|
||||
// it would make isActivelyReceiving() read idle mid-payload and let the queued TX stomp the frame.
|
||||
// A stuck header can't block TX forever - receiveDetected() ages it out after maxPacketTimeMsec,
|
||||
// and readData() clears everything once a real RX_DONE arrives.
|
||||
// A bare PREAMBLE is mid-reception, not an RX event: readData() here would run on nothing. With a TX
|
||||
// queued it goes through the same hold as the TX-path look; HEADER_VALID stays latched for readData().
|
||||
const bool preambleOnly = (irq & RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED) && !(irq & RADIOLIB_SX126X_IRQ_HEADER_VALID);
|
||||
if (!pollTxMode && !txQueue.empty() && preambleOnly && ((irq & ~noisyRxMask) == 0U)) {
|
||||
lora.clearIrqFlags(RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED);
|
||||
if (!pollTxMode && hasQueuedTx() && preambleOnly && ((irq & ~noisyRxMask) == 0U)) {
|
||||
holdOnPreamble();
|
||||
scheduleIrqPollTick();
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user