mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-10-22 22:11:56 +00:00
SmartCAD p1
This commit is contained in:
1 parent
0d70256d2e
commit
dc1239537e
36 files changed
+1154
-24
No files matched your search
@@ -693,6 +693,31 @@ void ZephyrDataStore::loadPrefs(NodePrefs &prefs)
|
||||
} else {
|
||||
prefs.v_battery_alert_mv = 0xFFFF;
|
||||
}
|
||||
|
||||
/* Offset 155: cad_auto (ZephCore extension, default 0 = dry-run) */
|
||||
if (off < len) {
|
||||
prefs.cad_auto = buf[off++];
|
||||
if (prefs.cad_auto > 1) {
|
||||
prefs.cad_auto = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Offset 156: cad_offset (ZephCore extension, signed -4..4, default 0) */
|
||||
if (off < len) {
|
||||
prefs.cad_offset = (int8_t)buf[off++];
|
||||
if (prefs.cad_offset < -4 || prefs.cad_offset > 4) {
|
||||
prefs.cad_offset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Offset 157: cad_probe_interval (ZephCore extension, seconds; 0 = off).
|
||||
* Absent in pre-existing files → keep the in-RAM default (60). */
|
||||
if (off < len) {
|
||||
prefs.cad_probe_interval = buf[off++];
|
||||
if (prefs.cad_probe_interval != 0 && prefs.cad_probe_interval < 10) {
|
||||
prefs.cad_probe_interval = 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ZephyrDataStore::savePrefs(const NodePrefs &prefs)
|
||||
@@ -774,7 +799,13 @@ void ZephyrDataStore::savePrefs(const NodePrefs &prefs)
|
||||
/* Offset 153: v_battery_alert_mv (ZephCore extension, 2 bytes LE) */
|
||||
buf[off++] = prefs.v_battery_alert_mv & 0xFF;
|
||||
buf[off++] = (prefs.v_battery_alert_mv >> 8) & 0xFF;
|
||||
/* Total: 155 bytes */
|
||||
/* Offset 155: cad_auto (ZephCore extension) */
|
||||
buf[off++] = prefs.cad_auto;
|
||||
/* Offset 156: cad_offset (ZephCore extension, signed) */
|
||||
buf[off++] = (uint8_t)prefs.cad_offset;
|
||||
/* Offset 157: cad_probe_interval (ZephCore extension, seconds) */
|
||||
buf[off++] = prefs.cad_probe_interval;
|
||||
/* Total: 158 bytes */
|
||||
|
||||
bool ok = atomicReplaceFile(PREFS_FILE, buf, off);
|
||||
LOG_DBG("savePrefs: wrote %s, ok=%d (%d bytes), name='%.16s'",
|
||||
|
||||
@@ -84,4 +84,19 @@ uint32_t LR1110Radio::hwWakeupTimeUs()
|
||||
return lr11xx_get_wakeup_time_us(_dev);
|
||||
}
|
||||
|
||||
int LR1110Radio::hwCadProbe(int8_t level)
|
||||
{
|
||||
return lr11xx_cad_probe(_dev, level);
|
||||
}
|
||||
|
||||
void LR1110Radio::hwCadSetPeakOffset(int8_t offset)
|
||||
{
|
||||
lr11xx_cad_set_peak_offset(_dev, offset);
|
||||
}
|
||||
|
||||
uint8_t LR1110Radio::hwCadBasePeak()
|
||||
{
|
||||
return lr11xx_cad_base_peak(_dev);
|
||||
}
|
||||
|
||||
} /* namespace mesh */
|
||||
@@ -29,6 +29,9 @@ protected:
|
||||
void hwSetRxBoost(bool enable) override;
|
||||
void hwResetAGC() override;
|
||||
uint32_t hwWakeupTimeUs() override;
|
||||
int hwCadProbe(int8_t level) override;
|
||||
void hwCadSetPeakOffset(int8_t offset) override;
|
||||
uint8_t hwCadBasePeak() override;
|
||||
};
|
||||
|
||||
} /* namespace mesh */
|
||||
@@ -78,4 +78,19 @@ void LR2021Radio::hwResetAGC()
|
||||
lr20xx_reset_agc(_dev);
|
||||
}
|
||||
|
||||
int LR2021Radio::hwCadProbe(int8_t level)
|
||||
{
|
||||
return lr20xx_cad_probe(_dev, level);
|
||||
}
|
||||
|
||||
void LR2021Radio::hwCadSetPeakOffset(int8_t offset)
|
||||
{
|
||||
lr20xx_cad_set_peak_offset(_dev, offset);
|
||||
}
|
||||
|
||||
uint8_t LR2021Radio::hwCadBasePeak()
|
||||
{
|
||||
return lr20xx_cad_base_peak(_dev);
|
||||
}
|
||||
|
||||
} /* namespace mesh */
|
||||
@@ -28,6 +28,9 @@ protected:
|
||||
bool hwIsReceiving() override;
|
||||
void hwSetRxBoost(bool enable) override;
|
||||
void hwResetAGC() override;
|
||||
int hwCadProbe(int8_t level) override;
|
||||
void hwCadSetPeakOffset(int8_t offset) override;
|
||||
uint8_t hwCadBasePeak() override;
|
||||
};
|
||||
|
||||
} /* namespace mesh */
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/random/random.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
|
||||
|
||||
@@ -46,6 +47,8 @@ LoRaRadioBase::LoRaRadioBase(const struct device *lora_dev, MainBoard &board,
|
||||
_last_rssi(0), _last_snr(0),
|
||||
_rx_head(0), _rx_tail(0),
|
||||
_noise_floor(DEFAULT_NOISE_FLOOR), _calibration_threshold(0), _ema_unguarded(0),
|
||||
_cad_auto(false), _cad_offset(0), _cad_probe_interval_s(0),
|
||||
_cad_last_probe_ms(0), _cad_last_decay_ms(0), _cad_probe_rr(0),
|
||||
_rx_duty_cycle_enabled(IS_ENABLED(CONFIG_ZEPHCORE_LORA_RX_DUTY_CYCLE)),
|
||||
_rx_boost_enabled(true),
|
||||
_tx_power_reduction_db(0),
|
||||
@@ -62,6 +65,7 @@ LoRaRadioBase::LoRaRadioBase(const struct device *lora_dev, MainBoard &board,
|
||||
k_poll_signal_init(&_tx_signal);
|
||||
k_sem_init(&_tx_start_sem, 0, 1);
|
||||
memset(_rx_ring, 0, sizeof(_rx_ring));
|
||||
memset(_cad_stats, 0, sizeof(_cad_stats));
|
||||
}
|
||||
|
||||
/* ── TX wait thread ──────────────────────────────────────────── */
|
||||
@@ -246,6 +250,14 @@ void LoRaRadioBase::buildModemConfig(struct lora_modem_config &cfg, bool tx)
|
||||
* direction-only fast path (which skips hwConfigure). RX paths
|
||||
* never read cad.mode, so this is harmless during receive. */
|
||||
cfg.cad.mode = LORA_CAD_MODE_LBT;
|
||||
|
||||
/* 4-symbol CAD at every SF (drivers default to 2 when this is 0).
|
||||
* Our LBT runs against mesh packets that are mostly payload airtime;
|
||||
* payload chirps correlate less reliably per symbol than preamble
|
||||
* upchirps, so the extra looks matter — AN1200.48 itself recommends
|
||||
* 4 symbols at SF9+. The drivers scale their blocking-CAD timeout
|
||||
* from this value, so slow presets stay covered. */
|
||||
cfg.cad.symbol_num = LORA_CAD_SYMB_4;
|
||||
}
|
||||
|
||||
uint32_t LoRaRadioBase::getActiveFrequencyHz() const
|
||||
@@ -446,6 +458,8 @@ void LoRaRadioBase::reconfigure()
|
||||
hwCancelReceive();
|
||||
atomic_set(&_in_recv_mode, 0);
|
||||
_config_cached = false; /* Force full reconfigure */
|
||||
/* CAD probe statistics are only valid for one freq/SF/BW config. */
|
||||
resetCadStats();
|
||||
startReceive();
|
||||
|
||||
uint32_t freq = _prefs ? (uint32_t)(_prefs->freq * 1000000.0f)
|
||||
@@ -931,6 +945,230 @@ bool LoRaRadioBase::isChannelActive(int threshold)
|
||||
return rssi > (_noise_floor + threshold);
|
||||
}
|
||||
|
||||
/* ── Adaptive CAD (LBT detPeak calibration) ───────────────────────────── */
|
||||
|
||||
void LoRaRadioBase::setCadParams(bool auto_enabled, int8_t offset,
|
||||
uint16_t probe_interval_s)
|
||||
{
|
||||
if (offset < CAD_LEVEL_MIN) offset = CAD_LEVEL_MIN;
|
||||
if (offset > CAD_LEVEL_MAX) offset = CAD_LEVEL_MAX;
|
||||
|
||||
_cad_auto = auto_enabled;
|
||||
_cad_offset = offset;
|
||||
_cad_probe_interval_s = probe_interval_s;
|
||||
hwCadSetPeakOffset(_cad_offset);
|
||||
|
||||
LOG_INF("cad: auto=%d offset=%d probe_interval=%us",
|
||||
(int)auto_enabled, (int)offset, (unsigned)probe_interval_s);
|
||||
}
|
||||
|
||||
void LoRaRadioBase::resetCadStats()
|
||||
{
|
||||
memset(_cad_stats, 0, sizeof(_cad_stats));
|
||||
_cad_probe_rr = 0;
|
||||
}
|
||||
|
||||
void LoRaRadioBase::decayCadStats()
|
||||
{
|
||||
for (int i = 0; i < CAD_NUM_LEVELS; i++) {
|
||||
_cad_stats[i].probes >>= 1;
|
||||
_cad_stats[i].busy >>= 1;
|
||||
_cad_stats[i].fp >>= 1;
|
||||
_cad_stats[i].tp >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
int8_t LoRaRadioBase::pickCadProbeLevel()
|
||||
{
|
||||
_cad_probe_rr++;
|
||||
|
||||
if (!_cad_auto) {
|
||||
/* Dry-run: even sweep across the observation window so the
|
||||
* user sees the whole FP-vs-detPeak curve in `get cad`. */
|
||||
int span = CAD_SWEEP_MAX - CAD_SWEEP_MIN + 1;
|
||||
|
||||
return (int8_t)(CAD_SWEEP_MIN + (_cad_probe_rr % span));
|
||||
}
|
||||
|
||||
/* Auto: concentrate samples on the frontier (one step more sensitive
|
||||
* than the operating point); every 4th probe self-checks the
|
||||
* operating level. */
|
||||
int8_t frontier = _cad_offset > CAD_LEVEL_MIN ? (int8_t)(_cad_offset - 1)
|
||||
: _cad_offset;
|
||||
|
||||
return ((_cad_probe_rr & 3) == 0) ? _cad_offset : frontier;
|
||||
}
|
||||
|
||||
void LoRaRadioBase::cadStaircaseStep()
|
||||
{
|
||||
/* Step down (more sensitive) when the frontier level has enough
|
||||
* samples and its suspected-FP rate is at or under target. */
|
||||
if (_cad_offset > CAD_LEVEL_MIN) {
|
||||
CadLevelStats &f = _cad_stats[(_cad_offset - 1) - CAD_LEVEL_MIN];
|
||||
|
||||
if (f.probes >= CAD_STEP_DOWN_MIN_PROBES &&
|
||||
(uint32_t)f.fp * 1000U <=
|
||||
(uint32_t)f.probes * CAD_FP_TARGET_PERMILLE) {
|
||||
_cad_offset--;
|
||||
hwCadSetPeakOffset(_cad_offset);
|
||||
LOG_INF("cad: step down -> offset %d (frontier %up/%ufp)",
|
||||
(int)_cad_offset, f.probes, f.fp);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Step up (less sensitive) when the operating level itself shows
|
||||
* FPs well above target. Lower sample bar: FPs at the operating
|
||||
* point cost real TX opportunities, react quickly. */
|
||||
if (_cad_offset < CAD_LEVEL_MAX) {
|
||||
CadLevelStats &o = _cad_stats[_cad_offset - CAD_LEVEL_MIN];
|
||||
|
||||
if (o.probes >= CAD_STEP_UP_MIN_PROBES &&
|
||||
(uint32_t)o.fp * 1000U >
|
||||
(uint32_t)o.probes * 2U * CAD_FP_TARGET_PERMILLE) {
|
||||
_cad_offset++;
|
||||
hwCadSetPeakOffset(_cad_offset);
|
||||
LOG_INF("cad: step up -> offset %d (operating %up/%ufp)",
|
||||
(int)_cad_offset, o.probes, o.fp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LoRaRadioBase::cadMaintenance()
|
||||
{
|
||||
if (_cad_probe_interval_s == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int64_t now = k_uptime_get();
|
||||
|
||||
/* Periodic decay keeps the stats fresh (and counters bounded). */
|
||||
if (_cad_last_decay_ms == 0) {
|
||||
_cad_last_decay_ms = now;
|
||||
} else if (now - _cad_last_decay_ms > (int64_t)CAD_STATS_DECAY_MS) {
|
||||
decayCadStats();
|
||||
_cad_last_decay_ms = now;
|
||||
}
|
||||
|
||||
if (now - _cad_last_probe_ms < (int64_t)_cad_probe_interval_s * 1000) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Same guards as the noise-floor calibrator: only probe from idle
|
||||
* continuous/duty-cycle RX, never during TX or an active packet,
|
||||
* never while the chip is in its duty-cycle sleep (BUSY) phase. */
|
||||
if (!atomic_get(&_in_recv_mode) || atomic_get(&_tx_active)) {
|
||||
return;
|
||||
}
|
||||
if (!isRadioReady() || isReceiving()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Ground-truth prefilter: skip when the channel is visibly busy —
|
||||
* a busy verdict against strong traffic teaches us nothing about
|
||||
* false positives. (Below-noise-floor LoRa can't be excluded here;
|
||||
* the post-probe RX check below handles that side.) */
|
||||
int16_t rssi = hwGetCurrentRSSI();
|
||||
|
||||
if (rssi == -128) {
|
||||
return;
|
||||
}
|
||||
if (_noise_floor != DEFAULT_NOISE_FLOOR &&
|
||||
rssi > _noise_floor + CAD_PROBE_RSSI_GUARD) {
|
||||
return;
|
||||
}
|
||||
|
||||
_cad_last_probe_ms = now;
|
||||
|
||||
int8_t level = pickCadProbeLevel();
|
||||
int ret = hwCadProbe(level);
|
||||
|
||||
/* The probe leaves the chip in STANDBY (driver state REST) — re-arm
|
||||
* RX immediately so an incoming packet isn't lost while we classify.
|
||||
* In duty-cycle mode this re-enters the DC cycle (same path as the
|
||||
* parked-RX watchdog re-arm). */
|
||||
atomic_set(&_in_recv_mode, 0);
|
||||
startReceive();
|
||||
|
||||
if (ret < 0) {
|
||||
if (ret != -ENOSYS) {
|
||||
LOG_WRN("cad: probe failed (%d)", ret);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
CadLevelStats &s = _cad_stats[level - CAD_LEVEL_MIN];
|
||||
|
||||
if (s.probes >= 0xFFF0) {
|
||||
decayCadStats();
|
||||
}
|
||||
s.probes++;
|
||||
|
||||
if (ret > 0) {
|
||||
s.busy++;
|
||||
|
||||
/* Ground-truth post-check: a real LoRa signal that tripped
|
||||
* CAD keeps transmitting — after RX restart its preamble or
|
||||
* header trips the receive path within a few symbols. Wait
|
||||
* ~8 symbols, then classify. RX is already armed, so the
|
||||
* packet itself is not at risk during this sleep. */
|
||||
uint8_t sf = getActiveSpreadingFactor();
|
||||
uint16_t bw_x10 = getActiveBandwidthKHzX10();
|
||||
uint32_t tsym_us = bw_x10 ? (uint32_t)(((1UL << sf) * 10000UL)
|
||||
/ bw_x10) : 1024;
|
||||
uint32_t wait_ms = (8U * tsym_us) / 1000U;
|
||||
|
||||
if (wait_ms < 20) wait_ms = 20;
|
||||
if (wait_ms > 400) wait_ms = 400;
|
||||
k_sleep(K_MSEC(wait_ms));
|
||||
|
||||
if (isReceiving()) {
|
||||
s.tp++;
|
||||
} else {
|
||||
s.fp++;
|
||||
}
|
||||
}
|
||||
|
||||
if (_cad_auto) {
|
||||
cadStaircaseStep();
|
||||
}
|
||||
}
|
||||
|
||||
int LoRaRadioBase::formatCadStatus(char *buf, int cap)
|
||||
{
|
||||
uint8_t base = hwCadBasePeak();
|
||||
int n = 0;
|
||||
|
||||
if (base == 0) {
|
||||
return snprintf(buf, cap, "CAD: not supported by this radio");
|
||||
}
|
||||
|
||||
int peak = (int)base + _cad_offset;
|
||||
|
||||
n += snprintf(buf + n, cap > n ? cap - n : 0,
|
||||
"auto:%s offset:%d peak:%d (base %u, 4 sym) probe:%us",
|
||||
_cad_auto ? "on" : "off", (int)_cad_offset, peak,
|
||||
base, (unsigned)_cad_probe_interval_s);
|
||||
|
||||
for (int i = 0; i < CAD_NUM_LEVELS; i++) {
|
||||
CadLevelStats &s = _cad_stats[i];
|
||||
|
||||
if (s.probes == 0) {
|
||||
continue;
|
||||
}
|
||||
/* FP rate in tenths of a percent */
|
||||
uint32_t fp_pm = ((uint32_t)s.fp * 1000U) / s.probes;
|
||||
|
||||
n += snprintf(buf + n, cap > n ? cap - n : 0,
|
||||
"\nlvl %+d (peak %d): %u probes, %u busy, %u fp, %u tp (fp %u.%u%%)",
|
||||
i + CAD_LEVEL_MIN, (int)base + i + CAD_LEVEL_MIN,
|
||||
s.probes, s.busy, s.fp, s.tp,
|
||||
(unsigned)(fp_pm / 10), (unsigned)(fp_pm % 10));
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
/* ── Power saving ─────────────────────────────────────────────────────── */
|
||||
|
||||
void LoRaRadioBase::enableRxDutyCycle(bool enable)
|
||||
|
||||
@@ -109,6 +109,14 @@ public:
|
||||
void setTxPowerReduction(int8_t reduction_db) override { _tx_power_reduction_db = reduction_db; }
|
||||
int8_t getTxPowerReduction() const override { return _tx_power_reduction_db; }
|
||||
|
||||
/* Adaptive CAD (LBT detPeak calibration) */
|
||||
void setCadParams(bool auto_enabled, int8_t offset,
|
||||
uint16_t probe_interval_s) override;
|
||||
void cadMaintenance() override;
|
||||
int8_t getCadOffset() const override { return _cad_offset; }
|
||||
void resetCadStats() override;
|
||||
int formatCadStatus(char *buf, int cap) override;
|
||||
|
||||
protected:
|
||||
/* ── Hardware primitives — subclass MUST implement ─────────── */
|
||||
|
||||
@@ -126,6 +134,18 @@ protected:
|
||||
/** GPIO-only BUSY check (no SPI). Default false for chips without duty-cycle sleep. */
|
||||
virtual bool hwIsChipBusy() { return false; }
|
||||
|
||||
/* ── Adaptive-CAD primitives — defaults suit chips without hardware
|
||||
* CAD (SX127x): probing unsupported, offset ignored. ───────────── */
|
||||
|
||||
/** Blocking calibration CAD at (family base detPeak + level).
|
||||
* Leaves the chip in STANDBY; caller restarts RX.
|
||||
* Returns 1 = busy, 0 = free, <0 = error / unsupported. */
|
||||
virtual int hwCadProbe(int8_t level) { (void)level; return -ENOSYS; }
|
||||
/** Apply the operating detPeak offset for all subsequent LBT CADs. */
|
||||
virtual void hwCadSetPeakOffset(int8_t offset) { (void)offset; }
|
||||
/** Per-SF base detPeak for the current config (0 = unsupported). */
|
||||
virtual uint8_t hwCadBasePeak() { return 0; }
|
||||
|
||||
/** Radio deaf time per duty-cycle wake transition (context restore +
|
||||
* PLL lock + TCXO startup where fitted), in microseconds. Counts
|
||||
* against the duty-cycle preamble-catch budget: per SX126x DS rev 2.2
|
||||
@@ -180,6 +200,25 @@ protected:
|
||||
int _calibration_threshold;
|
||||
uint8_t _ema_unguarded; /* tick counter for warmup + periodic bypass */
|
||||
|
||||
/* Adaptive CAD state */
|
||||
struct CadLevelStats {
|
||||
uint16_t probes; /* probes run at this level */
|
||||
uint16_t busy; /* raw busy verdicts */
|
||||
uint16_t fp; /* busy that passed the ground-truth filter (suspected false positive) */
|
||||
uint16_t tp; /* busy confirmed by RX activity right after */
|
||||
};
|
||||
CadLevelStats _cad_stats[CAD_NUM_LEVELS];
|
||||
bool _cad_auto; /* staircase acts on the stats */
|
||||
int8_t _cad_offset; /* operating detPeak offset (levels) */
|
||||
uint16_t _cad_probe_interval_s; /* 0 = probing disabled */
|
||||
int64_t _cad_last_probe_ms;
|
||||
int64_t _cad_last_decay_ms;
|
||||
uint8_t _cad_probe_rr; /* round-robin index (sweep) / frontier mix counter */
|
||||
|
||||
int8_t pickCadProbeLevel();
|
||||
void decayCadStats();
|
||||
void cadStaircaseStep();
|
||||
|
||||
/* Power saving */
|
||||
bool _rx_duty_cycle_enabled;
|
||||
bool _rx_boost_enabled;
|
||||
|
||||
@@ -96,6 +96,21 @@ uint32_t SX126xRadio::hwWakeupTimeUs()
|
||||
return sx126x_get_wakeup_time_us(_dev);
|
||||
}
|
||||
|
||||
int SX126xRadio::hwCadProbe(int8_t level)
|
||||
{
|
||||
return sx126x_cad_probe(_dev, level);
|
||||
}
|
||||
|
||||
void SX126xRadio::hwCadSetPeakOffset(int8_t offset)
|
||||
{
|
||||
sx126x_cad_set_peak_offset(_dev, offset);
|
||||
}
|
||||
|
||||
uint8_t SX126xRadio::hwCadBasePeak()
|
||||
{
|
||||
return sx126x_cad_base_peak(_dev);
|
||||
}
|
||||
|
||||
uint32_t SX126xRadio::getDutyCycleTimeoutRestarts() const
|
||||
{
|
||||
return sx126x_get_dc_timeout_restarts(_dev);
|
||||
|
||||
@@ -32,6 +32,9 @@ protected:
|
||||
void hwResetAGC() override;
|
||||
bool hwIsChipBusy() override;
|
||||
uint32_t hwWakeupTimeUs() override;
|
||||
int hwCadProbe(int8_t level) override;
|
||||
void hwCadSetPeakOffset(int8_t offset) override;
|
||||
uint8_t hwCadBasePeak() override;
|
||||
};
|
||||
|
||||
} /* namespace mesh */
|
||||
@@ -20,6 +20,23 @@
|
||||
#define NOISE_FLOOR_SAMPLING_THRESHOLD 14 /* dB above floor to reject as interference */
|
||||
#define DEFAULT_NOISE_FLOOR 0 /* sentinel: seed from first sample */
|
||||
|
||||
/* --- Adaptive CAD (LBT detPeak calibration) ---
|
||||
* Housekeeping-tick CAD probes accumulate per-level busy/free statistics;
|
||||
* a one-sided staircase converges on the lowest detPeak offset whose
|
||||
* false-positive rate stays under target. Levels are signed offsets from
|
||||
* the chip family's per-SF base detPeak (SX126x: SF+13; LR11xx/LR20xx:
|
||||
* 56-68 table) so the C++ layer stays scale-independent. */
|
||||
#define CAD_LEVEL_MIN (-4) /* most sensitive probe level */
|
||||
#define CAD_LEVEL_MAX 4 /* least sensitive probe level */
|
||||
#define CAD_NUM_LEVELS (CAD_LEVEL_MAX - CAD_LEVEL_MIN + 1)
|
||||
#define CAD_SWEEP_MIN (-3) /* dry-run sweep window */
|
||||
#define CAD_SWEEP_MAX 2
|
||||
#define CAD_FP_TARGET_PERMILLE 10 /* step-down needs FP rate <= 1% */
|
||||
#define CAD_STEP_DOWN_MIN_PROBES 300 /* samples before a down-step call */
|
||||
#define CAD_STEP_UP_MIN_PROBES 50 /* samples before an up-step call */
|
||||
#define CAD_PROBE_RSSI_GUARD 7 /* dB above floor = channel visibly busy, skip probe */
|
||||
#define CAD_STATS_DECAY_MS (6UL * 3600UL * 1000UL) /* halve counters every 6 h */
|
||||
|
||||
/* --- RX ring buffer --- */
|
||||
#define RX_RING_SIZE 8 /* ~2 KB; buffers burst arrivals at SF7/BW500 */
|
||||
|
||||
|
||||
Reference in new issue
Block a user