mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-09-01 21:08:19 +00:00
Make Rxdutycycling Great Again
This commit is contained in:
@@ -364,7 +364,7 @@ The custom `lr11xx_lora.c` driver handles several LR1110 firmware bugs:
|
||||
- **RX buffer drift**: Buffer base shifts 4 bytes per packet → `clear_rxbuffer()` after every RX
|
||||
- **Header error**: Can shift buffer pointer → standby before RX restart
|
||||
- **DIO1 stuck HIGH**: 5-cycle detection → full hardware reset + recovery
|
||||
- **RX duty cycle broken**: 23-40% packet loss → disabled, always continuous RX
|
||||
- **RX duty cycle**: wired via `SetRxDutyCycle` MODE_RX, sized by the shared adapter math (same as SX126x). The earlier "broken, 23-40% loss" verdict was a window-sizing bug (over-sleep + no header budget), not a chip defect — default-off, HW-verify before production use.
|
||||
|
||||
### 5.5 Default Radio Parameters
|
||||
|
||||
|
||||
+44
-17
@@ -485,27 +485,54 @@ config ZEPHCORE_LORA_RX_DUTY_CYCLE
|
||||
bool "Enable LoRa RX duty cycle power saving"
|
||||
default n
|
||||
help
|
||||
Enable RX duty cycling for power saving on battery-powered devices.
|
||||
Uses RadioLib's algorithm to calculate optimal timing based on SF/BW.
|
||||
Enable RX duty cycling (chip-autonomous sniff mode) for power
|
||||
saving on battery-powered devices. This sets the boot default;
|
||||
it can be toggled at runtime via CLI "set rxduty on/off".
|
||||
|
||||
Disabled by default for all roles. Enable manually if you want
|
||||
power savings and have verified acceptable packet loss/latency for
|
||||
your environment and radio.
|
||||
Duty cycle mode also resets AGC state after every received
|
||||
packet via Calibrate(ALL), preventing the "near-far" gain-lock
|
||||
problem where a strong signal desensitises the receiver.
|
||||
Still disabled for LR1110 (cannot lock on mid-preamble, causes packet
|
||||
loss) and LR2021 (untested — enable manually after verifying no
|
||||
packet loss). Can be toggled at runtime via CLI.
|
||||
Window timing is computed per SF/BW/preamble from primary-source
|
||||
constraints (SX1261/2 datasheet sec 13.1.7, AN1200.36): the deaf
|
||||
time per cycle (sleep + TCXO/PLL wake transition) never exceeds
|
||||
the budget needed to guarantee one full detection window inside
|
||||
the sender's preamble, and the post-detect timer budget
|
||||
(2*rxPeriod + sleepPeriod) always covers preamble + header.
|
||||
With a 32-symbol sender preamble (current MeshCore firmware at
|
||||
SF<=8) this yields ~40-55% radio-off time with zero loss against
|
||||
updated senders. Presets whose preamble is too short for a
|
||||
guaranteed catch (16 symbols at SF>=9) automatically fall back to
|
||||
continuous RX — no fake duty cycling.
|
||||
|
||||
The key insight is that LoRa preamble symbols are all identical,
|
||||
so CAD/RX can detect preamble even if we wake mid-stream. The
|
||||
algorithm ensures we always catch at least 8 symbols regardless
|
||||
of when we wake within the preamble window.
|
||||
Caveat for mixed meshes: nodes still on pre-preamble-32 firmware
|
||||
(16 symbols at SF<=8) are only caught ~50% worst-phase. Keep
|
||||
this off on infrastructure until the local mesh has converted.
|
||||
Floods are usually re-heard via updated repeaters; direct
|
||||
messages from legacy nodes are the real exposure.
|
||||
|
||||
Power savings: ~60-70% reduction in RX current (10-15mA to 3-5mA).
|
||||
Duty cycle mode also resets AGC state on entry via
|
||||
Calibrate(ALL), pins the boosted RX gain into the warm-start
|
||||
retention list (datasheet sec 9.6, mandatory for SetRxDutyCycle),
|
||||
and runs a parked-RX watchdog that re-arms the cycle after a
|
||||
false preamble detect (inspect via CLI "get dc.restarts").
|
||||
|
||||
Can be toggled at runtime via CLI "set rxduty on/off".
|
||||
LR1110/LR20xx use the same chip-level SetRxDutyCycle MODE_RX
|
||||
(identical preamble-detect-and-extend mechanism) sized by the same
|
||||
adapter math; the LR1110's earlier "broken" status was a window-
|
||||
sizing bug, not a chip defect. Both are wired but HW-unverified —
|
||||
confirm no packet loss on a live board before trusting in
|
||||
production. LR1110 has no infinite-park failure mode (its DC uses a
|
||||
bounded 2*rx+sleep timeout), so it re-arms via the normal RX-timeout
|
||||
path rather than the SX126x watchdog.
|
||||
|
||||
config ZEPHCORE_LORA_DC_MIN_SYMBOLS
|
||||
int "RX duty cycle: preamble symbols required for detection"
|
||||
range 4 16
|
||||
default 8
|
||||
help
|
||||
Minimum LoRa preamble symbols that must land inside one open RX
|
||||
window for detection to be considered guaranteed. 8 is Semtech's
|
||||
own figure for sniff mode (AN1200.36 sec 4); their time-synced
|
||||
LoRaWAN stacks budget 6. Raise to 10-12 to trade radio-off time
|
||||
for extra detection margin on very noisy sites. SF5/6 adds +4
|
||||
internally. Lowering below 8 risks missed packets at low SNR.
|
||||
|
||||
config ZEPHCORE_APC
|
||||
bool "Adaptive Power Control (APC)"
|
||||
|
||||
@@ -201,7 +201,7 @@ All `set uplink.*` changes are saved immediately and only applied after reboot.
|
||||
| `get loop.detect` | Loop detection level: `off`, `minimal`, `moderate`, or `strict` |
|
||||
| `get radio.rxgain` | RX gain boost: `0` or `1` |
|
||||
| `get rxduty` | RX duty cycle mode: `0` or `1` |
|
||||
| `get dc.restarts` | Duty-cycle preamble false-positive re-arm counter. High values mean the preamble detector is tripping on noise/interference without real packets arriving — inflates RX-on time and drains battery. Reset by `clear stats`. |
|
||||
| `get dc.restarts` | Duty-cycle preamble false-positive re-arm counter (RxTimeout re-arms + parked-RX watchdog recoveries). High values mean the preamble detector is tripping on noise/interference without real packets arriving — inflates RX-on time and drains battery; packets are never lost to it. Reset by `clear stats`. |
|
||||
| `get adc.multiplier` | Battery voltage ADC calibration multiplier |
|
||||
| `get bootloader.ver` | Bootloader version string |
|
||||
| `get public.key` | *(USB only)* Node's public key as hex |
|
||||
@@ -243,7 +243,7 @@ Changes are persisted immediately unless noted. Some require a reboot.
|
||||
| `set path.hash.mode <mode>` | 0, 1, or 2 | Path hashing algorithm |
|
||||
| `set loop.detect <mode>` | `off`, `minimal`, `moderate`, `strict` | Loop detection sensitivity |
|
||||
| `set radio.rxgain <0\|1\|on\|off>` | | RX gain boost *(reboot required)* |
|
||||
| `set rxduty <0\|1\|on\|off>` | | RX duty cycle mode *(reboot required)* |
|
||||
| `set rxduty <0\|1\|on\|off>` | | RX duty cycle mode *(reboot required)*. Window timing auto-sized per SF/BW/preamble from the SX126x datasheet constraints (boot log line `rxduty:` shows the result). Zero-loss guarantee assumes senders on preamble-32 firmware (current MeshCore at SF≤8); legacy preamble-16 senders are only caught ~50% worst-phase — keep off until the local mesh has converted. Presets with 16-symbol preambles (SF≥9) fall back to continuous RX automatically. |
|
||||
| `set adc.multiplier <mult>` | (0 = use board default) | Battery voltage ADC calibration multiplier |
|
||||
| `set prv.key <hex>` | 64-char hex (32-byte key) | Replace private key; derive new identity *(reboot to apply)* |
|
||||
|
||||
|
||||
@@ -533,6 +533,16 @@ void ZephyrDataStore::loadPrefs(NodePrefs &prefs)
|
||||
} else {
|
||||
prefs.auto_shutdown_mv = CONFIG_ZEPHCORE_AUTO_SHUTDOWN_MILLIVOLTS;
|
||||
}
|
||||
|
||||
/* Offset 150: rx_duty_cycle (ZephCore extension). Absent in pre-existing
|
||||
* files → leave the caller's in-RAM default (0 = continuous RX) untouched
|
||||
* so the no-op past-EOF read can't force it on. */
|
||||
if (off < len) {
|
||||
prefs.rx_duty_cycle = buf[off++];
|
||||
if (prefs.rx_duty_cycle > 1) {
|
||||
prefs.rx_duty_cycle = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ZephyrDataStore::savePrefs(const NodePrefs &prefs)
|
||||
@@ -605,7 +615,9 @@ void ZephyrDataStore::savePrefs(const NodePrefs &prefs)
|
||||
/* Offset 148: auto_shutdown_mv (ZephCore extension, 2 bytes LE) */
|
||||
buf[off++] = prefs.auto_shutdown_mv & 0xFF;
|
||||
buf[off++] = (prefs.auto_shutdown_mv >> 8) & 0xFF;
|
||||
/* Total: 150 bytes */
|
||||
/* Offset 150: rx_duty_cycle (ZephCore extension) */
|
||||
buf[off++] = prefs.rx_duty_cycle;
|
||||
/* Total: 151 bytes */
|
||||
|
||||
bool ok = atomicReplaceFile(PREFS_FILE, buf, off);
|
||||
LOG_DBG("savePrefs: wrote %s, ok=%d (%d bytes), name='%.16s'",
|
||||
|
||||
@@ -79,4 +79,9 @@ void LR1110Radio::hwResetAGC()
|
||||
lr11xx_reset_agc(_dev);
|
||||
}
|
||||
|
||||
uint32_t LR1110Radio::hwWakeupTimeUs()
|
||||
{
|
||||
return lr11xx_get_wakeup_time_us(_dev);
|
||||
}
|
||||
|
||||
} /* namespace mesh */
|
||||
|
||||
@@ -28,6 +28,7 @@ protected:
|
||||
bool hwIsReceiving() override;
|
||||
void hwSetRxBoost(bool enable) override;
|
||||
void hwResetAGC() override;
|
||||
uint32_t hwWakeupTimeUs() override;
|
||||
};
|
||||
|
||||
} /* namespace mesh */
|
||||
|
||||
@@ -23,8 +23,18 @@ static uint16_t preambleLengthForSF(uint8_t sf)
|
||||
return (sf <= 8) ? 32 : 16;
|
||||
}
|
||||
|
||||
static constexpr uint16_t RX_DUTY_RX_SYMBOLS = 13;
|
||||
static constexpr uint16_t RX_DUTY_SLEEP_SYMBOLS = 3;
|
||||
/* Minimum preamble symbols that must land inside one open duty-cycle RX
|
||||
* window for guaranteed detection. 8 is Semtech's own figure for sniff
|
||||
* mode (AN1200.36 §4: "8 symbols in LoRa make up the time required to
|
||||
* ensure that the SX1261/2 detects a valid incoming packet"); their
|
||||
* time-synced LoRaWAN stacks budget 6, so 8 already carries margin.
|
||||
* SF5/6 need more symbols to reach sensitivity (RadioLib/LBM use 12). */
|
||||
static uint16_t rxDutyDetectSymbols(uint8_t sf)
|
||||
{
|
||||
uint16_t d = CONFIG_ZEPHCORE_LORA_DC_MIN_SYMBOLS;
|
||||
|
||||
return (sf >= 7) ? d : (uint16_t)(d + 4);
|
||||
}
|
||||
|
||||
/* ── Constructor ─────────────────────────────────────────────── */
|
||||
|
||||
@@ -39,6 +49,7 @@ 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),
|
||||
_tx_power_reduction_db(0),
|
||||
_dc_last_rx_us(0), _dc_last_sleep_us(0),
|
||||
_config_cached(false),
|
||||
_has_radio_override(false),
|
||||
_override_freq(0), _override_bw(0),
|
||||
@@ -421,32 +432,88 @@ void LoRaRadioBase::startReceive()
|
||||
int ret;
|
||||
|
||||
if (_rx_duty_cycle_enabled) {
|
||||
/* Fixed duty-cycle window for field validation.
|
||||
* Keep build-time tunable via constants above. */
|
||||
/* Duty-cycle window sizing. All constraints primary-sourced
|
||||
* (SX1261/2 DS rev 2.2 §13.1.7 + AN1200.36):
|
||||
*
|
||||
* 1. Catch — worst case is a preamble starting D−ε symbols
|
||||
* before an RX window closes (detection aborts, must
|
||||
* complete in the NEXT window), so the total deaf time per
|
||||
* cycle (programmed sleep + wake transition) must satisfy
|
||||
* sleep + trans ≤ (P − 2D − 1)·Tsym
|
||||
* with 1 symbol margin for the chip's RC64k sleep timer.
|
||||
* Stricter than AN1200.36's own "P ≥ sleep + D" model,
|
||||
* which ignores the window-tail arrival case.
|
||||
* 2. Complete — DS: "Tpreamble + Theader ≤ 2·rxPeriod +
|
||||
* sleepPeriod". A preamble detected at its first symbols
|
||||
* restarts the chip timer with 2R+S; that budget must cover
|
||||
* the rest of the preamble + sync (4.25) + header (~8),
|
||||
* rounded up to P + 14 symbols. The driver also sets
|
||||
* StopTimerOnPreamble, but this sizing keeps packets safe
|
||||
* under either documented timer behaviour.
|
||||
* 3. Floor — rxPeriod ≥ (D+1)·Tsym so any single window can
|
||||
* detect on its own.
|
||||
*
|
||||
* No viable sleep budget (short preamble, or the TCXO restart
|
||||
* eats it) → honest fall-through to continuous RX. */
|
||||
struct lora_modem_config cfg;
|
||||
buildModemConfig(cfg, false);
|
||||
|
||||
uint8_t sf = (uint8_t)cfg.datarate;
|
||||
uint32_t bw_hz = bandwidth_to_hz(cfg.bandwidth);
|
||||
float bw_khz = (float)bw_hz / 1000.0f;
|
||||
if (bw_khz > 0.0f) {
|
||||
uint32_t symbol_us = (uint32_t)((float)(1 << sf) * 1000.0f / bw_khz);
|
||||
uint32_t rx_us = RX_DUTY_RX_SYMBOLS * symbol_us;
|
||||
uint32_t sleep_us = RX_DUTY_SLEEP_SYMBOLS * symbol_us;
|
||||
const uint8_t sf = (uint8_t)cfg.datarate;
|
||||
const uint32_t bw_hz = bandwidth_to_hz(cfg.bandwidth);
|
||||
const uint16_t P = cfg.preamble_len;
|
||||
const uint16_t D = rxDutyDetectSymbols(sf);
|
||||
|
||||
ret = lora_recv_duty_cycle(_dev,
|
||||
K_USEC(rx_us),
|
||||
K_USEC(sleep_us),
|
||||
rxCallbackStatic, this);
|
||||
if (ret == 0) {
|
||||
atomic_set(&_in_recv_mode, 1);
|
||||
return;
|
||||
}
|
||||
if (ret != -ENOSYS) {
|
||||
LOG_ERR("lora_recv_duty_cycle failed: %d", ret);
|
||||
if (bw_hz > 0 && P > 2 * D + 1) {
|
||||
const uint32_t sym_us = (uint32_t)
|
||||
(((uint64_t)(1U << sf) * 1000000ULL) / bw_hz);
|
||||
const uint32_t trans_us = hwWakeupTimeUs();
|
||||
const uint32_t deaf_us =
|
||||
(uint32_t)(P - 2 * D - 1) * sym_us;
|
||||
|
||||
if (deaf_us > trans_us + 2000) {
|
||||
const uint32_t sleep_us = deaf_us - trans_us;
|
||||
const uint32_t complete_us =
|
||||
(uint32_t)(P + 14) * sym_us;
|
||||
uint32_t rx_us = (uint32_t)(D + 1) * sym_us;
|
||||
|
||||
if (complete_us > sleep_us &&
|
||||
rx_us < (complete_us - sleep_us + 1) / 2) {
|
||||
rx_us = (complete_us - sleep_us + 1) / 2;
|
||||
}
|
||||
|
||||
if (rx_us != _dc_last_rx_us ||
|
||||
sleep_us != _dc_last_sleep_us) {
|
||||
_dc_last_rx_us = rx_us;
|
||||
_dc_last_sleep_us = sleep_us;
|
||||
LOG_INF("rxduty: rx=%ums sleep=%ums trans=%ums (P=%u D=%u, off=%u%%)",
|
||||
rx_us / 1000, sleep_us / 1000,
|
||||
trans_us / 1000, P, D,
|
||||
(uint32_t)(((uint64_t)sleep_us * 100) /
|
||||
(rx_us + sleep_us + trans_us)));
|
||||
}
|
||||
|
||||
ret = lora_recv_duty_cycle(_dev,
|
||||
K_USEC(rx_us),
|
||||
K_USEC(sleep_us),
|
||||
rxCallbackStatic, this);
|
||||
if (ret == 0) {
|
||||
atomic_set(&_in_recv_mode, 1);
|
||||
return;
|
||||
}
|
||||
if (ret != -ENOSYS) {
|
||||
LOG_ERR("lora_recv_duty_cycle failed: %d", ret);
|
||||
}
|
||||
/* Fall through to continuous RX */
|
||||
} else if (_dc_last_rx_us != UINT32_MAX) {
|
||||
_dc_last_rx_us = UINT32_MAX;
|
||||
LOG_INF("rxduty: wake transition %uus exceeds deaf budget %uus — continuous RX",
|
||||
trans_us, deaf_us);
|
||||
}
|
||||
} else if (_dc_last_rx_us != UINT32_MAX) {
|
||||
_dc_last_rx_us = UINT32_MAX;
|
||||
LOG_INF("rxduty: preamble %u too short for guaranteed catch (need >%u syms) — continuous RX",
|
||||
P, 2 * D + 1);
|
||||
}
|
||||
/* Fall through to normal recv_async */
|
||||
}
|
||||
|
||||
ret = lora_recv_async(_dev, rxCallbackStatic, this);
|
||||
|
||||
@@ -112,6 +112,13 @@ protected:
|
||||
/** GPIO-only BUSY check (no SPI). Default false for chips without duty-cycle sleep. */
|
||||
virtual bool hwIsChipBusy() { return false; }
|
||||
|
||||
/** 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
|
||||
* §13.1.7 the TCXO startup delay is inserted between the sleep and RX
|
||||
* periods, outside both. Default suits XTAL parts (LR2021). */
|
||||
virtual uint32_t hwWakeupTimeUs() { return 1500; }
|
||||
|
||||
/* Set to true by subclasses using the loramac-node driver backend.
|
||||
* Disables the direction-only fast path in configureTx()/configureRx():
|
||||
* loramac-node calls Radio.SetTxConfig() and Radio.SetRxConfig() which
|
||||
@@ -164,6 +171,12 @@ protected:
|
||||
bool _rx_boost_enabled;
|
||||
int8_t _tx_power_reduction_db;
|
||||
|
||||
/* Last duty-cycle timing handed to the driver — used to log timing
|
||||
* 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;
|
||||
|
||||
/* Config cache — skip redundant hwConfigure() */
|
||||
struct lora_modem_config _last_cfg;
|
||||
bool _config_cached;
|
||||
|
||||
@@ -91,6 +91,11 @@ bool SX126xRadio::hwIsChipBusy()
|
||||
return sx126x_is_chip_busy(_dev);
|
||||
}
|
||||
|
||||
uint32_t SX126xRadio::hwWakeupTimeUs()
|
||||
{
|
||||
return sx126x_get_wakeup_time_us(_dev);
|
||||
}
|
||||
|
||||
uint32_t SX126xRadio::getDutyCycleTimeoutRestarts() const
|
||||
{
|
||||
return sx126x_get_dc_timeout_restarts(_dev);
|
||||
|
||||
@@ -31,6 +31,7 @@ protected:
|
||||
void hwSetRxBoost(bool enable) override;
|
||||
void hwResetAGC() override;
|
||||
bool hwIsChipBusy() override;
|
||||
uint32_t hwWakeupTimeUs() override;
|
||||
};
|
||||
|
||||
} /* namespace mesh */
|
||||
|
||||
@@ -241,12 +241,6 @@ bool RepeaterDataStore::loadPrefs(NodePrefs& prefs) {
|
||||
LOG_INF("loadPrefs: upgraded prefs format (%d -> 296 bytes)", (int)entry.size);
|
||||
}
|
||||
|
||||
/* One-time migration: disable RX duty cycle if it was previously enabled. */
|
||||
if (prefs.rx_duty_cycle != 0) {
|
||||
prefs.rx_duty_cycle = 0;
|
||||
savePrefs(prefs);
|
||||
LOG_INF("loadPrefs: migrated rx_duty_cycle to disabled");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -259,7 +259,8 @@ should ONLY contain settings that can't be inferred from hardware:
|
||||
CONFIG_ZEPHCORE_UI_DISPLAY Auto from DT zephyr,display chosen
|
||||
CONFIG_SPI Auto from ZEPHCORE_RADIO_LR1110
|
||||
CONFIG_NORDIC_QSPI_NOR Auto from DT nordic,qspi-nor node
|
||||
CONFIG_ZEPHCORE_LORA_RX_DUTY_CYCLE Auto: ON for companion+SX1262, OFF for repeater/LR1110
|
||||
CONFIG_ZEPHCORE_LORA_RX_DUTY_CYCLE OFF by default for all roles (boot default only;
|
||||
runtime toggle via CLI "set rxduty on/off")
|
||||
CONFIG_ESP_SPIRAM Auto: ON when DT psram0 size > 0 (ESP32-S/C5)
|
||||
CONFIG_ESP_SPIRAM_SIZE Auto: read from DT psram0 size by upstream Kconfig
|
||||
|
||||
|
||||
@@ -79,10 +79,16 @@ struct lr11xx_data {
|
||||
volatile bool in_rx_mode;
|
||||
|
||||
/* Extension features (duty cycle, boost) */
|
||||
bool rx_duty_cycle_enabled; /* unused — LR1110 always continuous RX */
|
||||
bool rx_duty_cycle_enabled;
|
||||
bool rx_boost_enabled;
|
||||
bool rx_boost_applied; /* RX boost register written to hardware */
|
||||
|
||||
/* Stored duty-cycle timing from recv_duty_cycle() — the re-arm paths
|
||||
* (start_rx / restart_rx) reuse these exact values, never recompute:
|
||||
* window sizing is owned by the adapter layer (LoRaRadioBase). */
|
||||
uint32_t dc_rx_ms;
|
||||
uint32_t dc_sleep_ms;
|
||||
|
||||
/* CAD state */
|
||||
lora_cad_cb cad_cb;
|
||||
void *cad_user_data;
|
||||
@@ -305,18 +311,33 @@ static void lr11xx_start_rx(struct lr11xx_data *data,
|
||||
lr11xx_apply_modem_config(data, cfg, false);
|
||||
|
||||
/* Apply RX boost — persistent register, only written once.
|
||||
* Deferred from hw_init to here so radio is fully configured. */
|
||||
if (data->rx_boost_enabled && !data->rx_boost_applied) {
|
||||
* Deferred from hw_init to here so radio is fully configured.
|
||||
* In duty-cycle mode we force a re-apply on every (re)start: the
|
||||
* chip's sleep-with-retention warm wakes are autonomous (no IRQ per
|
||||
* wake, so software can't intervene mid-cycle), and we don't want to
|
||||
* assume the boost register survives them — cheap insurance against
|
||||
* the SX126x-style silent -3 dB loss across warm starts. */
|
||||
if (data->rx_boost_enabled &&
|
||||
(!data->rx_boost_applied || data->rx_duty_cycle_enabled)) {
|
||||
lr11xx_radio_cfg_rx_boosted(ctx, true);
|
||||
data->rx_boost_applied = true;
|
||||
}
|
||||
|
||||
/* Start continuous RX.
|
||||
* 0xFFFFFF is the magic RTC-step value for continuous RX.
|
||||
* Must use the raw RTC-step API — set_rx() converts from ms,
|
||||
* which overflows uint32_t and gives a ~131 s timeout instead.
|
||||
* LR1110 always uses continuous RX (duty cycle is broken). */
|
||||
lr11xx_radio_set_rx_with_timeout_in_rtc_step(ctx, 0xFFFFFF);
|
||||
if (data->rx_duty_cycle_enabled &&
|
||||
data->dc_rx_ms != 0 && data->dc_sleep_ms != 0) {
|
||||
/* Sniff mode: standard-RX listening with the chip's autonomous
|
||||
* sleep/wake. MODE_RX uses the same preamble-detect-and-extend
|
||||
* (2*rx + sleep) mechanism as the SX126x — catches a preamble
|
||||
* mid-window. Timing is computed by the adapter. */
|
||||
lr11xx_radio_set_rx_duty_cycle(ctx, data->dc_rx_ms,
|
||||
data->dc_sleep_ms, LR11XX_RADIO_RX_DUTY_CYCLE_MODE_RX);
|
||||
} else {
|
||||
/* Start continuous RX.
|
||||
* 0xFFFFFF is the magic RTC-step value for continuous RX.
|
||||
* Must use the raw RTC-step API — set_rx() converts from ms,
|
||||
* which overflows uint32_t and gives a ~131 s timeout instead. */
|
||||
lr11xx_radio_set_rx_with_timeout_in_rtc_step(ctx, 0xFFFFFF);
|
||||
}
|
||||
|
||||
/* LR1110 firmware sets CMD_ERROR IRQ flag on several write commands
|
||||
* (SetModParams, SetSyncWord, SetRxBoosted, SetRx) across all
|
||||
@@ -345,9 +366,23 @@ static void lr11xx_restart_rx(struct lr11xx_data *data)
|
||||
void *ctx = &data->hal_ctx;
|
||||
|
||||
lr11xx_system_clear_irq_status(ctx, LR11XX_SYSTEM_IRQ_ALL_MASK);
|
||||
lr11xx_radio_set_rx_with_timeout_in_rtc_step(ctx, 0xFFFFFF);
|
||||
|
||||
/* RX boost persists through SetRx — no re-apply needed. */
|
||||
if (data->rx_duty_cycle_enabled &&
|
||||
data->dc_rx_ms != 0 && data->dc_sleep_ms != 0) {
|
||||
/* Re-arm sniff mode with the stored timing (e.g. after RX_DONE
|
||||
* or a false-preamble 2*rx+sleep timeout dropped the chip to
|
||||
* standby). Re-apply boost — same warm-start caution as
|
||||
* start_rx. */
|
||||
if (data->rx_boost_enabled) {
|
||||
lr11xx_radio_cfg_rx_boosted(ctx, true);
|
||||
}
|
||||
lr11xx_radio_set_rx_duty_cycle(ctx, data->dc_rx_ms,
|
||||
data->dc_sleep_ms, LR11XX_RADIO_RX_DUTY_CYCLE_MODE_RX);
|
||||
} else {
|
||||
lr11xx_radio_set_rx_with_timeout_in_rtc_step(ctx, 0xFFFFFF);
|
||||
/* RX boost persists through SetRx — no re-apply needed. */
|
||||
}
|
||||
|
||||
data->in_rx_mode = true;
|
||||
}
|
||||
|
||||
@@ -772,6 +807,61 @@ static int lr11xx_lora_recv_async(const struct device *dev,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ── Driver API: recv_duty_cycle ─────────────────────────────────────── */
|
||||
|
||||
static int lr11xx_lora_recv_duty_cycle(const struct device *dev,
|
||||
k_timeout_t rx_period,
|
||||
k_timeout_t sleep_period,
|
||||
lora_recv_cb cb, void *user_data)
|
||||
{
|
||||
struct lr11xx_data *data = dev->data;
|
||||
const struct lr11xx_config *cfg = dev->config;
|
||||
|
||||
/* NULL cb = cancel (same as recv_async cancel) */
|
||||
if (cb == NULL) {
|
||||
k_mutex_lock(&data->spi_mutex, K_FOREVER);
|
||||
data->async_rx_cb = NULL;
|
||||
data->async_rx_user_data = NULL;
|
||||
data->rx_duty_cycle_enabled = false;
|
||||
data->in_rx_mode = false;
|
||||
k_mutex_unlock(&data->spi_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!data->configured) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Explicit timing only — the adapter (LoRaRadioBase) owns the window
|
||||
* sizing. No driver-side auto-compute. */
|
||||
if (K_TIMEOUT_EQ(rx_period, K_FOREVER) ||
|
||||
K_TIMEOUT_EQ(sleep_period, K_FOREVER)) {
|
||||
LOG_ERR("recv_duty_cycle: explicit rx/sleep periods required");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
uint32_t rx_ms = k_ticks_to_ms_ceil32(rx_period.ticks);
|
||||
uint32_t slp_ms = k_ticks_to_ms_ceil32(sleep_period.ticks);
|
||||
if (rx_ms < 1) rx_ms = 1;
|
||||
if (slp_ms < 1) slp_ms = 1;
|
||||
|
||||
k_mutex_lock(&data->spi_mutex, K_FOREVER);
|
||||
|
||||
data->async_rx_cb = cb;
|
||||
data->async_rx_user_data = user_data;
|
||||
data->dc_rx_ms = rx_ms;
|
||||
data->dc_sleep_ms = slp_ms;
|
||||
data->rx_duty_cycle_enabled = true;
|
||||
|
||||
/* start_rx() reads rx_duty_cycle_enabled + dc_*_ms and issues
|
||||
* SetRxDutyCycle(MODE_RX) with a forced boost re-apply. */
|
||||
lr11xx_start_rx(data, cfg);
|
||||
|
||||
k_mutex_unlock(&data->spi_mutex);
|
||||
LOG_INF("recv_duty_cycle: rx=%ums sleep=%ums", rx_ms, slp_ms);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ── Driver API: recv (sync) ─────────────────────────────────────────── */
|
||||
|
||||
static int lr11xx_lora_recv(const struct device *dev, uint8_t *buf,
|
||||
@@ -818,6 +908,21 @@ bool lr11xx_is_receiving(const struct device *dev)
|
||||
LR11XX_SYSTEM_IRQ_SYNC_WORD_HEADER_VALID)) != 0;
|
||||
}
|
||||
|
||||
uint32_t lr11xx_get_wakeup_time_us(const struct device *dev)
|
||||
{
|
||||
const struct lr11xx_config *cfg = dev->config;
|
||||
/* Context restore + PLL lock (~1.5 ms) plus the TCXO startup delay
|
||||
* where fitted. Per the LR11xx SetRxDutyCycle model the wake
|
||||
* transition is deaf time between sleep and RX, counting against the
|
||||
* adapter's preamble-catch budget — same accounting as the SX126x. */
|
||||
uint32_t us = 1500;
|
||||
|
||||
if (cfg->tcxo_voltage_mv > 0) {
|
||||
us += cfg->tcxo_startup_delay_ms * 1000U;
|
||||
}
|
||||
return us;
|
||||
}
|
||||
|
||||
void lr11xx_set_rx_boost(const struct device *dev, bool enable)
|
||||
{
|
||||
struct lr11xx_data *data = dev->data;
|
||||
@@ -1203,7 +1308,12 @@ static DEVICE_API(lora, lr11xx_lora_api) = {
|
||||
.recv_async = lr11xx_lora_recv_async,
|
||||
.cad = lr11xx_lora_cad,
|
||||
.cad_async = lr11xx_lora_cad_async,
|
||||
/* .recv_duty_cycle = NULL — LR1110 duty cycle broken */
|
||||
/* MODE_RX sniff: same preamble-detect-and-extend (2*rx+sleep) as the
|
||||
* SX126x. The earlier "broken" verdict was a window-sizing bug
|
||||
* (over-sleep + no header budget), not a chip defect — now sized by
|
||||
* the shared adapter math. Default-off via prefs; HW-verify on a
|
||||
* live LR1110 before trusting in production. */
|
||||
.recv_duty_cycle = lr11xx_lora_recv_duty_cycle,
|
||||
};
|
||||
|
||||
#define LR11XX_INIT(n) \
|
||||
|
||||
@@ -50,6 +50,19 @@ bool lr11xx_is_receiving(const struct device *dev);
|
||||
*/
|
||||
void lr11xx_set_rx_boost(const struct device *dev, bool enable);
|
||||
|
||||
/**
|
||||
* @brief Radio deaf time per duty-cycle wake transition, in microseconds
|
||||
*
|
||||
* Context restore + PLL lock plus the DTS-configured TCXO startup delay
|
||||
* where fitted. Used by the adapter (LoRaRadioBase) to size duty-cycle
|
||||
* windows so the wake transition is charged against the preamble-catch
|
||||
* budget — same accounting as the SX126x.
|
||||
*
|
||||
* @param dev LoRa device
|
||||
* @return Transition deaf time in microseconds
|
||||
*/
|
||||
uint32_t lr11xx_get_wakeup_time_us(const struct device *dev);
|
||||
|
||||
/**
|
||||
* @brief Get a random number from the radio
|
||||
*
|
||||
|
||||
@@ -86,6 +86,13 @@ struct lr20xx_data {
|
||||
bool rx_boost_enabled;
|
||||
bool rx_boost_applied;
|
||||
|
||||
/* Stored duty-cycle timing from recv_duty_cycle() — re-arm paths
|
||||
* must reuse these exact values, never recompute: the window sizing
|
||||
* (detection budget, datasheet completion rule, wake transition) is
|
||||
* owned by the adapter layer. */
|
||||
uint32_t dc_rx_ms;
|
||||
uint32_t dc_sleep_ms;
|
||||
|
||||
/* CAD state */
|
||||
lora_cad_cb cad_cb;
|
||||
void *cad_user_data;
|
||||
@@ -474,77 +481,28 @@ static void lr20xx_apply_modem_config(struct lr20xx_data *data,
|
||||
|
||||
/* ── RX duty cycle ──────────────────────────────────────────────────── */
|
||||
|
||||
/* Minimum preamble symbols needed for detection (same as SX126x) */
|
||||
#define LR20XX_DC_MIN_SYMBOLS_SF7_PLUS 8
|
||||
#define LR20XX_DC_MIN_SYMBOLS_SF6_LESS 12
|
||||
#define LR20XX_DC_TCXO_DELAY_US 1000
|
||||
|
||||
/**
|
||||
* Compute duty cycle timing from current modem config and issue the
|
||||
* SetRxDutyCycle command. Returns true on success, false if the
|
||||
* preamble is too short (falls back to continuous RX).
|
||||
*
|
||||
* Mirrors the SX126x symbol-based calculation: sleep through
|
||||
* (preamble_len - min_symbols - 2) symbols, wake for the remainder
|
||||
* plus margin.
|
||||
* Re-issue the SetRxDutyCycle command with the timing stored by
|
||||
* lr20xx_lora_recv_duty_cycle(). Returns true on success, false if no
|
||||
* timing has been provided yet (falls back to continuous RX).
|
||||
*/
|
||||
static bool lr20xx_apply_rx_duty_cycle(struct lr20xx_data *data)
|
||||
{
|
||||
void *ctx = &data->hal_ctx;
|
||||
struct lora_modem_config *mc = &data->modem_cfg;
|
||||
|
||||
uint8_t sf = (uint8_t)mc->datarate;
|
||||
float bw_khz = bw_enum_to_khz(mc->bandwidth);
|
||||
uint16_t preamble_len = mc->preamble_len;
|
||||
|
||||
uint16_t min_symbols = (sf >= 7) ? LR20XX_DC_MIN_SYMBOLS_SF7_PLUS
|
||||
: LR20XX_DC_MIN_SYMBOLS_SF6_LESS;
|
||||
|
||||
int16_t sleep_symbols = (int16_t)preamble_len - (int16_t)min_symbols;
|
||||
if (sleep_symbols <= 0) {
|
||||
LOG_WRN("Preamble too short for duty cycle "
|
||||
"(need >%d symbols, have %d) — continuous RX",
|
||||
min_symbols, preamble_len);
|
||||
if (data->dc_rx_ms == 0 || data->dc_sleep_ms == 0) {
|
||||
LOG_WRN("No duty-cycle timing stored — continuous RX");
|
||||
data->rx_duty_cycle_enabled = false;
|
||||
lr20xx_radio_common_set_rx_with_timeout_in_rtc_step(
|
||||
ctx, 0xFFFFFF);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* symbol_us = 2^SF / BW_kHz * 1000 */
|
||||
uint32_t symbol_us = (uint32_t)((float)(1 << sf) * 1000.0f / bw_khz);
|
||||
lr20xx_radio_common_set_rx_duty_cycle(ctx, data->dc_rx_ms,
|
||||
data->dc_sleep_ms, LR20XX_RADIO_COMMON_RX_DUTY_CYCLE_MODE_RX);
|
||||
|
||||
/* Shave 2 symbols off sleep for timing margin */
|
||||
int16_t sleep_symbols_safe = sleep_symbols - 2;
|
||||
if (sleep_symbols_safe < 1) {
|
||||
sleep_symbols_safe = 1;
|
||||
}
|
||||
uint32_t sleep_period_us = (uint16_t)sleep_symbols_safe * symbol_us;
|
||||
|
||||
uint32_t preamble_total_us = (preamble_len + 1) * symbol_us;
|
||||
int32_t wake_calc1 = ((int32_t)preamble_total_us -
|
||||
((int32_t)sleep_period_us -
|
||||
LR20XX_DC_TCXO_DELAY_US)) / 2;
|
||||
uint32_t wake_calc2 = (min_symbols + 1) * symbol_us;
|
||||
|
||||
uint32_t wake_period_us =
|
||||
(wake_calc1 > 0 && (uint32_t)wake_calc1 > wake_calc2)
|
||||
? (uint32_t)wake_calc1 : wake_calc2;
|
||||
|
||||
uint32_t rx_ms = (wake_period_us + 999) / 1000; /* round up */
|
||||
uint32_t slp_ms = (sleep_period_us + 999) / 1000;
|
||||
|
||||
if (rx_ms < 1) {
|
||||
rx_ms = 1;
|
||||
}
|
||||
if (slp_ms < 1) {
|
||||
slp_ms = 1;
|
||||
}
|
||||
|
||||
lr20xx_radio_common_set_rx_duty_cycle(ctx, rx_ms, slp_ms,
|
||||
LR20XX_RADIO_COMMON_RX_DUTY_CYCLE_MODE_RX);
|
||||
|
||||
LOG_DBG("RX duty cycle: SF%d rx=%ums sleep=%ums", sf, rx_ms, slp_ms);
|
||||
LOG_DBG("RX duty cycle re-armed: rx=%ums sleep=%ums",
|
||||
data->dc_rx_ms, data->dc_sleep_ms);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1311,6 +1269,13 @@ static int lr20xx_lora_recv_duty_cycle(const struct device *dev,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Explicit timing only — the adapter owns the window sizing. */
|
||||
if (K_TIMEOUT_EQ(rx_period, K_FOREVER) ||
|
||||
K_TIMEOUT_EQ(sleep_period, K_FOREVER)) {
|
||||
LOG_ERR("recv_duty_cycle: explicit rx/sleep periods required");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
k_mutex_lock(&data->spi_mutex, K_FOREVER);
|
||||
|
||||
data->async_rx_cb = cb;
|
||||
@@ -1328,6 +1293,10 @@ static int lr20xx_lora_recv_duty_cycle(const struct device *dev,
|
||||
if (rx_ms < 1) rx_ms = 1;
|
||||
if (slp_ms < 1) slp_ms = 1;
|
||||
|
||||
/* Store for the re-arm paths (lr20xx_start_rx / lr20xx_restart_rx)
|
||||
* so every re-entry uses exactly this timing. */
|
||||
data->dc_rx_ms = rx_ms;
|
||||
data->dc_sleep_ms = slp_ms;
|
||||
data->rx_duty_cycle_enabled = true;
|
||||
lr20xx_radio_common_set_rx_duty_cycle(ctx, rx_ms, slp_ms,
|
||||
LR20XX_RADIO_COMMON_RX_DUTY_CYCLE_MODE_RX);
|
||||
|
||||
@@ -88,6 +88,20 @@ void sx126x_apply_heltec_reg_patch(const struct device *dev);
|
||||
*/
|
||||
void sx126x_reset_agc(const struct device *dev);
|
||||
|
||||
/**
|
||||
* @brief Radio deaf time per duty-cycle wake transition, in microseconds
|
||||
*
|
||||
* Context save/restore + PLL lock (~1.5 ms) plus the DTS-configured TCXO
|
||||
* startup delay where fitted. Per datasheet rev 2.2 §13.1.7, the TCXO
|
||||
* delay is inserted between the sleep and RX periods of every duty-cycle
|
||||
* wake — it counts against the preamble-catch budget, outside both
|
||||
* programmed periods. Used by the adapter to size duty-cycle windows.
|
||||
*
|
||||
* @param dev LoRa device
|
||||
* @return Transition deaf time in microseconds
|
||||
*/
|
||||
uint32_t sx126x_get_wakeup_time_us(const struct device *dev);
|
||||
|
||||
/**
|
||||
* @brief Get duty-cycle preamble false-positive counter
|
||||
*
|
||||
|
||||
@@ -68,7 +68,7 @@ index 17689720dd2..09982dc2e70 100644
|
||||
return -EINVAL;
|
||||
}
|
||||
diff --git a/drivers/lora/native/sx126x/sx126x.c b/drivers/lora/native/sx126x/sx126x.c
|
||||
index 30243ba5dc7..d2339ee8780 100644
|
||||
index 30243ba5dc7..69eefee6e0f 100644
|
||||
--- a/drivers/lora/native/sx126x/sx126x.c
|
||||
+++ b/drivers/lora/native/sx126x/sx126x.c
|
||||
@@ -6,14 +6,21 @@
|
||||
@@ -173,21 +173,22 @@ index 30243ba5dc7..d2339ee8780 100644
|
||||
}
|
||||
|
||||
static int sx126x_set_sync_word(const struct device *dev, bool public_network)
|
||||
@@ -247,6 +311,13 @@ static int sx126x_set_rx_gain(const struct device *dev, bool boosted)
|
||||
@@ -247,6 +311,14 @@ static int sx126x_set_rx_gain(const struct device *dev, bool boosted)
|
||||
{
|
||||
uint8_t val = boosted ? SX126X_RX_GAIN_BOOSTED : SX126X_RX_GAIN_POWER_SAVING;
|
||||
|
||||
+ /* Retention NOT used: without writing SX126X_REG_RX_GAIN_RETENTION_0,
|
||||
+ * the chip resets 0x08AC to power-saving (0x94) on every SetRx command.
|
||||
+ * This clears any residual AGC state accumulated during strong-signal
|
||||
+ * reception (near-far problem: strong first relay locks AGC, weaker
|
||||
+ * second relay missed). sx126x_restart_rx() and hwStartReceive() both
|
||||
+ * re-apply the desired gain after SetRx, after BUSY goes low and before
|
||||
+ * any preamble can arrive (~3.4 ms RC startup window). */
|
||||
+ /* Host-driven RX paths do not rely on retention: the chip resets
|
||||
+ * 0x08AC to power-saving (0x94) on every SetRx command, and
|
||||
+ * sx126x_restart_rx() / recv_async re-apply the desired gain right
|
||||
+ * after SetRx (after BUSY goes low, before any preamble can arrive).
|
||||
+ * Duty-cycle mode is different: its sleep->RX wakes are chip-internal
|
||||
+ * warm starts the host never sees, so the DC entry points
|
||||
+ * additionally pin 0x08AC into the warm-start retention list via
|
||||
+ * sx126x_retain_rx_gain() (DS sec 9.6: mandatory for SetRxDutyCycle). */
|
||||
return sx126x_hal_write_regs(dev, SX126X_REG_RX_GAIN, &val, 1);
|
||||
}
|
||||
|
||||
@@ -296,7 +367,7 @@ static int sx126x_get_packet_status(const struct device *dev,
|
||||
@@ -296,7 +368,7 @@ static int sx126x_get_packet_status(const struct device *dev,
|
||||
uint8_t buf[3];
|
||||
int ret;
|
||||
|
||||
@@ -196,7 +197,7 @@ index 30243ba5dc7..d2339ee8780 100644
|
||||
if (ret == 0) {
|
||||
/* RSSI is -value/2 dBm */
|
||||
*rssi = -((int16_t)buf[0] >> 1);
|
||||
@@ -367,6 +438,26 @@ static int sx126x_chip_init(const struct device *dev)
|
||||
@@ -367,6 +439,26 @@ static int sx126x_chip_init(const struct device *dev)
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -223,7 +224,7 @@ index 30243ba5dc7..d2339ee8780 100644
|
||||
/* Set packet type to LoRa */
|
||||
ret = sx126x_set_packet_type(dev, SX126X_PACKET_TYPE_LORA);
|
||||
if (ret < 0) {
|
||||
@@ -374,10 +465,23 @@ static int sx126x_chip_init(const struct device *dev)
|
||||
@@ -374,10 +466,23 @@ static int sx126x_chip_init(const struct device *dev)
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -250,7 +251,7 @@ index 30243ba5dc7..d2339ee8780 100644
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Set IRQ params failed: %d", ret);
|
||||
return ret;
|
||||
@@ -398,7 +502,7 @@ static void sx126x_dio1_callback(const struct device *dev)
|
||||
@@ -398,7 +503,7 @@ static void sx126x_dio1_callback(const struct device *dev)
|
||||
{
|
||||
struct sx126x_data *data = dev->data;
|
||||
|
||||
@@ -259,7 +260,7 @@ index 30243ba5dc7..d2339ee8780 100644
|
||||
}
|
||||
|
||||
static void sx126x_set_rf_path(const struct device *dev, bool enable, bool tx)
|
||||
@@ -406,7 +510,20 @@ static void sx126x_set_rf_path(const struct device *dev, bool enable, bool tx)
|
||||
@@ -406,7 +511,20 @@ static void sx126x_set_rf_path(const struct device *dev, bool enable, bool tx)
|
||||
const struct sx126x_hal_config *config = dev->config;
|
||||
|
||||
sx126x_hal_set_antenna_enable(dev, enable);
|
||||
@@ -281,10 +282,44 @@ index 30243ba5dc7..d2339ee8780 100644
|
||||
sx126x_hal_set_rf_switch(dev, enable, tx);
|
||||
}
|
||||
}
|
||||
@@ -455,6 +572,98 @@ static int sx126x_reconnect_rf_gpios(const struct device *dev)
|
||||
@@ -455,6 +573,215 @@ static int sx126x_reconnect_rf_gpios(const struct device *dev)
|
||||
}
|
||||
#endif /* CONFIG_PM_DEVICE */
|
||||
|
||||
+/* -- Duty-cycle support helpers -- */
|
||||
+
|
||||
+/* Watchdog sampling period. Must exceed the longest legitimate
|
||||
+ * preamble-detect -> header gap at any supported preset so that two
|
||||
+ * consecutive parked sightings can never be one real packet (worst
|
||||
+ * case SF12/BW62.5, 16-sym preamble: ~1.9 s). */
|
||||
+#define SX126X_DC_WATCHDOG_MS 3000
|
||||
+
|
||||
+/* DS sec 9.6: the RX gain register (0x08AC) is not part of the warm-start
|
||||
+ * retention memory. RxDutyCycle sleep->RX wakes are chip-internal warm
|
||||
+ * starts -- without adding 0x08AC to the retention list, every window
|
||||
+ * after the first sleep reverts to power-saving gain (-3 dB sensitivity)
|
||||
+ * with no way for the host to intervene. The datasheet calls this
|
||||
+ * "mandatory when using the SetRxDutyCycle method, to keep on using the
|
||||
+ * Rx Boosted gain". Layout: entry count, then big-endian register
|
||||
+ * address per entry. */
|
||||
+static void sx126x_retain_rx_gain(const struct device *dev)
|
||||
+{
|
||||
+ uint8_t retention[3] = { 0x01, 0x08, 0xAC };
|
||||
+
|
||||
+ sx126x_hal_write_regs(dev, SX126X_REG_RX_GAIN_RETENTION_0,
|
||||
+ retention, 3);
|
||||
+}
|
||||
+
|
||||
+/* (Re)arm the parked-RX watchdog and forget any parked evidence -- called
|
||||
+ * from every duty-cycle (re)start site, which all bulk-clear the IRQ
|
||||
+ * register first. */
|
||||
+static void sx126x_dc_watchdog_kick(struct sx126x_data *data)
|
||||
+{
|
||||
+ data->dc_watchdog_strikes = 0;
|
||||
+ k_work_reschedule_for_queue(&data->dio1_wq, &data->dc_watchdog_work,
|
||||
+ K_MSEC(SX126X_DC_WATCHDOG_MS));
|
||||
+}
|
||||
+
|
||||
+/* -- Lightweight RX restart -- */
|
||||
+
|
||||
+/* Restart RX as fast as possible -- used for RX->RX transitions in the
|
||||
@@ -341,12 +376,20 @@ index 30243ba5dc7..d2339ee8780 100644
|
||||
+ sx126x_hal_write_cmd(dev, SX126X_CMD_STOP_TIMER_ON_PREAMBLE,
|
||||
+ &stop_on_preamble, 1);
|
||||
+
|
||||
+ /* Keep boosted RX gain alive across the chip-internal
|
||||
+ * sleep->RX wakes (DS sec 9.6, mandatory for DC mode).
|
||||
+ * Re-issued here as cheap insurance against Calibrate(ALL)
|
||||
+ * above disturbing the list. */
|
||||
+ sx126x_retain_rx_gain(dev);
|
||||
+
|
||||
+ /* Re-apply duty cycle with stored timing */
|
||||
+ uint8_t dc_buf[6];
|
||||
+ sys_put_be24(data->dc_rx_time, &dc_buf[0]);
|
||||
+ sys_put_be24(data->dc_sleep_time, &dc_buf[3]);
|
||||
+ sx126x_hal_write_cmd(dev, SX126X_CMD_SET_RX_DUTY_CYCLE,
|
||||
+ dc_buf, 6);
|
||||
+
|
||||
+ sx126x_dc_watchdog_kick(data);
|
||||
+ } else {
|
||||
+ /* Non-DC continuous RX: plain SetRx(continuous), no symbol
|
||||
+ * timer. An earlier draft armed SetLoRaSymbNumTimeout here for
|
||||
@@ -376,11 +419,86 @@ index 30243ba5dc7..d2339ee8780 100644
|
||||
+ sx126x_hal_write_regs(dev, SX126X_REG_RX_GAIN, &gain, 1);
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+/* Parked-RX watchdog (see struct field comment). Runs on the dedicated
|
||||
+ * DIO1 work queue so it serialises with the IRQ handler and restart
|
||||
+ * paths. Self-terminating: returns without re-arming when duty-cycle RX
|
||||
+ * is no longer active -- every DC (re)start kicks it again. */
|
||||
+static void sx126x_dc_watchdog_handler(struct k_work *work)
|
||||
+{
|
||||
+ struct k_work_delayable *dwork = k_work_delayable_from_work(work);
|
||||
+ struct sx126x_data *data = CONTAINER_OF(dwork, struct sx126x_data,
|
||||
+ dc_watchdog_work);
|
||||
+ const struct device *dev = data->dev;
|
||||
+ uint16_t irq_status = 0;
|
||||
+ bool parked = false;
|
||||
+
|
||||
+ if (!data->rx_duty_cycle_enabled ||
|
||||
+ atomic_get(&data->state) != SX126X_STATE_RX) {
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ /* Legit packet mid-decode (HEADER_VALID latched): check again later. */
|
||||
+ if (data->rx_packet_active) {
|
||||
+ data->dc_watchdog_strikes = 0;
|
||||
+ goto rearm;
|
||||
+ }
|
||||
+
|
||||
+ /* BUSY high = duty-cycle sleep phase = cycling normally. */
|
||||
+ if (sx126x_hal_is_busy(dev)) {
|
||||
+ data->dc_watchdog_strikes = 0;
|
||||
+ goto rearm;
|
||||
+ }
|
||||
+
|
||||
+ if (k_mutex_lock(&data->lock, K_NO_WAIT) != 0) {
|
||||
+ /* Another operation owns the chip right now -- not parked. */
|
||||
+ data->dc_watchdog_strikes = 0;
|
||||
+ goto rearm;
|
||||
+ }
|
||||
+
|
||||
+ /* Re-check BUSY under the lock (same pattern as get_rssi_inst): a
|
||||
+ * sleep transition between the check above and the SPI read below
|
||||
+ * would stall the HAL and force-wake the chip. */
|
||||
+ if (!sx126x_hal_is_busy(dev) &&
|
||||
+ sx126x_get_irq_status(dev, &irq_status) == 0) {
|
||||
+ parked = (irq_status & SX126X_IRQ_PREAMBLE_DETECTED) &&
|
||||
+ !(irq_status & SX126X_IRQ_HEADER_VALID);
|
||||
+ }
|
||||
+
|
||||
+ if (!parked) {
|
||||
+ data->dc_watchdog_strikes = 0;
|
||||
+ k_mutex_unlock(&data->lock);
|
||||
+ goto rearm;
|
||||
+ }
|
||||
+
|
||||
+ if (++data->dc_watchdog_strikes < 2) {
|
||||
+ /* First sighting: could be a real preamble whose header is
|
||||
+ * still in flight. A second sighting one full period later
|
||||
+ * cannot be the same packet (period > worst-case
|
||||
+ * preamble + sync + header airtime). */
|
||||
+ k_mutex_unlock(&data->lock);
|
||||
+ goto rearm;
|
||||
+ }
|
||||
+
|
||||
+ /* Two strikes: a false preamble detect parked the chip in full RX
|
||||
+ * (StopTimerOnPreamble behaviour, AN1200.36 sec 3). Nothing was
|
||||
+ * lost -- parked RX still receives -- but the duty cycle stopped.
|
||||
+ * Re-arm it. Shares the dc_timeout_restarts stat: both paths mean
|
||||
+ * "false preamble cost extra RX-on time". */
|
||||
+ atomic_inc(&data->dc_timeout_restarts);
|
||||
+ (void)sx126x_restart_rx(dev, data);
|
||||
+ k_mutex_unlock(&data->lock);
|
||||
+ return; /* restart_rx re-kicked the watchdog */
|
||||
+
|
||||
+rearm:
|
||||
+ k_work_schedule_for_queue(&data->dio1_wq, &data->dc_watchdog_work,
|
||||
+ K_MSEC(SX126X_DC_WATCHDOG_MS));
|
||||
+}
|
||||
+
|
||||
static int sx126x_set_sleep(const struct device *dev)
|
||||
{
|
||||
struct sx126x_data *data = dev->data;
|
||||
@@ -533,8 +742,16 @@ static void sx126x_handle_irq_rx_done(const struct device *dev, uint16_t irq_sta
|
||||
@@ -533,8 +860,16 @@ static void sx126x_handle_irq_rx_done(const struct device *dev, uint16_t irq_sta
|
||||
struct sx126x_data *data = dev->data;
|
||||
struct sx126x_rx_result result = { 0 };
|
||||
uint8_t payload_len = 0, offset = 0;
|
||||
@@ -397,7 +515,7 @@ index 30243ba5dc7..d2339ee8780 100644
|
||||
/* Get received packet info */
|
||||
ret = sx126x_get_rx_buffer_status(dev, &payload_len, &offset);
|
||||
if (ret < 0) {
|
||||
@@ -544,9 +761,26 @@ static void sx126x_handle_irq_rx_done(const struct device *dev, uint16_t irq_sta
|
||||
@@ -544,9 +879,26 @@ static void sx126x_handle_irq_rx_done(const struct device *dev, uint16_t irq_sta
|
||||
/* Get signal quality */
|
||||
sx126x_get_packet_status(dev, &result.rssi, &result.snr);
|
||||
|
||||
@@ -427,7 +545,7 @@ index 30243ba5dc7..d2339ee8780 100644
|
||||
result.status = -EIO;
|
||||
} else {
|
||||
/* Read payload into shared buffer */
|
||||
@@ -564,20 +798,34 @@ static void sx126x_handle_irq_rx_done(const struct device *dev, uint16_t irq_sta
|
||||
@@ -564,20 +916,34 @@ static void sx126x_handle_irq_rx_done(const struct device *dev, uint16_t irq_sta
|
||||
}
|
||||
}
|
||||
|
||||
@@ -474,7 +592,7 @@ index 30243ba5dc7..d2339ee8780 100644
|
||||
}
|
||||
} else {
|
||||
/* Sync mode */
|
||||
@@ -589,8 +837,69 @@ static void sx126x_handle_irq_rx_done(const struct device *dev, uint16_t irq_sta
|
||||
@@ -589,8 +955,69 @@ static void sx126x_handle_irq_rx_done(const struct device *dev, uint16_t irq_sta
|
||||
static void sx126x_handle_irq_timeout(const struct device *dev)
|
||||
{
|
||||
struct sx126x_data *data = dev->data;
|
||||
@@ -544,7 +662,7 @@ index 30243ba5dc7..d2339ee8780 100644
|
||||
sx126x_set_sleep(dev);
|
||||
|
||||
if (data->tx_async_signal != NULL) {
|
||||
@@ -633,10 +942,46 @@ static void sx126x_irq_work_handler(struct k_work *work)
|
||||
@@ -633,10 +1060,46 @@ static void sx126x_irq_work_handler(struct k_work *work)
|
||||
sx126x_handle_irq_rx_done(dev, irq_status);
|
||||
}
|
||||
|
||||
@@ -591,7 +709,7 @@ index 30243ba5dc7..d2339ee8780 100644
|
||||
/* Re-enable the DIO1 interrupt for the next event (unless sleeping) */
|
||||
if (atomic_get(&data->state) != SX126X_REST_STATE) {
|
||||
sx126x_hal_dio1_irq_enable(dev);
|
||||
@@ -682,7 +1027,7 @@ static int sx126x_lora_config(const struct device *dev,
|
||||
@@ -682,7 +1145,7 @@ static int sx126x_lora_config(const struct device *dev,
|
||||
/* Configure PA and TX power based on chip variant and frequency */
|
||||
ret = sx126x_hal_configure_tx_params(dev, config->tx_power,
|
||||
config->frequency,
|
||||
@@ -600,7 +718,7 @@ index 30243ba5dc7..d2339ee8780 100644
|
||||
if (ret < 0) {
|
||||
goto out;
|
||||
}
|
||||
@@ -698,6 +1043,29 @@ static int sx126x_lora_config(const struct device *dev,
|
||||
@@ -698,6 +1161,29 @@ static int sx126x_lora_config(const struct device *dev,
|
||||
goto out;
|
||||
}
|
||||
|
||||
@@ -630,7 +748,7 @@ index 30243ba5dc7..d2339ee8780 100644
|
||||
/* Set sync word */
|
||||
ret = sx126x_set_sync_word(dev, config->public_network);
|
||||
if (ret < 0) {
|
||||
@@ -721,6 +1089,8 @@ out:
|
||||
@@ -721,6 +1207,8 @@ out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -639,7 +757,7 @@ index 30243ba5dc7..d2339ee8780 100644
|
||||
static int sx126x_lora_send_async(const struct device *dev,
|
||||
uint8_t *data_buf, uint32_t data_len,
|
||||
struct k_poll_signal *async)
|
||||
@@ -738,11 +1108,27 @@ static int sx126x_lora_send_async(const struct device *dev,
|
||||
@@ -738,11 +1226,27 @@ static int sx126x_lora_send_async(const struct device *dev,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
@@ -669,7 +787,7 @@ index 30243ba5dc7..d2339ee8780 100644
|
||||
k_mutex_lock(&data->lock, K_FOREVER);
|
||||
|
||||
ret = sx126x_ensure_ready(dev);
|
||||
@@ -752,6 +1138,58 @@ static int sx126x_lora_send_async(const struct device *dev,
|
||||
@@ -752,6 +1256,58 @@ static int sx126x_lora_send_async(const struct device *dev,
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -728,7 +846,7 @@ index 30243ba5dc7..d2339ee8780 100644
|
||||
data->tx_async_signal = async;
|
||||
k_msgq_purge(&data->tx_msgq);
|
||||
|
||||
@@ -777,6 +1215,29 @@ static int sx126x_lora_send_async(const struct device *dev,
|
||||
@@ -777,6 +1333,29 @@ static int sx126x_lora_send_async(const struct device *dev,
|
||||
/* Enable antenna and set TX path */
|
||||
sx126x_set_rf_path(dev, true, true);
|
||||
|
||||
@@ -758,7 +876,7 @@ index 30243ba5dc7..d2339ee8780 100644
|
||||
/* Start transmission with 10 second timeout */
|
||||
ret = sx126x_set_tx(dev, 10000);
|
||||
if (ret < 0) {
|
||||
@@ -847,6 +1308,8 @@ static int sx126x_lora_recv(const struct device *dev, uint8_t *data_buf,
|
||||
@@ -847,6 +1426,8 @@ static int sx126x_lora_recv(const struct device *dev, uint8_t *data_buf,
|
||||
}
|
||||
|
||||
data->rx_cb = NULL;
|
||||
@@ -767,7 +885,7 @@ index 30243ba5dc7..d2339ee8780 100644
|
||||
k_msgq_purge(&data->rx_msgq);
|
||||
|
||||
/* Set packet parameters for variable length reception */
|
||||
@@ -918,6 +1381,8 @@ static int sx126x_lora_recv_async(const struct device *dev,
|
||||
@@ -918,6 +1499,8 @@ static int sx126x_lora_recv_async(const struct device *dev,
|
||||
/* Stop async reception */
|
||||
data->rx_cb = NULL;
|
||||
data->rx_cb_user_data = NULL;
|
||||
@@ -776,7 +894,7 @@ index 30243ba5dc7..d2339ee8780 100644
|
||||
if (atomic_cas(&data->state, SX126X_STATE_RX, SX126X_STATE_IDLE)) {
|
||||
sx126x_set_standby(dev, SX126X_STANDBY_RC);
|
||||
sx126x_set_sleep(dev);
|
||||
@@ -932,6 +1397,19 @@ static int sx126x_lora_recv_async(const struct device *dev,
|
||||
@@ -932,6 +1515,19 @@ static int sx126x_lora_recv_async(const struct device *dev,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
@@ -796,7 +914,7 @@ index 30243ba5dc7..d2339ee8780 100644
|
||||
if (!atomic_cas(&data->state, SX126X_REST_STATE, SX126X_STATE_RX)) {
|
||||
LOG_ERR("Busy");
|
||||
k_mutex_unlock(&data->lock);
|
||||
@@ -945,8 +1423,18 @@ static int sx126x_lora_recv_async(const struct device *dev,
|
||||
@@ -945,8 +1541,18 @@ static int sx126x_lora_recv_async(const struct device *dev,
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -815,7 +933,7 @@ index 30243ba5dc7..d2339ee8780 100644
|
||||
|
||||
/* Set packet parameters */
|
||||
ret = sx126x_set_packet_params(dev,
|
||||
@@ -959,6 +1447,7 @@ static int sx126x_lora_recv_async(const struct device *dev,
|
||||
@@ -959,6 +1565,7 @@ static int sx126x_lora_recv_async(const struct device *dev,
|
||||
SX126X_LORA_IQ_INVERTED : SX126X_LORA_IQ_STANDARD);
|
||||
if (ret < 0) {
|
||||
data->rx_cb = NULL;
|
||||
@@ -823,7 +941,7 @@ index 30243ba5dc7..d2339ee8780 100644
|
||||
sx126x_set_sleep(dev);
|
||||
k_mutex_unlock(&data->lock);
|
||||
return ret;
|
||||
@@ -971,11 +1460,21 @@ static int sx126x_lora_recv_async(const struct device *dev,
|
||||
@@ -971,11 +1578,21 @@ static int sx126x_lora_recv_async(const struct device *dev,
|
||||
ret = sx126x_set_rx(dev, 0);
|
||||
if (ret < 0) {
|
||||
data->rx_cb = NULL;
|
||||
@@ -845,7 +963,7 @@ index 30243ba5dc7..d2339ee8780 100644
|
||||
k_mutex_unlock(&data->lock);
|
||||
return 0;
|
||||
}
|
||||
@@ -1051,7 +1550,7 @@ static int sx126x_lora_test_cw(const struct device *dev, uint32_t frequency,
|
||||
@@ -1051,7 +1668,7 @@ static int sx126x_lora_test_cw(const struct device *dev, uint32_t frequency,
|
||||
|
||||
/* Set PA config and TX power */
|
||||
ret = sx126x_hal_configure_tx_params(dev, tx_power, frequency,
|
||||
@@ -854,7 +972,7 @@ index 30243ba5dc7..d2339ee8780 100644
|
||||
if (ret < 0) {
|
||||
sx126x_set_sleep(dev);
|
||||
k_mutex_unlock(&data->lock);
|
||||
@@ -1083,14 +1582,602 @@ static int sx126x_lora_test_cw(const struct device *dev, uint32_t frequency,
|
||||
@@ -1083,14 +1700,595 @@ static int sx126x_lora_test_cw(const struct device *dev, uint32_t frequency,
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1053,6 +1171,23 @@ index 30243ba5dc7..d2339ee8780 100644
|
||||
+ atomic_set(&data->dc_timeout_restarts, 0);
|
||||
+}
|
||||
+
|
||||
+uint32_t sx126x_get_wakeup_time_us(const struct device *dev)
|
||||
+{
|
||||
+ const struct sx126x_hal_config *config = dev->config;
|
||||
+ /* Context restore + PLL lock: "around 1 ms" per DS fig 13-2 ("not
|
||||
+ * accurate and may vary"), plus 500 us margin for the save side. */
|
||||
+ uint32_t us = 1500;
|
||||
+
|
||||
+ /* DS rev 2.2 sec 13.1.7 note: with a chip-controlled TCXO, the
|
||||
+ * programmed startup delay is inserted between the sleep and RX
|
||||
+ * periods of every duty-cycle wake -- pure deaf time, outside both
|
||||
+ * programmed periods. */
|
||||
+ if (config->dio3_tcxo_enable) {
|
||||
+ us += config->tcxo_startup_delay_ms * 1000U;
|
||||
+ }
|
||||
+ return us;
|
||||
+}
|
||||
+
|
||||
+void sx126x_apply_heltec_reg_patch(const struct device *dev)
|
||||
+{
|
||||
+ struct sx126x_data *data = dev->data;
|
||||
@@ -1360,55 +1495,25 @@ index 30243ba5dc7..d2339ee8780 100644
|
||||
+
|
||||
+ uint32_t rx_time, sleep_time;
|
||||
+
|
||||
+ /* Explicit timing only. The window sizing (detection budget,
|
||||
+ * datasheet Tpreamble+Theader <= 2*rxPeriod+sleepPeriod rule, TCXO
|
||||
+ * transition accounting) is owned by the caller -- the adapter layer
|
||||
+ * knows the mesh preamble policy, the driver just programs periods. */
|
||||
+ if (K_TIMEOUT_EQ(rx_period, K_FOREVER) ||
|
||||
+ K_TIMEOUT_EQ(sleep_period, K_FOREVER)) {
|
||||
+ /* Auto-compute from modem config (RadioLib algorithm) */
|
||||
+ struct lora_modem_config *mc = &data->config;
|
||||
+ uint8_t sf = (uint8_t)mc->datarate;
|
||||
+ uint32_t bw_hz = bandwidth_to_hz(mc->bandwidth);
|
||||
+ float bw_khz = (float)bw_hz / 1000.0f;
|
||||
+ uint16_t preamble_len = mc->preamble_len;
|
||||
+
|
||||
+ uint16_t min_symbols = (sf >= 7) ? 8 : 12;
|
||||
+ int16_t sleep_symbols = (int16_t)preamble_len -
|
||||
+ (int16_t)min_symbols;
|
||||
+ if (sleep_symbols <= 0) {
|
||||
+ LOG_WRN("Preamble too short for duty cycle "
|
||||
+ "(need >%d, have %d)",
|
||||
+ min_symbols, preamble_len);
|
||||
+ data->rx_cb = NULL;
|
||||
+ atomic_inc(&data->rx_cb_gen);
|
||||
+ sx126x_set_sleep(dev);
|
||||
+ k_mutex_unlock(&data->lock);
|
||||
+ atomic_set(&data->state, SX126X_REST_STATE);
|
||||
+ return -EINVAL;
|
||||
+ }
|
||||
+
|
||||
+ uint32_t symbol_us = (uint32_t)((float)(1 << sf) *
|
||||
+ 1000.0f / bw_khz);
|
||||
+
|
||||
+ int16_t sleep_symbols_safe = sleep_symbols - 2;
|
||||
+ if (sleep_symbols_safe < 1) sleep_symbols_safe = 1;
|
||||
+ uint32_t sleep_us = (uint16_t)sleep_symbols_safe * symbol_us;
|
||||
+
|
||||
+ uint32_t preamble_us = (preamble_len + 1) * symbol_us;
|
||||
+ int32_t wake1 = ((int32_t)preamble_us -
|
||||
+ ((int32_t)sleep_us - 1000)) / 2;
|
||||
+ uint32_t wake2 = (min_symbols + 1) * symbol_us;
|
||||
+ uint32_t rx_us = (wake1 > 0 && (uint32_t)wake1 > wake2)
|
||||
+ ? (uint32_t)wake1 : wake2;
|
||||
+
|
||||
+ /* Convert us to 15.625us steps */
|
||||
+ rx_time = (rx_us * 64) / 1000;
|
||||
+ sleep_time = (sleep_us * 64) / 1000;
|
||||
+ } else {
|
||||
+ /* Explicit timing from caller */
|
||||
+ uint32_t rx_us = k_ticks_to_us_ceil32(rx_period.ticks);
|
||||
+ uint32_t slp_us = k_ticks_to_us_ceil32(sleep_period.ticks);
|
||||
+ rx_time = (rx_us * 64) / 1000;
|
||||
+ sleep_time = (slp_us * 64) / 1000;
|
||||
+ LOG_ERR("recv_duty_cycle: explicit rx/sleep periods required");
|
||||
+ data->rx_cb = NULL;
|
||||
+ atomic_inc(&data->rx_cb_gen);
|
||||
+ sx126x_set_sleep(dev);
|
||||
+ k_mutex_unlock(&data->lock);
|
||||
+ atomic_set(&data->state, SX126X_REST_STATE);
|
||||
+ return -EINVAL;
|
||||
+ }
|
||||
+
|
||||
+ /* Convert us to 15.625us steps */
|
||||
+ rx_time = (k_ticks_to_us_ceil32(rx_period.ticks) * 64) / 1000;
|
||||
+ sleep_time = (k_ticks_to_us_ceil32(sleep_period.ticks) * 64) / 1000;
|
||||
+
|
||||
+ if (rx_time < 64) rx_time = 64;
|
||||
+ if (sleep_time < 64) sleep_time = 64;
|
||||
+
|
||||
@@ -1425,16 +1530,22 @@ index 30243ba5dc7..d2339ee8780 100644
|
||||
+ sx126x_hal_write_cmd(dev, SX126X_CMD_STOP_TIMER_ON_PREAMBLE,
|
||||
+ &stop_on_preamble, 1);
|
||||
+
|
||||
+ /* Apply RX gain, then pin it into the warm-start retention list so the
|
||||
+ * chip-internal sleep->RX wakes restore it (DS sec 9.6: mandatory for
|
||||
+ * SetRxDutyCycle -- without this every window after the first sleep
|
||||
+ * listens at power-saving gain, a silent -3 dB sensitivity loss). */
|
||||
+ uint8_t gain = data->rx_boost_enabled ? SX126X_RX_GAIN_BOOSTED
|
||||
+ : SX126X_RX_GAIN_POWER_SAVING;
|
||||
+ sx126x_hal_write_regs(dev, SX126X_REG_RX_GAIN, &gain, 1);
|
||||
+ sx126x_retain_rx_gain(dev);
|
||||
+
|
||||
+ uint8_t buf[6];
|
||||
+
|
||||
+ sys_put_be24(rx_time, &buf[0]);
|
||||
+ sys_put_be24(sleep_time, &buf[3]);
|
||||
+ sx126x_hal_write_cmd(dev, SX126X_CMD_SET_RX_DUTY_CYCLE, buf, 6);
|
||||
+
|
||||
+ /* Apply RX gain */
|
||||
+ uint8_t gain = data->rx_boost_enabled ? SX126X_RX_GAIN_BOOSTED
|
||||
+ : SX126X_RX_GAIN_POWER_SAVING;
|
||||
+ sx126x_hal_write_regs(dev, SX126X_REG_RX_GAIN, &gain, 1);
|
||||
+ sx126x_dc_watchdog_kick(data);
|
||||
+
|
||||
+ k_mutex_unlock(&data->lock);
|
||||
+
|
||||
@@ -1464,7 +1575,7 @@ index 30243ba5dc7..d2339ee8780 100644
|
||||
};
|
||||
|
||||
#ifdef CONFIG_PM_DEVICE
|
||||
@@ -1112,6 +2199,7 @@ static int sx126x_pm_action(const struct device *dev,
|
||||
@@ -1112,6 +2310,7 @@ static int sx126x_pm_action(const struct device *dev,
|
||||
static int sx126x_init(const struct device *dev)
|
||||
{
|
||||
struct sx126x_data *data = dev->data;
|
||||
@@ -1472,7 +1583,7 @@ index 30243ba5dc7..d2339ee8780 100644
|
||||
int ret;
|
||||
|
||||
/* Initialize data structures */
|
||||
@@ -1121,9 +2209,26 @@ static int sx126x_init(const struct device *dev)
|
||||
@@ -1121,9 +2320,29 @@ static int sx126x_init(const struct device *dev)
|
||||
k_msgq_init(&data->rx_msgq, (char *)&data->rx_result,
|
||||
sizeof(struct sx126x_rx_result), 1);
|
||||
k_work_init(&data->irq_work, sx126x_irq_work_handler);
|
||||
@@ -1490,6 +1601,9 @@ index 30243ba5dc7..d2339ee8780 100644
|
||||
+ * sx126x_set_rx_boost() extension API. */
|
||||
+ data->rx_boost_enabled = config->rx_boosted;
|
||||
+ atomic_set(&data->dc_timeout_restarts, 0);
|
||||
+ k_work_init_delayable(&data->dc_watchdog_work,
|
||||
+ sx126x_dc_watchdog_handler);
|
||||
+ data->dc_watchdog_strikes = 0;
|
||||
+
|
||||
+ /* Start dedicated DIO1 work queue */
|
||||
+ k_work_queue_start(&data->dio1_wq, sx126x_dio1_wq_stack,
|
||||
@@ -1500,10 +1614,10 @@ index 30243ba5dc7..d2339ee8780 100644
|
||||
/* Initialize HAL */
|
||||
ret = sx126x_hal_init(dev);
|
||||
diff --git a/drivers/lora/native/sx126x/sx126x.h b/drivers/lora/native/sx126x/sx126x.h
|
||||
index 9dbf3f26586..880b3240d22 100644
|
||||
index 9dbf3f26586..2598b56d048 100644
|
||||
--- a/drivers/lora/native/sx126x/sx126x.h
|
||||
+++ b/drivers/lora/native/sx126x/sx126x.h
|
||||
@@ -56,13 +56,58 @@ struct sx126x_data {
|
||||
@@ -56,13 +56,69 @@ struct sx126x_data {
|
||||
/* Async RX callback */
|
||||
lora_recv_cb rx_cb;
|
||||
void *rx_cb_user_data;
|
||||
@@ -1545,6 +1659,17 @@ index 9dbf3f26586..880b3240d22 100644
|
||||
+ uint32_t dc_rx_time; /* stored duty cycle rx period (15.625us steps) */
|
||||
+ uint32_t dc_sleep_time; /* stored duty cycle sleep period (15.625us steps) */
|
||||
+
|
||||
+ /* Parked-RX watchdog. StopTimerOnPreamble=1 means a false preamble
|
||||
+ * detect parks the chip in full RX "until the next received frame"
|
||||
+ * (AN1200.36 sec 3) with no IRQ fired -- nothing is lost (parked RX
|
||||
+ * still receives) but duty cycling silently stops costing full RX
|
||||
+ * current. The watchdog samples the IRQ register every few seconds
|
||||
+ * and re-arms the duty cycle after two consecutive sightings of
|
||||
+ * PREAMBLE_DETECTED without HEADER_VALID (one packet can never span
|
||||
+ * two sightings; restart sites and terminal IRQs clear the bit). */
|
||||
+ struct k_work_delayable dc_watchdog_work;
|
||||
+ uint8_t dc_watchdog_strikes;
|
||||
+
|
||||
+ /* Duty-cycle preamble false-positive counter: incremented whenever
|
||||
+ * IRQ_RX_TX_TIMEOUT fires during duty-cycle RX (preamble detector
|
||||
+ * tripped but no valid sync/header followed -- re-arm triggered).
|
||||
|
||||
Reference in New Issue
Block a user