mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-09-02 10:33:47 +00:00
rx path fixes, dc.restart wired in to lr chips,
TX timeout magic number is gone
This commit is contained in:
@@ -31,6 +31,16 @@ void LR1110Radio::begin()
|
||||
LoRaRadioBase::begin();
|
||||
}
|
||||
|
||||
uint32_t LR1110Radio::getDutyCycleTimeoutRestarts() const
|
||||
{
|
||||
return lr11xx_get_dc_timeout_restarts(_dev);
|
||||
}
|
||||
|
||||
void LR1110Radio::resetDutyCycleTimeoutRestarts()
|
||||
{
|
||||
lr11xx_reset_dc_timeout_restarts(_dev);
|
||||
}
|
||||
|
||||
/* ── Hardware primitives ──────────────────────────────────────────────── */
|
||||
|
||||
bool LR1110Radio::hwConfigure(const struct lora_modem_config &cfg)
|
||||
|
||||
@@ -18,6 +18,13 @@ public:
|
||||
|
||||
void begin() override;
|
||||
|
||||
/* Duty-cycle false-preamble re-arm count, behind `get dc.restarts`.
|
||||
* Without these the base class's stub answers 0 forever, and the one
|
||||
* instrument for tuning the RX window reads clean on a node that is
|
||||
* re-arming constantly. */
|
||||
uint32_t getDutyCycleTimeoutRestarts() const override;
|
||||
void resetDutyCycleTimeoutRestarts() override;
|
||||
|
||||
protected:
|
||||
/* Hardware primitives */
|
||||
bool hwConfigure(const struct lora_modem_config &cfg) override;
|
||||
|
||||
@@ -51,6 +51,16 @@ void LR2021Radio::resetStats()
|
||||
lr20xx_reset_freq_offset(_dev);
|
||||
}
|
||||
|
||||
uint32_t LR2021Radio::getDutyCycleTimeoutRestarts() const
|
||||
{
|
||||
return lr20xx_get_dc_timeout_restarts(_dev);
|
||||
}
|
||||
|
||||
void LR2021Radio::resetDutyCycleTimeoutRestarts()
|
||||
{
|
||||
lr20xx_reset_dc_timeout_restarts(_dev);
|
||||
}
|
||||
|
||||
int LR2021Radio::formatFreqErrorStatus(char *buf, int cap)
|
||||
{
|
||||
struct lr20xx_freq_offset_stats st;
|
||||
|
||||
@@ -18,6 +18,13 @@ public:
|
||||
|
||||
void begin() override;
|
||||
|
||||
/* Duty-cycle false-preamble re-arm count, behind `get dc.restarts`.
|
||||
* Without these the base class's stub answers 0 forever, and the one
|
||||
* instrument for tuning the RX window reads clean on a node that is
|
||||
* re-arming constantly. */
|
||||
uint32_t getDutyCycleTimeoutRestarts() const override;
|
||||
void resetDutyCycleTimeoutRestarts() override;
|
||||
|
||||
/* LoRa side detectors — LR2021-only multi-SF receive. */
|
||||
bool configSideDetectors(const uint8_t *sfs, uint8_t num) override;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "LoRaRadioBase.h"
|
||||
#include "radio_common.h"
|
||||
#include <mesh/LoRaConfig.h>
|
||||
#include <mesh/MeshCore.h> /* MAX_TRANS_UNIT */
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/random/random.h>
|
||||
#include <string.h>
|
||||
@@ -94,7 +95,12 @@ LoRaRadioBase::LoRaRadioBase(const struct device *lora_dev, MainBoard &board,
|
||||
* it from this thread costs no SPI and cannot race the radio. */
|
||||
uint32_t LoRaRadioBase::txWaitBudgetMs() const
|
||||
{
|
||||
uint32_t air = lora_airtime(_dev, _tx_len ? _tx_len : 255);
|
||||
/* _tx_len is published by startSendRaw() before it releases
|
||||
* _tx_start_sem, so by the time this thread runs it is always the length
|
||||
* of the transmit in flight — the fallback is defensive only, and uses
|
||||
* the protocol maximum rather than a literal so it tracks MAX_TRANS_UNIT
|
||||
* if the FIFO bound ever moves. */
|
||||
uint32_t air = lora_airtime(_dev, _tx_len ? _tx_len : MAX_TRANS_UNIT);
|
||||
|
||||
/* All four drivers behind this class implement .airtime (native sx126x,
|
||||
* lr11xx, lr20xx, loramac-node sx127x), and lora_airtime() dereferences
|
||||
|
||||
@@ -99,19 +99,33 @@ struct lr11xx_data {
|
||||
uint32_t dc_rx_ms;
|
||||
uint32_t dc_sleep_ms;
|
||||
|
||||
/* Duty-cycle re-arms triggered by a timeout, i.e. the false-preamble
|
||||
* case: UM §7.2.6 restarts the window timer with 2*RxPeriod +
|
||||
* SleepPeriod on preamble detection, and when that expires with no
|
||||
* packet the chip leaves the loop and the host puts it back. A climbing
|
||||
* rate means the window is catching noise rather than packets. Exposed
|
||||
* as `get dc.restarts`, which reported a hardcoded 0 on this radio until
|
||||
* nothing counted them. */
|
||||
atomic_t dc_timeout_restarts;
|
||||
|
||||
/* CAD state */
|
||||
lora_cad_cb cad_cb;
|
||||
void *cad_user_data;
|
||||
struct k_sem cad_sem;
|
||||
int cad_result;
|
||||
/* No cad_active flag: there was one, written at four sites and read at
|
||||
* none. The SX126x driver's copy IS read (sx126x_handle_irq_timeout()
|
||||
* returns early on it), so this one was inherited without the read that
|
||||
* gave it a purpose. Not needed here: the TIMEOUT branch below is only
|
||||
* reachable during a CAD when the chip took the CAD->TX exit, and that
|
||||
* path sets tx_active first. Exclusion between CADs comes from both
|
||||
* entry points (LBT from startSendRaw, probes from cadMaintenance)
|
||||
* running on the mesh loop thread. */
|
||||
* none. The SX126x driver's copy IS read — sx126x_handle_irq_timeout()
|
||||
* returns early on it — so this was inherited without the read that gave
|
||||
* it a purpose.
|
||||
*
|
||||
* It is not needed here, and the reason is the locking model, not luck:
|
||||
* sx126x_irq_work_handler() takes no lock at all, so there its timeout
|
||||
* path genuinely races a CAD being set up on the mesh thread and the
|
||||
* flag is the only thing preventing a teardown. This handler holds
|
||||
* spi_mutex across its whole body, and every CAD entry point holds that
|
||||
* same mutex across SetStandby -> clear_irq_status(ALL) -> SetCad — so
|
||||
* the two cannot interleave, and any TIMEOUT latched before the CAD is
|
||||
* discarded by the clear inside lr11xx_do_cad(). */
|
||||
/* Adaptive-CAD: signed offset applied to the per-SF base detPeak on
|
||||
* every LBT CAD; cad_probe_peak overrides for one calibration probe. */
|
||||
int8_t cad_peak_offset;
|
||||
@@ -448,7 +462,16 @@ static void lr11xx_start_rx(struct lr11xx_data *data,
|
||||
* Packet params are NOT re-applied here — they persist through SetRx.
|
||||
* RadioLib (Arduino) also skips re-applying packet params on RX restart.
|
||||
* Only TX changes pld_len, and TX→RX goes through full start_rx(). */
|
||||
static void lr11xx_restart_rx(struct lr11xx_data *data)
|
||||
/* Returns 0 if the receiver is believed to be back on air, <0 if a command the
|
||||
* re-arm depends on was rejected and the caller should escalate.
|
||||
*
|
||||
* "Believed" on the duty-cycle path: verifying there would mean a GetStatus
|
||||
* after SetRxDutyCycle, and that NSS edge terminates the very cycle being
|
||||
* checked (UM §7.2.6) — the probe would cause the fault it looks for. The check
|
||||
* sits on the SetStandby instead, which is the command that actually fails on a
|
||||
* wedged chip and is safe to poll because the standby has just ended any live
|
||||
* cycle. */
|
||||
static int lr11xx_restart_rx(struct lr11xx_data *data)
|
||||
{
|
||||
void *ctx = &data->hal_ctx;
|
||||
|
||||
@@ -461,17 +484,66 @@ static void lr11xx_restart_rx(struct lr11xx_data *data)
|
||||
* or a false-preamble 2*rx+sleep timeout dropped the chip to
|
||||
* standby). Re-apply boost — same warm-start caution as
|
||||
* start_rx. */
|
||||
|
||||
/* SetStandby first. The cycle is not necessarily over when we
|
||||
* get here: UM §7.2.6 terminates the loop on exactly three
|
||||
* things — a packet detected, a host SetStandby, or an NSS wake
|
||||
* from sleep — and a LoRa header error is none of them (it
|
||||
* raises no RX_DONE), so the chip is still cycling on that path.
|
||||
* Re-arming then means driving an NSS edge into a live cycle,
|
||||
* and the manual is explicit about that case: the device "is
|
||||
* woken up from Sleep mode with a falling edge of NSS. In that
|
||||
* case, the user should send the SetStandby() command to avoid
|
||||
* race conditions". LR2021 DS §6.3.8 says the same in nearly
|
||||
* the same words, and there the missing standby was observed on
|
||||
* hardware as a latched CMD_ERROR with DIO1 stuck high, five
|
||||
* strikes to a reset, ~88 ms deaf per noise header error. Free
|
||||
* when the chip is already in standby. */
|
||||
lr11xx_system_set_standby(ctx, LR11XX_SYSTEM_STANDBY_CFG_RC);
|
||||
|
||||
/* Safe to poll: the standby above ended any live cycle, so this
|
||||
* NSS edge has nothing left to disturb. A chip that will not
|
||||
* even take a SetStandby will not take a duty cycle either. */
|
||||
{
|
||||
lr11xx_system_stat1_t s1 = { 0 };
|
||||
|
||||
if (lr11xx_system_get_status(ctx, &s1, NULL, NULL) ==
|
||||
LR11XX_STATUS_OK &&
|
||||
s1.command_status != LR11XX_SYSTEM_CMD_STATUS_OK &&
|
||||
s1.command_status != LR11XX_SYSTEM_CMD_STATUS_DATA) {
|
||||
LOG_WRN("restart_rx: standby rejected (cmd=%d) "
|
||||
"before duty-cycle re-arm",
|
||||
(int)s1.command_status);
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
/* No duty cycle armed, so polling is free of the NSS hazard. */
|
||||
{
|
||||
lr11xx_system_stat2_t s2 = { 0 };
|
||||
|
||||
if (lr11xx_system_get_status(ctx, NULL, &s2, NULL) ==
|
||||
LR11XX_STATUS_OK &&
|
||||
s2.chip_mode != LR11XX_SYSTEM_CHIP_MODE_RX) {
|
||||
LOG_WRN("restart_rx: chip is in mode %d, not RX",
|
||||
(int)s2.chip_mode);
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ── DIO1 IRQ handler (work queue, thread context) ──────────────────── */
|
||||
@@ -652,7 +724,24 @@ static void lr11xx_dio1_work_handler(struct k_work *work)
|
||||
lr11xx_start_rx(data, cfg);
|
||||
rx_restarted = true;
|
||||
} else {
|
||||
lr11xx_restart_rx(data);
|
||||
/* Under a duty cycle this is the false-preamble case:
|
||||
* the 2*RxPeriod + SleepPeriod window the preamble
|
||||
* detect restarted (UM §7.2.6) expired with no packet,
|
||||
* so the chip left the loop. Counting it is what makes
|
||||
* `get dc.restarts` mean something on this radio. */
|
||||
if (data->rx_duty_cycle_enabled) {
|
||||
atomic_inc(&data->dc_timeout_restarts);
|
||||
}
|
||||
if (lr11xx_restart_rx(data) < 0) {
|
||||
/* Escalate to the full path: forces standby,
|
||||
* reprograms the modem and re-issues SetRx from
|
||||
* scratch. The LR11xx counterpart of the
|
||||
* SX126x's retry-from-sleep. If that fails too
|
||||
* the stuck-DIO1 counter still reaches its
|
||||
* hardware reset. */
|
||||
LOG_WRN("Timeout: light re-arm failed — full RX restart");
|
||||
lr11xx_start_rx(data, cfg);
|
||||
}
|
||||
rx_restarted = true;
|
||||
}
|
||||
}
|
||||
@@ -921,12 +1010,40 @@ static int lr11xx_lora_send_async(const struct device *dev,
|
||||
lr11xx_reset_rx_busy_signals(data);
|
||||
lr11xx_hal_enable_dio1_irq(&data->hal_ctx);
|
||||
|
||||
/* Store signal and start TX. 10 s timeout: worst legal preset
|
||||
* (SF12 / BW62.5, max payload) exceeds 5 s airtime — a shorter
|
||||
* timeout would abort mid-transmission. Matches the SX126x driver. */
|
||||
/* Store signal and start TX.
|
||||
*
|
||||
* Chip-side Tx safeguard, scaled from airtime. UM §7.2.3: "If the RTC
|
||||
* event fires before the end of transmission, it will trigger a TIMEOUT
|
||||
* IRQ, and stop the device transmission" — so this must exceed real
|
||||
* airtime. The fixed 10 s it replaces carried the right reasoning with
|
||||
* the arithmetic never done: the comment said the worst preset "exceeds
|
||||
* 5 s", but SF12/BW62.5 at 255 bytes CR 4/8 is 28.6 s, so 10 s truncated
|
||||
* every packet from 76 B up there, and from 31 B at SF12/BW31.25.
|
||||
*
|
||||
* Floored at the previous 10000 so nothing that works today tightens.
|
||||
* Programmed in RTC steps rather than through the millisecond wrapper:
|
||||
* lr11xx_radio_convert_time_in_ms_to_rtc_step() computes ms * 32768 in
|
||||
* uint32 and overflows above 131071 ms, which SF12/BW7.81 at 255 B
|
||||
* (~229 s) reaches. Saturating at the 24-bit field is 512 s. */
|
||||
data->tx_signal = async;
|
||||
data->tx_active = true;
|
||||
lr11xx_radio_set_tx(ctx, 10000);
|
||||
{
|
||||
uint32_t air_ms = lr11xx_lora_airtime(dev, data_len);
|
||||
uint32_t tmo_ms = air_ms + (air_ms / 4U) + 500U;
|
||||
uint64_t steps;
|
||||
|
||||
if (tmo_ms < 10000U) {
|
||||
tmo_ms = 10000U;
|
||||
}
|
||||
steps = ((uint64_t)tmo_ms * 32768U) / 1000U;
|
||||
if (steps > 0x00FFFFFFU) {
|
||||
steps = 0x00FFFFFFU;
|
||||
}
|
||||
LOG_DBG("SET_TX: airtime=%u ms, timeout=%u ms (%u steps)",
|
||||
air_ms, tmo_ms, (uint32_t)steps);
|
||||
lr11xx_radio_set_tx_with_timeout_in_rtc_step(ctx,
|
||||
(uint32_t)steps);
|
||||
}
|
||||
|
||||
k_mutex_unlock(&data->spi_mutex);
|
||||
return 0;
|
||||
@@ -1241,6 +1358,20 @@ bool lr11xx_is_receiving(const struct device *dev)
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t lr11xx_get_dc_timeout_restarts(const struct device *dev)
|
||||
{
|
||||
struct lr11xx_data *data = dev->data;
|
||||
|
||||
return (uint32_t)atomic_get(&data->dc_timeout_restarts);
|
||||
}
|
||||
|
||||
void lr11xx_reset_dc_timeout_restarts(const struct device *dev)
|
||||
{
|
||||
struct lr11xx_data *data = dev->data;
|
||||
|
||||
atomic_set(&data->dc_timeout_restarts, 0);
|
||||
}
|
||||
|
||||
uint32_t lr11xx_get_wakeup_time_us(const struct device *dev)
|
||||
{
|
||||
const struct lr11xx_config *cfg = dev->config;
|
||||
|
||||
@@ -63,6 +63,28 @@ void lr11xx_set_rx_boost(const struct device *dev, bool enable);
|
||||
*/
|
||||
uint32_t lr11xx_get_wakeup_time_us(const struct device *dev);
|
||||
|
||||
/**
|
||||
* @brief Duty-cycle re-arms caused by a timeout since boot or reset
|
||||
*
|
||||
* Counts the false-preamble case: UM §7.2.6 restarts the window timer with
|
||||
* 2*RxPeriod + SleepPeriod on preamble detection, and when that expires with no
|
||||
* packet the chip leaves the loop and the driver puts it back. A climbing rate
|
||||
* means the RX window is catching noise rather than packets. Backs
|
||||
* `get dc.restarts`, which reported a hardcoded 0 on this radio before anything
|
||||
* counted them.
|
||||
*
|
||||
* @param dev LoRa device
|
||||
* @return re-arm count
|
||||
*/
|
||||
uint32_t lr11xx_get_dc_timeout_restarts(const struct device *dev);
|
||||
|
||||
/**
|
||||
* @brief Clear the duty-cycle timeout re-arm counter
|
||||
*
|
||||
* @param dev LoRa device
|
||||
*/
|
||||
void lr11xx_reset_dc_timeout_restarts(const struct device *dev);
|
||||
|
||||
/**
|
||||
* @brief Get a random number from the radio
|
||||
*
|
||||
|
||||
@@ -95,6 +95,15 @@ struct lr20xx_data {
|
||||
bool rx_boost_enabled;
|
||||
bool rx_boost_applied;
|
||||
|
||||
/* Duty-cycle re-arms triggered by a timeout, i.e. the false-preamble
|
||||
* case: the preamble-restarted rx_max_time + cycle_time window expired
|
||||
* with no packet, so the chip left the loop and the host put it back.
|
||||
* The rate is the instrument for duty-cycle tuning — a climbing count
|
||||
* means the window is catching noise, not packets. Exposed as
|
||||
* `get dc.restarts`, which reported a hardcoded 0 on this radio until
|
||||
* nothing counted them. */
|
||||
atomic_t dc_timeout_restarts;
|
||||
|
||||
/* 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
|
||||
@@ -108,11 +117,18 @@ struct lr20xx_data {
|
||||
struct k_sem cad_sem;
|
||||
int cad_result; /* 0=free, 1=busy, <0=error */
|
||||
/* No cad_active flag: there was one, written at five sites and read at
|
||||
* none. It read like a concurrent-CAD guard and guarded nothing — the
|
||||
* real exclusion is that every CAD entry point (LBT from startSendRaw,
|
||||
* probes from cadMaintenance) runs on the mesh loop thread, so two CADs
|
||||
* cannot overlap by construction. A flag nothing tests is worse than no
|
||||
* flag: it invites the next reader to assume the invariant is enforced. */
|
||||
* none. The SX126x driver's copy IS read — sx126x_handle_irq_timeout()
|
||||
* returns early on it — so this was inherited without the read that gave
|
||||
* it a purpose.
|
||||
*
|
||||
* It is not needed here, and the reason is the locking model, not luck:
|
||||
* sx126x_irq_work_handler() takes no lock at all, so there its timeout
|
||||
* path genuinely races a CAD being set up on the mesh thread and the
|
||||
* flag is the only thing preventing a teardown. This handler holds
|
||||
* spi_mutex across its whole body, and every CAD entry point holds that
|
||||
* same mutex across SetStandby -> clear_irq_status(ALL) -> SetCad — so
|
||||
* the two cannot interleave, and any TIMEOUT latched before the CAD is
|
||||
* discarded by the clear inside lr20xx_do_cad(). */
|
||||
/* Adaptive-CAD: signed offset applied to the per-SF base detPeak on
|
||||
* every LBT CAD; cad_probe_peak overrides for one calibration probe. */
|
||||
int8_t cad_peak_offset;
|
||||
@@ -1075,7 +1091,17 @@ static void lr20xx_start_rx(struct lr20xx_data *data,
|
||||
|
||||
/* ── Lightweight RX restart (no modem reconfig) ─────────────────────── */
|
||||
|
||||
static void lr20xx_restart_rx(struct lr20xx_data *data)
|
||||
/* Returns 0 if the receiver is believed to be back on air, <0 if a command the
|
||||
* re-arm depends on was rejected and the caller should escalate.
|
||||
*
|
||||
* "Believed" is deliberate on the duty-cycle path: verification there would mean
|
||||
* a GetStatus after SetRxDutyCycle, and an NSS falling edge terminates the cycle
|
||||
* being checked (DS §6.3.8) — the probe would cause the fault it looks for. So
|
||||
* the check is placed on the SetStandby that precedes the re-arm, which is the
|
||||
* command that actually fails when the chip is wedged, and is safe to poll
|
||||
* because the standby has just ended any live cycle. Continuous RX has no such
|
||||
* constraint and is checked after SetRx. */
|
||||
static int lr20xx_restart_rx(struct lr20xx_data *data)
|
||||
{
|
||||
void *ctx = &data->hal_ctx;
|
||||
|
||||
@@ -1123,26 +1149,62 @@ static void lr20xx_restart_rx(struct lr20xx_data *data)
|
||||
if (lr20xx_system_get_status(ctx, NULL, &s2, NULL) == LR20XX_STATUS_OK &&
|
||||
s2.chip_mode == LR20XX_SYSTEM_CHIP_MODE_RX) {
|
||||
data->in_rx_mode = true;
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (data->rx_duty_cycle_enabled) {
|
||||
/* SetStandby first: a header/CRC error does not terminate the
|
||||
* duty-cycle loop (DS 6.3.8 ends it on packet reception), and
|
||||
* re-arming a live cycle is refused — the refusal latches
|
||||
* CMD_ERROR and holds DIO1 high. Near-free when already in
|
||||
* STDBY_RC. See ARCHITECTURE.md 5.5.1. */
|
||||
lr20xx_system_stat1_t s1 = { 0 };
|
||||
|
||||
/* SetStandby first. A header error does not terminate the
|
||||
* duty-cycle loop — DS §6.3.8 lists exactly three terminators
|
||||
* (packet reception, a host SetStandby, an NSS wake from sleep)
|
||||
* and a header error is none of them — so the chip can still be
|
||||
* cycling here. Re-arming then drives an NSS edge into a live
|
||||
* cycle, and the datasheet is explicit for that case: on a wake
|
||||
* "by the device with a falling edge of NSS. A SetStandby
|
||||
* command should also be sent, to avoid the race conditions".
|
||||
* Observed on hardware without it: latched CMD_ERROR, DIO1 held
|
||||
* high, five strikes to a reset, ~88 ms deaf per noise header
|
||||
* error. Near-free when already in STDBY_RC.
|
||||
* See ARCHITECTURE.md 5.5.1. */
|
||||
lr20xx_system_set_standby_mode(ctx,
|
||||
LR20XX_SYSTEM_STANDBY_MODE_RC);
|
||||
|
||||
/* Safe to poll: the standby above ended any live cycle, so this
|
||||
* NSS edge has nothing left to disturb. If the chip would not
|
||||
* even take a SetStandby, arming a duty cycle on top of it is
|
||||
* pointless — say so and let the caller escalate. */
|
||||
if (lr20xx_system_get_status(ctx, &s1, NULL, NULL) ==
|
||||
LR20XX_STATUS_OK &&
|
||||
s1.command_status != 2 && s1.command_status != 3) {
|
||||
LOG_WRN("restart_rx: standby rejected (cmd=%d) before "
|
||||
"duty-cycle re-arm", s1.command_status);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
lr20xx_apply_rx_duty_cycle(data);
|
||||
} else {
|
||||
lr20xx_radio_common_set_rx_with_timeout_in_rtc_step(
|
||||
ctx, 0xFFFFFF);
|
||||
data->in_rx_mode = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
lr20xx_radio_common_set_rx_with_timeout_in_rtc_step(ctx, 0xFFFFFF);
|
||||
CHECK_CMD(ctx, "restart_rx set_rx");
|
||||
data->in_rx_mode = true;
|
||||
|
||||
/* No duty cycle armed, so polling is free of the NSS hazard. */
|
||||
{
|
||||
lr20xx_system_stat2_t s2 = { 0 };
|
||||
|
||||
if (lr20xx_system_get_status(ctx, NULL, &s2, NULL) ==
|
||||
LR20XX_STATUS_OK &&
|
||||
s2.chip_mode != LR20XX_SYSTEM_CHIP_MODE_RX) {
|
||||
LOG_WRN("restart_rx: chip is in mode %d, not RX",
|
||||
s2.chip_mode);
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Is the receiver still live? Conservative: true (leave it alone) whenever it
|
||||
@@ -1474,8 +1536,26 @@ static void lr20xx_dio1_work_handler(struct k_work *work)
|
||||
lr20xx_start_rx(data, cfg);
|
||||
rx_restarted = true;
|
||||
} else {
|
||||
/* Under a duty cycle this is the false-preamble case:
|
||||
* the preamble-restarted rx_max_time + cycle_time window
|
||||
* expired with no packet, so the chip left the loop.
|
||||
* Counting it is what makes `get dc.restarts` mean
|
||||
* something on this radio. */
|
||||
if (data->rx_duty_cycle_enabled) {
|
||||
atomic_inc(&data->dc_timeout_restarts);
|
||||
}
|
||||
LOG_DBG("Timeout IRQ — restarting RX");
|
||||
lr20xx_restart_rx(data);
|
||||
if (lr20xx_restart_rx(data) < 0) {
|
||||
/* The light re-arm could not get the chip back on
|
||||
* air. Escalate to the full path, which forces
|
||||
* standby, reprograms the modem and re-issues
|
||||
* SetRx from scratch — the LR20xx counterpart of
|
||||
* the SX126x's retry-from-sleep. If that fails
|
||||
* too the chip is wedged, and the stuck-DIO1
|
||||
* counter below still reaches its reset. */
|
||||
LOG_WRN("Timeout: light re-arm failed — full RX restart");
|
||||
lr20xx_start_rx(data, cfg);
|
||||
}
|
||||
rx_restarted = true;
|
||||
}
|
||||
}
|
||||
@@ -2294,6 +2374,20 @@ void lr20xx_reset_freq_offset(const struct device *dev)
|
||||
k_mutex_unlock(&data->spi_mutex);
|
||||
}
|
||||
|
||||
uint32_t lr20xx_get_dc_timeout_restarts(const struct device *dev)
|
||||
{
|
||||
struct lr20xx_data *data = dev->data;
|
||||
|
||||
return (uint32_t)atomic_get(&data->dc_timeout_restarts);
|
||||
}
|
||||
|
||||
void lr20xx_reset_dc_timeout_restarts(const struct device *dev)
|
||||
{
|
||||
struct lr20xx_data *data = dev->data;
|
||||
|
||||
atomic_set(&data->dc_timeout_restarts, 0);
|
||||
}
|
||||
|
||||
uint32_t lr20xx_get_wakeup_time_us(const struct device *dev)
|
||||
{
|
||||
const struct lr20xx_config *cfg = dev->config;
|
||||
|
||||
@@ -65,6 +65,28 @@ uint32_t lr20xx_get_freq_offset(const struct device *dev,
|
||||
*/
|
||||
void lr20xx_reset_freq_offset(const struct device *dev);
|
||||
|
||||
/**
|
||||
* @brief Duty-cycle re-arms caused by a timeout since boot or reset
|
||||
*
|
||||
* Counts the false-preamble case: the preamble-restarted rx_max_time +
|
||||
* cycle_time window expired with no packet, the chip left the duty-cycle loop,
|
||||
* and the driver put it back. A climbing rate means the RX window is catching
|
||||
* noise rather than packets, which is the signal for retuning it. Backs
|
||||
* `get dc.restarts`, which reported a hardcoded 0 on this radio before anything
|
||||
* counted them.
|
||||
*
|
||||
* @param dev LoRa device
|
||||
* @return re-arm count
|
||||
*/
|
||||
uint32_t lr20xx_get_dc_timeout_restarts(const struct device *dev);
|
||||
|
||||
/**
|
||||
* @brief Clear the duty-cycle timeout re-arm counter
|
||||
*
|
||||
* @param dev LoRa device
|
||||
*/
|
||||
void lr20xx_reset_dc_timeout_restarts(const struct device *dev);
|
||||
|
||||
/**
|
||||
* @brief Radio deaf time for one duty-cycle wake transition, in microseconds
|
||||
*
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
Scale the chip-side Tx timeout from airtime instead of a fixed 10 s.
|
||||
|
||||
sx126x_lora_send_async() programmed SetTx with a constant 10000 ms. DS section
|
||||
13.1.5 is explicit that when the Tx timer fires the transmission is stopped, so
|
||||
that constant is not a safeguard on slow presets -- it is a truncation.
|
||||
|
||||
Airtime of a 255-byte packet at CR 4/8 reaches 17.7 s at SF10/BW31.25, 15.9 s at
|
||||
SF11/BW62.5 and 28.6 s at SF12/BW62.5. Against a 10 s ceiling those presets lost
|
||||
every packet from 136 B, 154 B and 76 B up respectively, cut mid-transmission, with
|
||||
nothing in the log connecting the loss to this line: TX_DONE never arrives, the
|
||||
timeout handler re-arms RX, and the host wait is what eventually reports it.
|
||||
|
||||
Scale as airtime + 25% + 500 ms, floored at the previous 10000 so no preset that
|
||||
works today gets a tighter deadline. Clamp to 262143 ms: sx126x_set_tx() converts
|
||||
via SX126X_MS_TO_TIMEOUT (64 steps/ms) and sys_put_be24(), which truncates
|
||||
silently above that. The ceiling is 262 s against a worst reachable airtime of
|
||||
~229 s (SF12/BW7.81 at 255 B), so it never binds on a real packet.
|
||||
|
||||
Kept separate from 0003 because that patch cannot be regenerated from the tree,
|
||||
and the line being changed is upstream context within it rather than an added
|
||||
line -- same reason 0011, 0012 and 0013 are separate.
|
||||
|
||||
diff --git a/drivers/lora/native/sx126x/sx126x.c b/drivers/lora/native/sx126x/sx126x.c
|
||||
--- a/drivers/lora/native/sx126x/sx126x.c
|
||||
+++ b/drivers/lora/native/sx126x/sx126x.c
|
||||
@@ -1432,6 +1432,7 @@
|
||||
|
||||
static int sx126x_lora_cad(const struct device *dev, k_timeout_t timeout);
|
||||
static uint32_t sx126x_cad_timeout_ms(struct sx126x_data *data);
|
||||
+static uint32_t sx126x_lora_airtime(const struct device *dev, uint32_t data_len);
|
||||
|
||||
static int sx126x_lora_send_async(const struct device *dev,
|
||||
uint8_t *data_buf, uint32_t data_len,
|
||||
@@ -1614,8 +1615,34 @@
|
||||
}
|
||||
#endif /* CONFIG_LORA_SX126X_NATIVE_STANDALONE */
|
||||
|
||||
- /* Start transmission with 10 second timeout */
|
||||
- ret = sx126x_set_tx(dev, 10000);
|
||||
+ /* Chip-side Tx safeguard, scaled from airtime.
|
||||
+ *
|
||||
+ * DS §13.1.5: when the Tx timer fires the transmission is stopped, so
|
||||
+ * this has to exceed real airtime — and a fixed 10 s cannot. A 255-byte
|
||||
+ * packet at CR 4/8 is 17.7 s at SF10/BW31.25 and 28.6 s at SF12/BW62.5;
|
||||
+ * on those presets the old constant truncated every packet from 136 B
|
||||
+ * and 76 B up respectively, mid-transmission and with nothing in the log
|
||||
+ * tying the loss to this line.
|
||||
+ *
|
||||
+ * Floored at the previous 10000 so no preset that works today gets a
|
||||
+ * tighter deadline, and clamped to the 24-bit field: sx126x_set_tx()
|
||||
+ * converts with SX126X_MS_TO_TIMEOUT (64 steps/ms at 15.625 us) and then
|
||||
+ * sys_put_be24(), which would silently truncate above 262143 ms. That
|
||||
+ * ceiling is 262 s against a worst reachable airtime of ~229 s
|
||||
+ * (SF12/BW7.81, 255 B), so the clamp never binds on a real packet. */
|
||||
+ {
|
||||
+ uint32_t air_ms = sx126x_lora_airtime(dev, data_len);
|
||||
+ uint32_t tmo_ms = air_ms + (air_ms / 4U) + 500U;
|
||||
+
|
||||
+ if (tmo_ms < 10000U) {
|
||||
+ tmo_ms = 10000U;
|
||||
+ }
|
||||
+ if (tmo_ms > 262143U) {
|
||||
+ tmo_ms = 262143U;
|
||||
+ }
|
||||
+ LOG_DBG("SET_TX: airtime=%u ms, timeout=%u ms", air_ms, tmo_ms);
|
||||
+ ret = sx126x_set_tx(dev, tmo_ms);
|
||||
+ }
|
||||
if (ret < 0) {
|
||||
goto out_error;
|
||||
}
|
||||
@@ -762,9 +762,23 @@ public:
|
||||
|
||||
void clearStats() override {
|
||||
lora_radio.resetStats();
|
||||
lora_radio.resetDutyCycleTimeoutRestarts();
|
||||
companion_mesh.resetStats();
|
||||
}
|
||||
|
||||
/* Duty-cycle false-preamble re-arm count. Without these the
|
||||
* CommonCLICallbacks default answers 0 forever, so `get dc.restarts`
|
||||
* read clean on every companion regardless of what the radio was doing
|
||||
* — and the companion is the role the duty cycle actually runs in.
|
||||
* Mirrors RepeaterMesh::getDutyCycleTimeoutRestarts(). */
|
||||
uint32_t getDutyCycleTimeoutRestarts() const override {
|
||||
return lora_radio.getDutyCycleTimeoutRestarts();
|
||||
}
|
||||
|
||||
void resetDutyCycleTimeoutRestarts() override {
|
||||
lora_radio.resetDutyCycleTimeoutRestarts();
|
||||
}
|
||||
|
||||
/* Temp radio params — deferred; stub for now. */
|
||||
void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr,
|
||||
int timeout_mins) override {
|
||||
|
||||
Reference in New Issue
Block a user