mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-09-12 16:05:40 +00:00
Improved RX duty-cycle watchdog observation with esp32 sleep in mind
This commit is contained in:
@@ -1464,5 +1464,6 @@ bool MyMesh::hasPendingWork() const {
|
||||
#if defined(WITH_BRIDGE)
|
||||
if (bridge.isRunning()) return true; // bridge needs WiFi radio, can't sleep
|
||||
#endif
|
||||
if (radio_driver.isWatchdogObserving()) return true; // keep MCU awake for one radio duty cycle
|
||||
return _mgr->getOutboundTotal() > 0;
|
||||
}
|
||||
|
||||
@@ -1049,5 +1049,6 @@ bool MyMesh::hasPendingWork() const {
|
||||
#if defined(WITH_BRIDGE)
|
||||
if (bridge.isRunning()) return true; // bridge needs WiFi radio, can't sleep
|
||||
#endif
|
||||
if (radio_driver.isWatchdogObserving()) return true; // keep MCU awake for one radio duty cycle
|
||||
return _mgr->getOutboundTotal() > 0;
|
||||
}
|
||||
|
||||
@@ -88,7 +88,12 @@ void RadioLibWrapper::resetAGC() {
|
||||
|
||||
void RadioLibWrapper::rxPsWatchdogCheck() {
|
||||
// don't interfere mid-transmit or with a completed-but-unread packet
|
||||
if ((state & STATE_INT_READY) != 0 || (state & ~STATE_INT_READY) == STATE_TX_WAIT) return;
|
||||
// (a pending DIO1 event is itself proof the radio is alive; recvRaw() will
|
||||
// re-arm and re-base the watchdog)
|
||||
if ((state & STATE_INT_READY) != 0 || (state & ~STATE_INT_READY) == STATE_TX_WAIT) {
|
||||
_wd_observe_until = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned long now = millis();
|
||||
bool tripped = false;
|
||||
@@ -96,12 +101,35 @@ void RadioLibWrapper::rxPsWatchdogCheck() {
|
||||
if (_rx_ps_armed && state == STATE_RX && _wd_stuck_thresh > 0) {
|
||||
bool busy = isChipBusy();
|
||||
if (busy != _wd_last_busy) {
|
||||
// the sleep/listen wave is present -> radio healthy
|
||||
_wd_last_busy = busy;
|
||||
_wd_last_transition = now;
|
||||
_wd_stage = 0; // the sleep/listen wave is present -> radio healthy
|
||||
_wd_stage = 0;
|
||||
_wd_strikes = 0;
|
||||
_wd_observe_until = 0;
|
||||
} else if (_wd_observe_until != 0) {
|
||||
// active observation window in progress (MCU kept awake via
|
||||
// isWatchdogObserving()); a healthy chip must toggle BUSY within it
|
||||
if ((long)(now - _wd_observe_until) >= 0) {
|
||||
_wd_observe_until = 0;
|
||||
if (!busy && isReceivingPacket()) {
|
||||
// BUSY held low by an ongoing reception (extended RX) - alive
|
||||
_wd_last_transition = now;
|
||||
_wd_strikes = 0;
|
||||
} else if (++_wd_strikes >= 2) {
|
||||
_wd_strikes = 0;
|
||||
tripped = true;
|
||||
} else {
|
||||
_wd_last_transition = now; // full threshold before the next window
|
||||
}
|
||||
}
|
||||
} else if (now - _wd_last_transition > _wd_stuck_thresh) {
|
||||
tripped = true; // BUSY frozen: chip fell out of the duty cycle with no IRQ
|
||||
// no proof of life for too long: actively watch one full cycle
|
||||
_wd_observe_until = now + _wd_observe_ms;
|
||||
if (_wd_observe_until == 0) _wd_observe_until = 1; // 0 means "off"
|
||||
}
|
||||
} else {
|
||||
_wd_observe_until = 0;
|
||||
}
|
||||
if (_startrx_fails >= 3) tripped = true; // can't even re-arm receive mode
|
||||
|
||||
@@ -109,6 +137,7 @@ void RadioLibWrapper::rxPsWatchdogCheck() {
|
||||
|
||||
_wd_last_transition = now; // grace period before the next escalation
|
||||
_startrx_fails = 0;
|
||||
_wd_observe_until = 0;
|
||||
|
||||
if (_wd_stage == 0) {
|
||||
_wd_stage = 1;
|
||||
@@ -168,9 +197,15 @@ void RadioLibWrapper::startRecv() {
|
||||
// Longest legitimate silence on the BUSY pin: one full cycle, plus the
|
||||
// extended RX after a (possibly false) preamble detect (2*rx + sleep),
|
||||
// plus a worst-case packet airtime, plus margin for TCXO/transitions.
|
||||
// Floored at 60s so a light-sleeping MCU (ESP32 wakes every ~30s) opens
|
||||
// an observation window every couple of wakeups instead of on each one.
|
||||
uint32_t rx_ms = _rx_ps_rx_us / 1000, sleep_ms = _rx_ps_sleep_us / 1000;
|
||||
_wd_stuck_thresh = (rx_ms + sleep_ms) + 2 * (2 * rx_ms + sleep_ms)
|
||||
+ getEstAirtimeFor(MAX_TRANS_UNIT) + 1000;
|
||||
if (_wd_stuck_thresh < 60000) _wd_stuck_thresh = 60000;
|
||||
// active observation window must cover one full duty cycle
|
||||
_wd_observe_ms = rx_ms + sleep_ms + 50;
|
||||
if (_wd_observe_ms > 1500) _wd_observe_ms = 1500;
|
||||
}
|
||||
} else {
|
||||
if (_startrx_fails < 255) _startrx_fails++;
|
||||
|
||||
@@ -28,11 +28,18 @@ protected:
|
||||
// RX duty-cycle watchdog: a healthy duty cycle shows a square wave on the
|
||||
// BUSY pin (high in the sleep window / TCXO warmup, low while listening).
|
||||
// If the wave stops, the chip fell out of the cycle without an IRQ.
|
||||
// Passive sampling only works while the main loop spins; on MCUs that light
|
||||
// sleep between wakeups the watchdog instead opens an "active observation"
|
||||
// window (isWatchdogObserving() keeps the MCU awake) spanning one full radio
|
||||
// cycle - a healthy chip must toggle BUSY within it.
|
||||
bool _wd_last_busy;
|
||||
uint8_t _wd_stage; // 0 = healthy, 1 = soft re-arm done, 2 = hard reset done
|
||||
uint8_t _wd_strikes; // consecutive failed observation windows
|
||||
uint8_t _startrx_fails; // consecutive startReceiveMode() failures
|
||||
unsigned long _wd_last_transition; // millis of last BUSY level change
|
||||
unsigned long _wd_stuck_thresh; // ms without a transition considered stuck
|
||||
unsigned long _wd_last_transition; // millis of last BUSY level change (proof of life)
|
||||
unsigned long _wd_stuck_thresh; // ms without proof of life before observing
|
||||
unsigned long _wd_observe_until; // 0 = not observing, else millis deadline
|
||||
uint32_t _wd_observe_ms; // observation window: one full cycle + margin
|
||||
uint32_t n_wd_soft, n_wd_hard;
|
||||
|
||||
// last applied radio settings, reapplied after a hard radio reset
|
||||
@@ -64,7 +71,8 @@ public:
|
||||
RadioLibWrapper(PhysicalLayer& radio, mesh::MainBoard& board)
|
||||
: _radio(&radio), _board(&board), _preamble_sf(0), _rx_ps_enabled(false), _rx_ps_armed(false),
|
||||
_rx_ps_rx_us(RX_PS_FALLBACK_RX_US), _rx_ps_sleep_us(RX_PS_FALLBACK_SLEEP_US),
|
||||
_wd_last_busy(false), _wd_stage(0), _startrx_fails(0), _wd_last_transition(0), _wd_stuck_thresh(0),
|
||||
_wd_last_busy(false), _wd_stage(0), _wd_strikes(0), _startrx_fails(0), _wd_last_transition(0),
|
||||
_wd_stuck_thresh(0), _wd_observe_until(0), _wd_observe_ms(0),
|
||||
_params_valid(false), _dbm_valid(false) { n_recv = n_sent = n_recv_errors = n_wd_soft = n_wd_hard = 0; }
|
||||
|
||||
void begin() override;
|
||||
@@ -106,6 +114,9 @@ public:
|
||||
uint32_t getPacketsSent() const { return n_sent; }
|
||||
uint32_t getRxPsWatchdogSoftCount() const { return n_wd_soft; }
|
||||
uint32_t getRxPsWatchdogHardCount() const { return n_wd_hard; }
|
||||
// true while the watchdog is actively watching for BUSY transitions; used by
|
||||
// the app's hasPendingWork() to keep the MCU out of light sleep for the window
|
||||
bool isWatchdogObserving() const { return _wd_observe_until != 0; }
|
||||
void resetStats() { n_recv = n_sent = n_recv_errors = 0; }
|
||||
|
||||
virtual float getLastRSSI() const override;
|
||||
|
||||
Reference in New Issue
Block a user