Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 84 additions & 25 deletions src/helpers/radiolib/RadioLibWrappers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
#define NF_CALIB_INTERVAL_MS 60000UL
#define NF_CALIB_TIMEOUT_MS 5000UL
#define NF_CALIB_SETTLE_MS 20UL
#define NF_CALIB_SAMPLE_INTERVAL_MS 1UL
#define NF_CALIB_MAX_SAMPLE_ATTEMPTS (NUM_NOISE_FLOOR_SAMPLES * 4U)

static volatile uint8_t state = STATE_IDLE;

Expand Down Expand Up @@ -81,20 +83,78 @@ void RadioLibWrapper::doResetAGC() {

void RadioLibWrapper::resetAGC() {
// make sure we're not mid-receive or mid-transmit of a packet
if ((state & STATE_INT_READY) != 0 || isReceivingPacket() || state == STATE_TX_WAIT) return;
if (isPacketPendingOrReceiving() || state == STATE_TX_WAIT) return;

if (_rx_ps_armed) stopReceiveDutyCycle();

doResetAGC();
state = STATE_IDLE; // trigger a startReceive()
state = STATE_IDLE;

// Reset noise floor sampling so it reconverges from scratch.
// Without this, a stuck _noise_floor of -120 makes the sampling threshold
// too low (-106) to accept normal samples (~-105), self-reinforcing the
// stuck value even after the receiver has recovered.
// Recalibrate synchronously so callers never observe the temporary zero
// used to bypass a stale sampling threshold after an AGC reset.
const int16_t previous_noise_floor = _noise_floor;
_noise_floor = 0;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the culpit.
Even with this PR, there will be a very slim chance to see noisefloor 0.

IMO, I will experiment a different default noise floor if someone still sess noise floor 0.

_num_floor_samples = 0;
_floor_sample_sum = 0;

_nf_calib_active = true; // force startReceiveMode() into continuous RX
bool packet_in_progress = false;
int16_t err = startReceiveMode();
if (err == RADIOLIB_ERR_NONE) {
state = STATE_RX;
delay(NF_CALIB_SETTLE_MS);

uint16_t sample_attempts = 0;
while (_num_floor_samples < NUM_NOISE_FLOOR_SAMPLES &&
sample_attempts++ < NF_CALIB_MAX_SAMPLE_ATTEMPTS) {
if (isPacketPendingOrReceiving()) {
packet_in_progress = true;
break;
}
sampleNoiseFloorOnce();
if (_num_floor_samples < NUM_NOISE_FLOOR_SAMPLES) {
delay(NF_CALIB_SAMPLE_INTERVAL_MS);
}
}
}

if (!publishNoiseFloor()) {
_noise_floor = previous_noise_floor;
_num_floor_samples = 0;
_floor_sample_sum = 0;
}

_nf_calib_active = false;
_nf_last_calib = millis();

// A frame can start after resetAGC()'s initial idle check. Keep continuous
// RX alive until recvRaw() consumes it; restarting RX here would abort a
// frame that is between preamble detection and RX_DONE.
if (!packet_in_progress) packet_in_progress = isPacketPendingOrReceiving();
if (!packet_in_progress) requestRestartRecv();
}

void RadioLibWrapper::sampleNoiseFloorOnce() {
int rssi = getCurrentRSSI();
if (rssi < _noise_floor + SAMPLING_THRESHOLD) {
_num_floor_samples++;
_floor_sample_sum += rssi;
}
}

bool RadioLibWrapper::publishNoiseFloor() {
if (_num_floor_samples < NUM_NOISE_FLOOR_SAMPLES || _floor_sample_sum == 0) return false;

_noise_floor = _floor_sample_sum / NUM_NOISE_FLOOR_SAMPLES;
if (_noise_floor < -120) {
_noise_floor = -120; // clamp to lower bound of -120dBi
}
_floor_sample_sum = 0;

#ifdef MESH_DEBUG_NOISE_FLOOR
MESH_DEBUG_PRINTLN("RadioLibWrapper: noise_floor = %d", (int)_noise_floor);
#endif
return true;
}

// Clear the RX/idle state so the next loop calls startRecv() again, without
Expand All @@ -109,28 +169,35 @@ void RadioLibWrapper::requestRestartRecv() {
interrupts();
}

bool RadioLibWrapper::isPacketPendingOrReceiving() {
return (state & STATE_INT_READY) != 0 || isReceivingPacket();
}

void RadioLibWrapper::noiseFloorCalibCheck() {
unsigned long now = millis();
if (_nf_calib_active) {
if (!_rx_ps_enabled || (long)(now - _nf_calib_deadline) >= 0) {
endNoiseFloorCalib(now);
} else if (_rx_ps_armed && !isPacketPendingOrReceiving()) {
// A packet may have delayed the RXPS-to-continuous-RX transition.
requestRestartRecv();
}
} else if (_rx_ps_enabled && _rx_ps_armed && state == STATE_RX &&
(_nf_last_calib == 0 || now - _nf_last_calib >= NF_CALIB_INTERVAL_MS) &&
!isReceivingPacket()) {
!isPacketPendingOrReceiving()) {
_nf_calib_active = true;
_nf_calib_deadline = now + NF_CALIB_TIMEOUT_MS;
_nf_sample_from = now + NF_CALIB_SETTLE_MS;
_num_floor_samples = 0;
_floor_sample_sum = 0;
requestRestartRecv();
if (!isPacketPendingOrReceiving()) requestRestartRecv();
}
}

void RadioLibWrapper::endNoiseFloorCalib(unsigned long now) {
_nf_calib_active = false;
_nf_last_calib = now;
requestRestartRecv();
if (!isPacketPendingOrReceiving()) requestRestartRecv();
}

void RadioLibWrapper::loop() {
Expand All @@ -139,23 +206,9 @@ void RadioLibWrapper::loop() {
if (state == STATE_RX && _num_floor_samples < NUM_NOISE_FLOOR_SAMPLES) {
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++;
_floor_sample_sum += rssi;
}
sampleNoiseFloorOnce();
}
} else if (_num_floor_samples >= NUM_NOISE_FLOOR_SAMPLES && _floor_sample_sum != 0) {
_noise_floor = _floor_sample_sum / NUM_NOISE_FLOOR_SAMPLES;
if (_noise_floor < -120) {
_noise_floor = -120; // clamp to lower bound of -120dBi
}
_floor_sample_sum = 0;

#ifdef MESH_DEBUG_NOISE_FLOOR
MESH_DEBUG_PRINTLN("RadioLibWrapper: noise_floor = %d", (int)_noise_floor);
#endif

} else if (publishNoiseFloor()) {
if (_nf_calib_active) endNoiseFloorCalib(millis());
}
}
Expand All @@ -173,6 +226,12 @@ void RadioLibWrapper::startRecv() {
}

int16_t RadioLibWrapper::startReceiveMode() {
// Periodic calibration switches RXPS to continuous RX. Re-check at the
// hardware transition so a preamble that arrived after the scheduler's
// idle check is not aborted by stopReceiveDutyCycle().
if (_nf_calib_active && _rx_ps_armed && isPacketPendingOrReceiving()) {
return RADIOLIB_ERR_NONE;
}
if (_rx_ps_armed) stopReceiveDutyCycle();
if (!_rx_ps_enabled || _nf_calib_active) {
_rx_ps_armed = false;
Expand Down
3 changes: 3 additions & 0 deletions src/helpers/radiolib/RadioLibWrappers.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ class RadioLibWrapper : public mesh::Radio, public RxPowerSavingControl {
void idle();
void startRecv();
void requestRestartRecv();
bool isPacketPendingOrReceiving();
void prepareForRadioConfig();
void sampleNoiseFloorOnce();
bool publishNoiseFloor();
void noiseFloorCalibCheck();
void endNoiseFloorCalib(unsigned long now);
int16_t startReceiveMode();
Expand Down
Loading