fix(power): use XOSC standby to keep TCXO on through CAD→RX; add burst window

- cadWake() now sets standbyXOSC=true so RadioLib's internal standby() calls
  keep the TCXO powered continuously through the CAD scan and into startReceive,
  eliminating the freq-drift window that dropped ~50% of long packets
- PS_RX_TIMEOUT_MS 1500→3000 to cover 256 B packets at SF8/BW62.5/CR5 (~1925 ms)
- 500 ms burst window after each successful receive: skip inter-scan sleep so
  back-to-back messages are caught without waiting out the 65 ms cadence

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-06-10 18:01:39 +02:00
parent 25d154d6d0
commit 25341e8b8f
3 changed files with 18 additions and 6 deletions

View File

@@ -41,14 +41,15 @@ public:
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); } void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
// Warm sleep between CAD scans: ~1.5 µA vs ~600 µA in standby-RC. // Warm sleep between CAD scans: ~1.5 µA vs ~600 µA in standby-RC.
// cadWake() delays 5 ms so the TCXO fully re-stabilises before the next scan // cadWake() sets standbyXOSC=true so every subsequent internal standby() call
// (previous attempt without the delay corrupted long packets via freq drift). // keeps the TCXO powered — no gap between CAD and startReceive, no freq drift.
// The chip's programmed TCXO timeout (set by begin()) handles stabilisation.
void cadSleep() override { void cadSleep() override {
((CustomSX1262 *)_radio)->sleep(true); // warm sleep — config retained ((CustomSX1262 *)_radio)->sleep(true); // warm sleep — config retained
} }
void cadWake() override { void cadWake() override {
_radio->standby(); ((SX126x *)_radio)->standbyXOSC = true; // all subsequent standby() → XOSC
delayMicroseconds(5000); // TCXO stabilisation after warm sleep _radio->standby(); // wake into standby-XOSC, TCXO on
} }
void setRxBoostedGainMode(bool en) override { void setRxBoostedGainMode(bool en) override {

View File

@@ -24,8 +24,13 @@
// → 65 ms gives 2 guaranteed windows with margin. Revisit if SF/BW change. // → 65 ms gives 2 guaranteed windows with margin. Revisit if SF/BW change.
#define PS_CAD_INTERVAL_MS 65 #define PS_CAD_INTERVAL_MS 65
// If CAD detects activity but no packet actually lands, fall back to scanning // If CAD detects activity but no packet actually lands, fall back to scanning
// instead of sitting in continuous RX forever. // instead of sitting in continuous RX forever. Must exceed the longest possible
#define PS_RX_TIMEOUT_MS 1500 // packet air-time: 256 B at SF8/BW62.5/CR5 ≈ 1925 ms from preamble start, so
// 3000 ms gives ~1 s margin for a false-detect case.
#define PS_RX_TIMEOUT_MS 3000
// After a successful receive, skip inter-scan sleep for this long so back-to-back
// messages are caught without waiting out the 65 ms window each time.
#define PS_BURST_WINDOW_MS 500
static volatile uint8_t state = STATE_IDLE; static volatile uint8_t state = STATE_IDLE;
@@ -160,6 +165,10 @@ void RadioLibWrapper::armRecv() {
} }
void RadioLibWrapper::enterCadSleep() { void RadioLibWrapper::enterCadSleep() {
if ((int32_t)(millis() - _ps_burst_until) < 0) {
armRecv(); // burst window: skip sleep, scan again immediately
return;
}
cadSleep(); // warm sleep (SX126x) or standby fallback between scans cadSleep(); // warm sleep (SX126x) or standby fallback between scans
_ps_phase = PS_SLEEPING; _ps_phase = PS_SLEEPING;
_ps_timer = millis() + PS_CAD_INTERVAL_MS; _ps_timer = millis() + PS_CAD_INTERVAL_MS;
@@ -228,6 +237,7 @@ int RadioLibWrapper::recvRaw(uint8_t* bytes, int sz) {
} else { } else {
// Serial.print(" readData() -> "); Serial.println(len); // Serial.print(" readData() -> "); Serial.println(len);
n_recv++; n_recv++;
_ps_burst_until = millis() + PS_BURST_WINDOW_MS;
} }
} }
state = STATE_IDLE; // need another startReceive() state = STATE_IDLE; // need another startReceive()

View File

@@ -27,6 +27,7 @@ protected:
bool _power_save = false; bool _power_save = false;
uint8_t _ps_phase = 0; // PS_* (see .cpp): scanning / sleeping / rxing uint8_t _ps_phase = 0; // PS_* (see .cpp): scanning / sleeping / rxing
uint32_t _ps_timer = 0; // next-scan time, or RX false-detect deadline uint32_t _ps_timer = 0; // next-scan time, or RX false-detect deadline
uint32_t _ps_burst_until = 0; // scan continuously until this time after RX
void armRecv(); // arm RX honouring power-save (CAD) or continuous void armRecv(); // arm RX honouring power-save (CAD) or continuous
void powerSaveLoop(); void powerSaveLoop();
void enterCadSleep(); void enterCadSleep();