mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-10-23 03:41:27 +00:00
refactor patches and bump to 1.17.2
This commit is contained in:
1 parent
9ab9a9b950
commit
5c4ac21bd4
28 files changed
+1382
-856
No files matched your search
@@ -78,6 +78,21 @@ bool LR1110Radio::hwIsReceiving()
|
||||
return lr11xx_is_receiving(_dev);
|
||||
}
|
||||
|
||||
void LR1110Radio::hwResetAgc()
|
||||
{
|
||||
lr11xx_reset_agc(_dev);
|
||||
}
|
||||
|
||||
void LR1110Radio::hwRecalibrate()
|
||||
{
|
||||
lr11xx_recalibrate(_dev);
|
||||
}
|
||||
|
||||
int16_t LR1110Radio::hwGetChipTempC()
|
||||
{
|
||||
return lr11xx_get_chip_temp_c(_dev);
|
||||
}
|
||||
|
||||
void LR1110Radio::hwSetRxBoost(bool enable)
|
||||
{
|
||||
lr11xx_set_rx_boost(_dev, enable);
|
||||
|
||||
@@ -34,6 +34,9 @@ protected:
|
||||
int16_t hwGetCurrentRSSI() override;
|
||||
bool hwIsReceiving() override;
|
||||
void hwSetRxBoost(bool enable) override;
|
||||
void hwResetAgc() override;
|
||||
void hwRecalibrate() override;
|
||||
int16_t hwGetChipTempC() override;
|
||||
uint32_t hwWakeupTimeUs() override;
|
||||
int hwCadProbe(int8_t level) override;
|
||||
void hwCadSetPeakOffset(int8_t offset) override;
|
||||
|
||||
@@ -167,6 +167,23 @@ uint8_t LR2021Radio::hwCadPeakMax()
|
||||
return lr20xx_cad_peak_max();
|
||||
}
|
||||
|
||||
void LR2021Radio::hwResetAgc()
|
||||
{
|
||||
lr20xx_reset_agc(_dev);
|
||||
}
|
||||
|
||||
void LR2021Radio::hwRecalibrate()
|
||||
{
|
||||
/* Front-end calibration included: this is the temperature-drift path, and
|
||||
* image/FE cal is the part a temperature swing actually invalidates. */
|
||||
lr20xx_recalibrate(_dev);
|
||||
}
|
||||
|
||||
int16_t LR2021Radio::hwGetChipTempC()
|
||||
{
|
||||
return lr20xx_get_chip_temp_c(_dev);
|
||||
}
|
||||
|
||||
uint32_t LR2021Radio::hwWakeupTimeUs()
|
||||
{
|
||||
/* Per-device, because the TCXO term dominates and is board-specific:
|
||||
|
||||
@@ -52,6 +52,9 @@ protected:
|
||||
uint8_t hwCadPeakMin() override;
|
||||
uint8_t hwCadPeakMax() override;
|
||||
uint32_t hwWakeupTimeUs() override;
|
||||
void hwResetAgc() override;
|
||||
void hwRecalibrate() override;
|
||||
int16_t hwGetChipTempC() override;
|
||||
};
|
||||
|
||||
} /* namespace mesh */
|
||||
@@ -60,6 +60,8 @@ LoRaRadioBase::LoRaRadioBase(const struct device *lora_dev, MainBoard &board,
|
||||
_rx_duty_cycle_enabled(IS_ENABLED(CONFIG_ZEPHCORE_LORA_RX_DUTY_CYCLE)),
|
||||
_rx_boost_enabled(true),
|
||||
_dc_last_rx_us(0), _dc_last_sleep_us(0),
|
||||
_agc_rx_count_shadow(0), _agc_last_activity_ms(0),
|
||||
_agc_last_cal_temp_c(INT16_MIN),
|
||||
_config_cached(false),
|
||||
_has_radio_override(false),
|
||||
_override_freq(0), _override_bw(0),
|
||||
@@ -1125,6 +1127,102 @@ bool LoRaRadioBase::isReceiving()
|
||||
return isChannelActive();
|
||||
}
|
||||
|
||||
/* ── Receiver hygiene ─────────────────────────────────────────────────
|
||||
*
|
||||
* Two independent faults, two independent triggers, neither on the packet path.
|
||||
*
|
||||
* 1. STUCK AGC. Semtech's remedy is a warm sleep plus recalibration; it is not
|
||||
* in the datasheets, so it is not up for removal on datasheet reasoning.
|
||||
* What IS a design choice is when to fire it, and the honest answer is that
|
||||
* a periodic reset gets it backwards: a timer fires most often on a busy
|
||||
* channel, which is exactly where a received packet has just PROVED the AGC
|
||||
* is working, and it fires no more often on a silent one, where a
|
||||
* desensitised receiver is invisible and nothing else will reveal it.
|
||||
*
|
||||
* So trigger on silence instead. If nothing has been heard for
|
||||
* AGC_IDLE_RESET_MS the receiver is either genuinely idle — in which case
|
||||
* the reset costs nothing, there is no traffic to miss — or it is deaf, in
|
||||
* which case this is the only thing that will recover it. Self-targeting in
|
||||
* both directions: a busy node essentially never resets, and the resets a
|
||||
* quiet node does perform are free. Arduino MeshCore's periodic
|
||||
* `agc_reset_interval` addressed the same fault and shipped defaulted to 0
|
||||
* (off), which is a fair summary of how well a plain timer serves it.
|
||||
*
|
||||
* Deliberately NOT reset here: the noise floor. The previous periodic
|
||||
* implementation (removed in fe6e585) zeroed it on every fire, forcing a
|
||||
* fresh seed and a full EMA warmup each time — that, not the AGC work, is
|
||||
* what made it a net loss. Arduino zeroes it too; we do not.
|
||||
*
|
||||
* 2. CALIBRATION DRIFT. Image/front-end and PLL/AAF calibration are valid for
|
||||
* a temperature range, not forever: "Image calibration is necessary if there
|
||||
* is a frequency change > 10MHz, or a temperature change > 10 C", and the
|
||||
* LR2021 additionally advises redoing PLL and AAF beyond +/-20 C. A node
|
||||
* that boots on a hot afternoon and runs into a cold night crosses both.
|
||||
* Nothing does this automatically: the LR2021's temperature-compensation
|
||||
* block (DS 6.12) only corrects crystal drift from TX self-heating, and it
|
||||
* refuses to run at all when a TCXO is fitted.
|
||||
*
|
||||
* AGC_RECAL_TEMP_DELTA_C is deliberately tighter than either datasheet
|
||||
* figure — cheap insurance, and the reading is a junction temperature that
|
||||
* lags ambient. */
|
||||
#define AGC_IDLE_RESET_MS 60000U
|
||||
#define AGC_RECAL_TEMP_DELTA_C 5
|
||||
|
||||
void LoRaRadioBase::agcMaintenance()
|
||||
{
|
||||
/* Never mid-transmit: both operations sleep the chip. */
|
||||
if (atomic_get(&_tx_active)) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t now = (uint32_t)k_uptime_get_32();
|
||||
|
||||
/* Any demodulation activity counts as proof of life, errored frames
|
||||
* included — a CRC failure still means RF reached the demodulator, which
|
||||
* is precisely what a stuck AGC would prevent. Counting only good
|
||||
* packets would fire resets on a node that is merely out of range. */
|
||||
uint32_t rx_total = (uint32_t)atomic_get(&_packets_recv) +
|
||||
(uint32_t)atomic_get(&_packets_recv_errors);
|
||||
|
||||
if (rx_total != _agc_rx_count_shadow || _agc_last_activity_ms == 0) {
|
||||
_agc_rx_count_shadow = rx_total;
|
||||
_agc_last_activity_ms = now ? now : 1;
|
||||
} else if ((now - _agc_last_activity_ms) >= AGC_IDLE_RESET_MS) {
|
||||
LOG_INF("agc: %u ms without RX activity — resetting AGC",
|
||||
(unsigned)(now - _agc_last_activity_ms));
|
||||
hwResetAgc();
|
||||
/* hwResetAgc() leaves the chip out of RX by contract, so this is
|
||||
* a genuine re-entry and re-arms the duty cycle if one is set. */
|
||||
startReceive();
|
||||
_agc_last_activity_ms = now ? now : 1;
|
||||
}
|
||||
|
||||
/* Temperature drift. Sampled every pass because the read is one cheap
|
||||
* command; the expensive recalibration is gated on the delta. */
|
||||
int16_t temp_c = hwGetChipTempC();
|
||||
|
||||
if (temp_c == INT16_MIN) {
|
||||
return; /* backend cannot measure — drift handling disabled */
|
||||
}
|
||||
if (_agc_last_cal_temp_c == INT16_MIN) {
|
||||
_agc_last_cal_temp_c = temp_c; /* first reading is the baseline */
|
||||
return;
|
||||
}
|
||||
|
||||
int delta = (int)temp_c - (int)_agc_last_cal_temp_c;
|
||||
|
||||
if (delta < 0) {
|
||||
delta = -delta;
|
||||
}
|
||||
if (delta >= AGC_RECAL_TEMP_DELTA_C) {
|
||||
LOG_INF("agc: chip temp moved %d C (%d -> %d) — recalibrating",
|
||||
delta, (int)_agc_last_cal_temp_c, (int)temp_c);
|
||||
hwRecalibrate();
|
||||
startReceive();
|
||||
_agc_last_cal_temp_c = temp_c;
|
||||
}
|
||||
}
|
||||
|
||||
void LoRaRadioBase::recoverRxState()
|
||||
{
|
||||
/* Called by the Dispatcher on CAD timeout when isReceiving() has been
|
||||
|
||||
@@ -121,6 +121,9 @@ public:
|
||||
void setCadParams(bool auto_enabled, int8_t offset,
|
||||
uint16_t probe_interval_s, uint8_t busycap_pct) override;
|
||||
void cadMaintenance() override;
|
||||
/** Deaf-aware AGC unstick + temperature-drift recalibration.
|
||||
* Called from Dispatcher::maintenanceLoop(); never on the packet path. */
|
||||
void agcMaintenance() override;
|
||||
uint32_t msUntilNextMaintenance() override;
|
||||
int8_t getCadOffset() const override { return _cad_offset; }
|
||||
|
||||
@@ -175,6 +178,26 @@ protected:
|
||||
virtual uint8_t hwCadPeakMin() { return 0; }
|
||||
virtual uint8_t hwCadPeakMax() { return 0; }
|
||||
|
||||
/* ── Receiver hygiene — see LoRaRadioBase::agcMaintenance() ────── */
|
||||
|
||||
/** Unstick a jammed AGC: warm sleep to drop the analog front end, then
|
||||
* recalibrate on the way back up. Semtech's stated remedy; the
|
||||
* datasheets do not describe it, so do not "simplify" it away on
|
||||
* datasheet grounds alone. Must leave the driver out of RX so the
|
||||
* caller's startReceive() performs a real re-entry rather than hitting
|
||||
* an idempotent fast path. Default: unsupported, no-op. */
|
||||
virtual void hwResetAgc() {}
|
||||
|
||||
/** Redo the frequency-dependent calibrations (image / front end, and
|
||||
* PLL+AAF where the part separates them) at the current operating
|
||||
* frequency. Called on temperature drift, never on the packet path.
|
||||
* Same RX-state contract as hwResetAgc(). Default: unsupported. */
|
||||
virtual void hwRecalibrate() {}
|
||||
|
||||
/** Chip junction temperature in whole degrees C, or INT16_MIN when the
|
||||
* backend cannot measure it (which disables drift recalibration). */
|
||||
virtual int16_t hwGetChipTempC() { return INT16_MIN; }
|
||||
|
||||
/** 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
|
||||
@@ -298,8 +321,16 @@ protected:
|
||||
* changes once at INF instead of on every RX restart. 0/0 = never
|
||||
* computed; UINT32_MAX rx = continuous-RX fallback active. */
|
||||
uint32_t _dc_last_rx_us;
|
||||
|
||||
uint32_t _dc_last_sleep_us;
|
||||
|
||||
/* agcMaintenance() bookkeeping. RX activity is inferred by sampling the
|
||||
* existing packet counters rather than timestamping in the RX callback,
|
||||
* so nothing is added to the ISR path. */
|
||||
uint32_t _agc_rx_count_shadow;
|
||||
uint32_t _agc_last_activity_ms;
|
||||
int16_t _agc_last_cal_temp_c;
|
||||
|
||||
/* Config cache — skip redundant hwConfigure() */
|
||||
struct lora_modem_config _last_cfg;
|
||||
bool _config_cached;
|
||||
|
||||
@@ -75,6 +75,27 @@ bool SX126xRadio::hwIsReceiving()
|
||||
return sx126x_is_receiving(_dev);
|
||||
}
|
||||
|
||||
void SX126xRadio::hwResetAgc()
|
||||
{
|
||||
/* Leaves the chip in STANDBY and the driver state at IDLE, so the
|
||||
* caller's startReceive() performs a real re-entry. */
|
||||
sx126x_reset_agc(_dev);
|
||||
}
|
||||
|
||||
void SX126xRadio::hwRecalibrate()
|
||||
{
|
||||
/* Calibrate(ALL) on this part already includes image rejection and
|
||||
* sx126x_reset_agc() re-issues CalibrateImage for the operating
|
||||
* frequency, so the drift path needs nothing extra. */
|
||||
sx126x_reset_agc(_dev);
|
||||
}
|
||||
|
||||
/* No hwGetChipTempC(): the SX126x exposes no junction-temperature readout —
|
||||
* no command in the datasheet, and RadioLib has no getTemperature() for it
|
||||
* either. Drift recalibration is therefore inactive on this family, which
|
||||
* costs little: unlike the LR parts, its datasheet gives no temperature
|
||||
* threshold for image calibration in the first place. */
|
||||
|
||||
void SX126xRadio::hwSetRxBoost(bool enable)
|
||||
{
|
||||
sx126x_set_rx_boost(_dev, enable);
|
||||
|
||||
@@ -29,6 +29,8 @@ protected:
|
||||
int16_t hwGetCurrentRSSI() override;
|
||||
bool hwIsReceiving() override;
|
||||
void hwSetRxBoost(bool enable) override;
|
||||
void hwResetAgc() override;
|
||||
void hwRecalibrate() override;
|
||||
bool hwIsChipBusy() override;
|
||||
uint32_t hwWakeupTimeUs() override;
|
||||
int hwCadProbe(int8_t level) override;
|
||||
|
||||
Reference in new issue
Block a user