Added RX duty-cycle noise floor calibration again (this time it works!)
This commit is contained in:
1 parent
6582e8451f
commit
0af1bf14c5
6 files changed
+84
-7
No files matched your search
@@ -1465,5 +1465,6 @@ bool MyMesh::hasPendingWork() const {
|
||||
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
|
||||
if (radio_driver.isCalibratingNoiseFloor()) return true; // keep MCU awake for the noise-floor window
|
||||
return _mgr->getOutboundTotal() > 0;
|
||||
}
|
||||
@@ -1050,5 +1050,6 @@ bool MyMesh::hasPendingWork() const {
|
||||
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
|
||||
if (radio_driver.isCalibratingNoiseFloor()) return true; // keep MCU awake for the noise-floor window
|
||||
return _mgr->getOutboundTotal() > 0;
|
||||
}
|
||||
@@ -43,7 +43,9 @@ protected:
|
||||
// reconfiguring the radio
|
||||
stopReceiveDutyCycle();
|
||||
}
|
||||
if (!_rx_ps_enabled) {
|
||||
if (!_rx_ps_enabled || _nf_calib_active) {
|
||||
// plain continuous RX: powersaving off, or a periodic noise-floor
|
||||
// calibration window is in progress
|
||||
return _radio->startReceive();
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,9 @@ protected:
|
||||
// silently abort the RX we are about to start
|
||||
stopReceiveDutyCycle();
|
||||
}
|
||||
if (!_rx_ps_enabled) {
|
||||
if (!_rx_ps_enabled || _nf_calib_active) {
|
||||
// plain continuous RX: powersaving off, or a periodic noise-floor
|
||||
// calibration window is in progress
|
||||
return _radio->startReceive();
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,11 @@
|
||||
#define NUM_NOISE_FLOOR_SAMPLES 64
|
||||
#define SAMPLING_THRESHOLD 14
|
||||
|
||||
// periodic noise-floor calibration windows (RX duty-cycle powersaving only)
|
||||
#define NF_CALIB_INTERVAL_MS 60000UL // at least once a minute
|
||||
#define NF_CALIB_TIMEOUT_MS 5000UL // give up on the batch (busy channel)
|
||||
#define NF_CALIB_SETTLE_MS 20UL // frontend/AGC settle after RX entry
|
||||
|
||||
static volatile uint8_t state = STATE_IDLE;
|
||||
|
||||
// this function is called when a complete packet
|
||||
@@ -158,16 +163,60 @@ void RadioLibWrapper::rxPsWatchdogCheck() {
|
||||
}
|
||||
}
|
||||
|
||||
// Periodic noise-floor calibration, active only with RX duty-cycle powersaving:
|
||||
// a duty-cycled receiver can't be sampled reliably (the frontend is off in the
|
||||
// sleep windows and settling right after each wake), so at least once a minute
|
||||
// the receive mode is dropped to plain continuous RX, a fresh sample batch is
|
||||
// collected exactly like the non-powersaving path does, and the duty cycle is
|
||||
// re-armed. The published average stays in _noise_floor as usual.
|
||||
void RadioLibWrapper::noiseFloorCalibCheck() {
|
||||
unsigned long now = millis();
|
||||
if (_nf_calib_active) {
|
||||
if (!_rx_ps_enabled || (long)(now - _nf_calib_deadline) >= 0) {
|
||||
// powersaving turned off mid-window, or the batch couldn't complete
|
||||
// (busy channel / stuck filter) - keep the previous floor
|
||||
endNoiseFloorCalib(now);
|
||||
}
|
||||
} else if (_rx_ps_enabled && _rx_ps_armed && state == STATE_RX
|
||||
&& now - _nf_last_calib >= NF_CALIB_INTERVAL_MS
|
||||
&& !isReceivingPacket()) {
|
||||
// never interrupt an ongoing reception to calibrate (a TX in flight is
|
||||
// already excluded by state == STATE_RX); retries next loop iteration
|
||||
_nf_calib_active = true;
|
||||
_nf_calib_deadline = now + NF_CALIB_TIMEOUT_MS;
|
||||
_nf_sample_from = now + NF_CALIB_SETTLE_MS;
|
||||
_num_floor_samples = 0; // start a fresh batch for this window
|
||||
_floor_sample_sum = 0;
|
||||
state = STATE_IDLE; // recvRaw() re-arms; startReceiveMode() sees the
|
||||
// active flag and starts continuous RX, not duty-cycle
|
||||
}
|
||||
}
|
||||
|
||||
void RadioLibWrapper::endNoiseFloorCalib(unsigned long now) {
|
||||
_nf_calib_active = false;
|
||||
_nf_last_calib = now;
|
||||
// force a receive re-arm back into duty-cycle mode, but don't clobber a
|
||||
// completed-but-unread packet or an in-flight TX (recvRaw()/onSendFinished()
|
||||
// will re-arm right after those anyway; same guard style as setRxPowerSaving)
|
||||
if ((state & STATE_INT_READY) == 0 && (state & ~STATE_INT_READY) != STATE_TX_WAIT) {
|
||||
state = STATE_IDLE;
|
||||
}
|
||||
}
|
||||
|
||||
void RadioLibWrapper::loop() {
|
||||
if (_rx_ps_enabled) {
|
||||
rxPsWatchdogCheck();
|
||||
}
|
||||
noiseFloorCalibCheck();
|
||||
|
||||
if (state == STATE_RX && _num_floor_samples < NUM_NOISE_FLOOR_SAMPLES) {
|
||||
// In RX duty-cycle (powersaving) mode only sample while the chip is in a
|
||||
// listen window: during the sleep window the frontend is off, so the RSSI
|
||||
// is meaningless and the SPI read would stall until the next window.
|
||||
if (!(_rx_ps_armed && isChipBusy()) && !isReceivingPacket()) {
|
||||
// Noise floor is only sampled outside RX duty-cycle mode: continuously in
|
||||
// plain RX (powersaving off), or inside the periodic calibration window
|
||||
// (powersaving on), skipping the first moments after RX entry there while
|
||||
// the frontend/AGC settles (unsettled GetRssiInst reads ~-127 dBm garbage).
|
||||
if (!_rx_ps_armed
|
||||
&& !(_nf_calib_active && (long)(millis() - _nf_sample_from) < 0)
|
||||
&& !isReceivingPacket()) {
|
||||
int rssi = getCurrentRSSI();
|
||||
if (rssi < _noise_floor + SAMPLING_THRESHOLD) { // only consider samples below current floor + sampling THRESHOLD
|
||||
_num_floor_samples++;
|
||||
@@ -182,6 +231,10 @@ void RadioLibWrapper::loop() {
|
||||
_floor_sample_sum = 0;
|
||||
|
||||
MESH_DEBUG_PRINTLN("RadioLibWrapper: noise_floor = %d", (int)_noise_floor);
|
||||
|
||||
if (_nf_calib_active) {
|
||||
endNoiseFloorCalib(millis()); // fresh floor published - back to duty cycle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,9 +48,21 @@ protected:
|
||||
int8_t _cur_dbm;
|
||||
bool _params_valid, _dbm_valid;
|
||||
|
||||
// Periodic noise-floor calibration (only while RX duty-cycle powersaving is
|
||||
// armed): a duty-cycled receiver can't be sampled reliably, so at least once
|
||||
// a minute the wrapper drops to plain continuous RX, collects a fresh sample
|
||||
// batch exactly like the non-powersaving path does, publishes the average
|
||||
// into _noise_floor and re-arms the duty cycle.
|
||||
bool _nf_calib_active;
|
||||
unsigned long _nf_last_calib; // millis of last completed/attempted window
|
||||
unsigned long _nf_calib_deadline; // abort window if the batch can't complete
|
||||
unsigned long _nf_sample_from; // no samples before this (RX entry settle)
|
||||
|
||||
void idle();
|
||||
void startRecv();
|
||||
void rxPsWatchdogCheck();
|
||||
void noiseFloorCalibCheck();
|
||||
void endNoiseFloorCalib(unsigned long now);
|
||||
void cacheParams(float freq, float bw, uint8_t sf, uint8_t cr) {
|
||||
_cur_freq = freq; _cur_bw = bw; _cur_sf = sf; _cur_cr = cr; _params_valid = true;
|
||||
}
|
||||
@@ -73,7 +85,9 @@ public:
|
||||
_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), _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; }
|
||||
_params_valid(false), _dbm_valid(false),
|
||||
_nf_calib_active(false), _nf_last_calib(0), _nf_calib_deadline(0), _nf_sample_from(0)
|
||||
{ n_recv = n_sent = n_recv_errors = n_wd_soft = n_wd_hard = 0; }
|
||||
|
||||
void begin() override;
|
||||
virtual void powerOff() { _radio->sleep(); }
|
||||
@@ -117,6 +131,10 @@ public:
|
||||
// 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; }
|
||||
// true while a periodic noise-floor calibration window is in progress; the
|
||||
// app's hasPendingWork() must keep the MCU awake so the sample batch and the
|
||||
// return to duty-cycle complete promptly
|
||||
bool isCalibratingNoiseFloor() const { return _nf_calib_active; }
|
||||
void resetStats() { n_recv = n_sent = n_recv_errors = 0; }
|
||||
|
||||
virtual float getLastRSSI() const override;
|
||||
|
||||
Reference in new issue
Block a user