mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-09-01 20:09:17 +00:00
port upstream: AGC reset, autoadd max hops, noise floor EMA, fixes
- AGC reset: proper warm sleep + Calibrate(ALL) + image recal for both
SX126x and LR1110 (was cancel+restart / no-op respectively)
- Guard AGC reset against mid-receive (matches upstream RadioLib)
- Noise floor calibration: replace blocking 64-sample burst with
single-sample EMA (alpha=1/8) per housekeeping tick — zero blocking,
better temporal spread for real-world noise floor tracking
- Auto-add max hops: filter new contacts by hop count (NodePrefs,
BaseChatMesh, CompanionMesh, ZephyrDataStore persistence)
- Fix CommonCLI memcmp("prefs", 4) → 5
This commit is contained in:
@@ -571,7 +571,12 @@ void ZephyrDataStore::loadPrefs(NodePrefs &prefs)
|
||||
off += 4;
|
||||
prefs.autoadd_config = buf[off++];
|
||||
|
||||
/* Offset 91: rx_boost (ZephCore extension — Arduino stops at 91 bytes) */
|
||||
/* Offset 91: autoadd_max_hops (matches Arduino layout) */
|
||||
if (off < len) {
|
||||
prefs.autoadd_max_hops = buf[off++];
|
||||
}
|
||||
|
||||
/* Offset 92: rx_boost (ZephCore extension — Arduino stops at 92 bytes) */
|
||||
if (off < len) {
|
||||
prefs.rx_boost = buf[off++];
|
||||
} else {
|
||||
@@ -621,9 +626,11 @@ void ZephyrDataStore::savePrefs(const NodePrefs &prefs)
|
||||
memcpy(&buf[off], &prefs.gps_interval, sizeof(uint32_t));
|
||||
off += 4;
|
||||
buf[off++] = prefs.autoadd_config;
|
||||
/* Offset 91: rx_boost (ZephCore extension — Arduino ignores) */
|
||||
/* Offset 91: autoadd_max_hops (matches Arduino layout) */
|
||||
buf[off++] = prefs.autoadd_max_hops;
|
||||
/* Offset 92: rx_boost (ZephCore extension — Arduino ignores) */
|
||||
buf[off++] = prefs.rx_boost;
|
||||
/* Total: 92 bytes (Arduino reads 91, ZephCore reads 92) */
|
||||
/* Total: 93 bytes (Arduino reads 92, ZephCore reads 93) */
|
||||
|
||||
bool ok = openWrite(prefsFile(), buf, off);
|
||||
LOG_INF("savePrefs: wrote %s, ok=%d (%d bytes), name='%.16s'",
|
||||
|
||||
@@ -91,7 +91,8 @@ void LR1110Radio::hwSetRxDutyCycle(bool enable)
|
||||
|
||||
void LR1110Radio::hwResetAGC()
|
||||
{
|
||||
/* LR1110 doesn't have the same AGC issues as SX126x */
|
||||
/* Warm sleep → Calibrate(ALL) → re-calibrate image → re-apply RX boost */
|
||||
lr11xx_reset_agc(_dev);
|
||||
}
|
||||
|
||||
} /* namespace mesh */
|
||||
|
||||
@@ -510,33 +510,49 @@ void LoRaRadioBase::triggerNoiseFloorCalibrate(int threshold)
|
||||
return;
|
||||
}
|
||||
|
||||
int64_t start = k_uptime_get();
|
||||
int sum = 0;
|
||||
int count = 0;
|
||||
for (int i = 0; i < NUM_NOISE_FLOOR_SAMPLES; i++) {
|
||||
if (isReceiving()) {
|
||||
break;
|
||||
}
|
||||
int16_t rssi = hwGetCurrentRSSI();
|
||||
if (rssi < _noise_floor + NOISE_FLOOR_SAMPLING_THRESHOLD) {
|
||||
sum += rssi;
|
||||
count++;
|
||||
}
|
||||
/* Skip if mid-receive — don't want signal energy in the floor. */
|
||||
if (isReceiving()) {
|
||||
return;
|
||||
}
|
||||
int64_t elapsed = k_uptime_get() - start;
|
||||
|
||||
if (count >= NUM_NOISE_FLOOR_SAMPLES / 2) {
|
||||
_noise_floor = sum / count;
|
||||
int16_t rssi = hwGetCurrentRSSI();
|
||||
|
||||
/* First sample after reset (DEFAULT_NOISE_FLOOR == 0): seed directly. */
|
||||
if (_noise_floor == DEFAULT_NOISE_FLOOR) {
|
||||
_noise_floor = rssi;
|
||||
if (_noise_floor < -120) _noise_floor = -120;
|
||||
if (_noise_floor > -50) _noise_floor = -50;
|
||||
LOG_DBG("noise_floor_cal: seed=%d", _noise_floor);
|
||||
return;
|
||||
}
|
||||
LOG_DBG("noise_floor_cal: %d samples/%d total, floor=%d, took %lld ms",
|
||||
count, NUM_NOISE_FLOOR_SAMPLES, _noise_floor, elapsed);
|
||||
|
||||
/* Reject samples above floor + threshold (interference / signal). */
|
||||
if (rssi >= _noise_floor + NOISE_FLOOR_SAMPLING_THRESHOLD) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* EMA: floor += (sample - floor) >> SHIFT. Integer-only. */
|
||||
_noise_floor += (rssi - _noise_floor) >> NOISE_FLOOR_EMA_SHIFT;
|
||||
if (_noise_floor < -120) _noise_floor = -120;
|
||||
if (_noise_floor > -50) _noise_floor = -50;
|
||||
|
||||
LOG_DBG("noise_floor_cal: rssi=%d, floor=%d", rssi, _noise_floor);
|
||||
}
|
||||
|
||||
void LoRaRadioBase::resetAGC()
|
||||
{
|
||||
/* Don't reset AGC while actively receiving a packet — warm sleep would
|
||||
* corrupt it. The Dispatcher will retry next interval. */
|
||||
if (isReceiving()) {
|
||||
return;
|
||||
}
|
||||
|
||||
hwResetAGC();
|
||||
|
||||
/* Reset noise floor sampling so it reconverges from scratch.
|
||||
* Without this, a stuck _noise_floor of -120 makes the sampling threshold
|
||||
* too low to accept normal samples, self-reinforcing the stuck value. */
|
||||
_noise_floor = DEFAULT_NOISE_FLOOR;
|
||||
}
|
||||
|
||||
bool LoRaRadioBase::isReceiving()
|
||||
|
||||
@@ -91,10 +91,8 @@ void SX126xRadio::hwSetRxDutyCycle(bool enable)
|
||||
|
||||
void SX126xRadio::hwResetAGC()
|
||||
{
|
||||
if (_in_recv_mode && !isReceiving()) {
|
||||
hwCancelReceive();
|
||||
startReceive();
|
||||
}
|
||||
/* Warm sleep → Calibrate(ALL) → re-calibrate image → re-apply RX settings */
|
||||
sx126x_reset_agc(_dev);
|
||||
}
|
||||
|
||||
} /* namespace mesh */
|
||||
|
||||
@@ -11,10 +11,14 @@
|
||||
|
||||
#include <zephyr/drivers/lora.h>
|
||||
|
||||
/* --- Noise floor calibration --- */
|
||||
#define NUM_NOISE_FLOOR_SAMPLES 64
|
||||
/* --- Noise floor calibration (EMA) ---
|
||||
* Single RSSI sample per tick, smoothed with exponential moving average.
|
||||
* alpha = 1/8 (bit-shiftable): new_floor = floor + (sample - floor) / 8
|
||||
* Convergence: ~8 ticks (~40s at 5s housekeeping) to track a step change.
|
||||
* Samples above floor + SAMPLING_THRESHOLD are rejected (interference). */
|
||||
#define NOISE_FLOOR_EMA_SHIFT 3 /* alpha = 1 / (1 << 3) = 1/8 */
|
||||
#define NOISE_FLOOR_SAMPLING_THRESHOLD 14 /* only sample if rssi < floor + threshold */
|
||||
#define DEFAULT_NOISE_FLOOR 0 /* first calibration accepts all samples */
|
||||
#define DEFAULT_NOISE_FLOOR 0 /* accept all samples until first update */
|
||||
|
||||
/* --- RX ring buffer ---
|
||||
* 8 slots buffer ~40ms+ of back-to-back arrivals at SF7/BW500.
|
||||
|
||||
@@ -1171,6 +1171,11 @@ bool CompanionMesh::shouldOverwriteWhenFull() const
|
||||
return (prefs.autoadd_config & AUTO_ADD_OVERWRITE_OLDEST) != 0;
|
||||
}
|
||||
|
||||
uint8_t CompanionMesh::getAutoAddMaxHops() const
|
||||
{
|
||||
return prefs.autoadd_max_hops;
|
||||
}
|
||||
|
||||
void CompanionMesh::onContactsFull()
|
||||
{
|
||||
sendPush(PUSH_CODE_CONTACTS_FULL);
|
||||
@@ -2538,16 +2543,21 @@ bool CompanionMesh::handleProtocolFrame(const uint8_t *data, size_t len)
|
||||
case CMD_SET_AUTOADD_CONFIG:
|
||||
if (len >= 2) {
|
||||
prefs.autoadd_config = data[1];
|
||||
LOG_INF("SET_AUTOADD_CONFIG: autoadd_config=0x%02x", prefs.autoadd_config);
|
||||
if (len >= 3) {
|
||||
prefs.autoadd_max_hops = (data[2] > 64) ? 64 : data[2];
|
||||
}
|
||||
LOG_INF("SET_AUTOADD_CONFIG: autoadd_config=0x%02x max_hops=%u",
|
||||
prefs.autoadd_config, prefs.autoadd_max_hops);
|
||||
_store->savePrefs(prefs);
|
||||
}
|
||||
sendPacketOk();
|
||||
return true;
|
||||
|
||||
case CMD_GET_AUTOADD_CONFIG: {
|
||||
uint8_t rsp[2];
|
||||
uint8_t rsp[3];
|
||||
rsp[0] = PACKET_AUTOADD_CONFIG;
|
||||
rsp[1] = prefs.autoadd_config;
|
||||
rsp[2] = prefs.autoadd_max_hops;
|
||||
writeFrame(rsp, sizeof(rsp));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -252,6 +252,7 @@ protected:
|
||||
bool isAutoAddEnabled() const override;
|
||||
bool shouldAutoAddContactType(uint8_t type) const override;
|
||||
bool shouldOverwriteWhenFull() const override;
|
||||
uint8_t getAutoAddMaxHops() const override;
|
||||
void onContactsFull() override;
|
||||
void onContactOverwrite(const uint8_t *pub_key) override;
|
||||
|
||||
|
||||
@@ -166,6 +166,15 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet *packet, const mesh::Identity &id,
|
||||
return;
|
||||
}
|
||||
|
||||
// check hop limit for new contacts (0 = no limit, 1 = direct (0 hops), N = up to N-1 hops)
|
||||
uint8_t max_hops = getAutoAddMaxHops();
|
||||
if (max_hops > 0 && packet->getPathHashCount() >= max_hops) {
|
||||
ContactInfo ci;
|
||||
populateContactFromAdvert(ci, id, parser, timestamp);
|
||||
onDiscoveredContact(ci, true, packet->path_len, packet->path);
|
||||
return;
|
||||
}
|
||||
|
||||
from = allocateContactSlot();
|
||||
if (from == nullptr) {
|
||||
LOG_WRN("onAdvertRecv: contact table full, cannot allocate slot");
|
||||
|
||||
@@ -118,6 +118,7 @@ protected:
|
||||
virtual bool shouldAutoAddContactType(uint8_t type) const { return true; }
|
||||
virtual void onContactsFull() {}
|
||||
virtual bool shouldOverwriteWhenFull() const { return false; }
|
||||
virtual uint8_t getAutoAddMaxHops() const { return 0; } // 0 = no limit, 1 = direct (0 hops), N = up to N-1 hops
|
||||
virtual void onContactOverwrite(const uint8_t *pub_key) {}
|
||||
virtual void onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path_len, const uint8_t *path) = 0;
|
||||
virtual ContactInfo *processAck(const uint8_t *data) = 0;
|
||||
|
||||
@@ -723,7 +723,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
_prefs->advert_loc_policy = ADVERT_LOC_SHARE;
|
||||
savePrefs();
|
||||
strcpy(reply, "ok");
|
||||
} else if (memcmp(command + 11, "prefs", 4) == 0) {
|
||||
} else if (memcmp(command + 11, "prefs", 5) == 0) {
|
||||
_prefs->advert_loc_policy = ADVERT_LOC_PREFS;
|
||||
savePrefs();
|
||||
strcpy(reply, "ok");
|
||||
|
||||
@@ -69,6 +69,7 @@ struct NodePrefs {
|
||||
uint8_t autoadd_config;
|
||||
uint8_t client_repeat; // 1 = offgrid mode (forward packets), 0 = companion only
|
||||
uint8_t path_hash_mode; // which path mode to use when sending (0-2)
|
||||
uint8_t autoadd_max_hops; // 0 = no limit, 1 = direct (0 hops), N = up to N-1 hops (max 64)
|
||||
};
|
||||
|
||||
/* Default prefs — MUST match LoRaConfig.h defaults for radio interop.
|
||||
|
||||
@@ -818,6 +818,44 @@ uint32_t lr11xx_get_random(const struct device *dev)
|
||||
return random;
|
||||
}
|
||||
|
||||
void lr11xx_reset_agc(const struct device *dev)
|
||||
{
|
||||
struct lr11xx_data *data = dev->data;
|
||||
|
||||
k_mutex_lock(&data->spi_mutex, K_FOREVER);
|
||||
|
||||
/* Warm sleep — powers down analog frontend (resets AGC gain state) */
|
||||
lr11xx_system_sleep_cfg_t sleep_cfg = {
|
||||
.is_warm_start = true,
|
||||
.is_rtc_timeout = false,
|
||||
};
|
||||
lr11xx_system_set_sleep(&data->hal_ctx, sleep_cfg, 0);
|
||||
k_sleep(K_USEC(500));
|
||||
|
||||
/* Wake to STDBY_RC for calibration */
|
||||
lr11xx_system_set_standby(&data->hal_ctx, LR11XX_SYSTEM_STANDBY_CFG_RC);
|
||||
|
||||
/* Recalibrate all blocks */
|
||||
lr11xx_system_calibrate(&data->hal_ctx, 0x3F);
|
||||
|
||||
/* Re-calibrate image rejection for the actual operating frequency */
|
||||
if (data->configured) {
|
||||
uint16_t freq_mhz = data->modem_cfg.frequency / 1000000;
|
||||
lr11xx_system_calibrate_image_in_mhz(&data->hal_ctx,
|
||||
freq_mhz - 4, freq_mhz + 4);
|
||||
}
|
||||
|
||||
/* Re-apply RX boost if it was enabled */
|
||||
if (data->rx_boost_enabled) {
|
||||
lr11xx_radio_cfg_rx_boosted(&data->hal_ctx, true);
|
||||
lr11xx_system_clear_irq_status(&data->hal_ctx,
|
||||
LR11XX_SYSTEM_IRQ_ALL_MASK);
|
||||
data->rx_boost_applied = true;
|
||||
}
|
||||
|
||||
k_mutex_unlock(&data->spi_mutex);
|
||||
}
|
||||
|
||||
/* ── Deferred hardware init (runs on first lora_config call) ────────── */
|
||||
|
||||
static int lr11xx_hw_init(struct lr11xx_data *data,
|
||||
|
||||
@@ -73,6 +73,19 @@ void lr11xx_set_rx_boost(const struct device *dev, bool enable);
|
||||
*/
|
||||
uint32_t lr11xx_get_random(const struct device *dev);
|
||||
|
||||
/**
|
||||
* @brief Reset AGC by performing warm sleep + full recalibration
|
||||
*
|
||||
* Warm sleep powers down the analog frontend (resets AGC gain state),
|
||||
* then Calibrate(0x3F) refreshes all blocks. Re-applies image
|
||||
* calibration for the operating frequency and RX boost afterward.
|
||||
*
|
||||
* Must be called while NOT actively receiving a packet.
|
||||
*
|
||||
* @param dev LoRa device
|
||||
*/
|
||||
void lr11xx_reset_agc(const struct device *dev);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -61,6 +61,19 @@ void sx126x_set_rx_duty_cycle(const struct device *dev, bool enable);
|
||||
*/
|
||||
void sx126x_set_rx_boost(const struct device *dev, bool enable);
|
||||
|
||||
/**
|
||||
* @brief Reset AGC by performing warm sleep + full recalibration
|
||||
*
|
||||
* Warm sleep powers down the analog frontend (resets AGC gain state),
|
||||
* then Calibrate(0x7F) refreshes all blocks (ADC, PLL, image, oscillators).
|
||||
* Re-applies DIO2 RF switch, RX boosted gain, and image calibration afterward.
|
||||
*
|
||||
* Must be called while NOT actively receiving a packet.
|
||||
*
|
||||
* @param dev LoRa device
|
||||
*/
|
||||
void sx126x_reset_agc(const struct device *dev);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user