Skip to content
Open
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
60 changes: 39 additions & 21 deletions src/helpers/radiolib/RadioLibWrappers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,22 @@
#define STATE_TX_DONE 4
#define STATE_INT_READY 16

#define NUM_NOISE_FLOOR_SAMPLES 64
#define SAMPLING_THRESHOLD 14

static volatile uint8_t state = STATE_IDLE;

// In-place insertion sort of int16_t samples for the noise-floor median. Runs once per
// calibration block (64 elements, ~every 2 s of idle), so O(n^2) is irrelevant here.
static void sortInt16(int16_t* a, int n) {
for (int i = 1; i < n; i++) {
int16_t key = a[i];
int j = i - 1;
while (j >= 0 && a[j] > key) {
a[j + 1] = a[j];
j--;
}
a[j + 1] = key;
}
}

// this function is called when a complete packet
// is transmitted by the module
static
Expand Down Expand Up @@ -40,7 +51,7 @@ void RadioLibWrapper::begin() {

// start average out some samples
_num_floor_samples = 0;
_floor_sample_sum = 0;
_floor_block_ready = false;
}

uint32_t RadioLibWrapper::getRngSeed() {
Expand All @@ -58,9 +69,9 @@ void RadioLibWrapper::idle() {

void RadioLibWrapper::triggerNoiseFloorCalibrate(int threshold) {
_threshold = threshold;
if (_num_floor_samples >= NUM_NOISE_FLOOR_SAMPLES) { // ignore trigger if currently sampling
if (_num_floor_samples >= NUM_NOISE_FLOOR_SAMPLES) { // restart only once the current block is complete
_num_floor_samples = 0;
_floor_sample_sum = 0;
_floor_block_ready = false;
}
}

Expand All @@ -75,32 +86,39 @@ void RadioLibWrapper::resetAGC() {
doResetAGC();
state = STATE_IDLE; // trigger a startReceive()

// 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.
_noise_floor = 0;
// Discard any in-progress noise-floor block: the analog frontend was just reset, so
// queued RSSI samples are stale. _noise_floor itself is left in place — the median
// estimator no longer drifts to -120 (the reason the old ratchet needed a hard
// _noise_floor = 0 reset), and forcing 0 here would create a brief permissive LBT
// window (margin = RSSI - 0) until the next block completes.
_num_floor_samples = 0;
_floor_sample_sum = 0;
_floor_block_ready = false;
}

void RadioLibWrapper::loop() {
if (state == STATE_RX && _num_floor_samples < NUM_NOISE_FLOOR_SAMPLES) {
if (!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;
}
// Accept every idle sample. The old "rssi < floor + threshold" filter was a one-way
// ratchet: it only ever accepted samples below the current floor, so the block average
// drifted downward to the -120 clamp and never recovered — leaving _noise_floor stuck
// low and the RSSI-margin LBT permanently over-sensitive.
_floor_samples[_num_floor_samples++] = (int16_t)getCurrentRSSI();
}
} else if (_num_floor_samples >= NUM_NOISE_FLOOR_SAMPLES && _floor_sample_sum != 0) {
_noise_floor = _floor_sample_sum / NUM_NOISE_FLOOR_SAMPLES;
} else if (_num_floor_samples >= NUM_NOISE_FLOOR_SAMPLES && !_floor_block_ready) {
// Block complete: reduce to the median. The median rejects transient interference
// spikes (high and low outliers) and recovers in BOTH directions, unlike the ratcheted
// mean. _noise_floor is written only here, so the previous value stays valid while the
// next block is sampled — no reset-to-0, no permissive LBT window during reconvergence.
sortInt16(_floor_samples, NUM_NOISE_FLOOR_SAMPLES);
int16_t median = (int16_t)(((int32_t)_floor_samples[NUM_NOISE_FLOOR_SAMPLES / 2 - 1]
+ (int32_t)_floor_samples[NUM_NOISE_FLOOR_SAMPLES / 2]) / 2);
_noise_floor = median;
if (_noise_floor < -120) {
_noise_floor = -120; // clamp to lower bound of -120dBi
}
_floor_sample_sum = 0;
_floor_block_ready = true;

MESH_DEBUG_PRINTLN("RadioLibWrapper: noise_floor = %d", (int)_noise_floor);
MESH_DEBUG_PRINTLN("RadioLibWrapper: noise_floor = %d (median)", (int)_noise_floor);
}
}

Expand Down
5 changes: 4 additions & 1 deletion src/helpers/radiolib/RadioLibWrappers.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#include <Mesh.h>
#include <RadioLib.h>

#define NUM_NOISE_FLOOR_SAMPLES 64 // RSSI samples reduced to a median per noise-floor calibration block

class RadioLibWrapper : public mesh::Radio {
protected:
PhysicalLayer* _radio;
Expand All @@ -11,7 +13,8 @@ class RadioLibWrapper : public mesh::Radio {
int16_t _noise_floor, _threshold;
bool _cad_enabled;
uint16_t _num_floor_samples;
int32_t _floor_sample_sum;
int16_t _floor_samples[NUM_NOISE_FLOOR_SAMPLES];
bool _floor_block_ready; // true once a full block has been reduced to a median (waits for trigger to restart)
uint8_t _preamble_sf;

void idle();
Expand Down