mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-09-01 23:38:18 +00:00
LR1110 duty cycle finally and zephyr PM vs UART wedge fixes
This commit is contained in:
@@ -581,6 +581,26 @@ config ZEPHCORE_LORA_DC_MIN_SYMBOLS
|
||||
for extra detection margin on very noisy sites. SF5/6 adds +4
|
||||
internally. Lowering below 8 risks missed packets at low SNR.
|
||||
|
||||
config ZEPHCORE_LORA_DC_MARGIN_PCT
|
||||
int "RX duty cycle: sleep-budget safety margin (percent)"
|
||||
range 0 40
|
||||
default 15
|
||||
help
|
||||
Percentage the per-cycle deaf-time budget is derated before the
|
||||
sleep period is programmed. The theoretical catch budget assumes
|
||||
the sleep clock and wake transition are exact, but the chip's sleep
|
||||
timer runs on an internal RC oscillator (RC64k on SX126x, RTC on
|
||||
LR11xx) that drifts several percent over temperature, and the wake-
|
||||
transition estimate is a datasheet figure the datasheet itself calls
|
||||
"not accurate and may vary". Either overshoot silently pushes real
|
||||
deaf time past the budget and drops the fraction of packets whose
|
||||
preamble phase aligns with the window edge — signal-strength-
|
||||
independent loss that presents as random DC packet drops. This
|
||||
margin trades a little radio-off time for that robustness and is
|
||||
shared by every radio (SX126x / LR11xx / LR20xx). Set to 0 to
|
||||
restore the old zero-margin behaviour; 15% is a conservative default,
|
||||
lower it once you have measured your board's clock+transition on air.
|
||||
|
||||
endmenu
|
||||
|
||||
config ZEPHCORE_RTC_AUTODISCOVER
|
||||
|
||||
@@ -935,6 +935,21 @@ static void gps_uart_set_power(bool on)
|
||||
if (!device_is_ready(gps_uart_dev)) {
|
||||
return;
|
||||
}
|
||||
if (!on) {
|
||||
/* Let the GNSS line go quiet before suspending. Every caller
|
||||
* cuts module power (GPS_EN low / reset asserted / regulator
|
||||
* off) immediately before this, but a byte can still be in
|
||||
* flight. Settle so it finishes and the driver's RX ISR re-arms,
|
||||
* leaving the receiver armed-and-idle when the suspend's STOPRX
|
||||
* fires — that state yields RXTO, whereas a just-stopped,
|
||||
* un-rearmed receiver can produce none and (pre-0010) hung the
|
||||
* main thread. ~5 ms comfortably covers one character time at
|
||||
* GNSS baud plus ISR latency; standby happens at most every few
|
||||
* minutes, so the cost is negligible. Backstop: patch 0010
|
||||
* bounds the driver's RXTO wait so a missed RXTO can never hang
|
||||
* us even if a byte still lands in the race window. */
|
||||
k_msleep(5);
|
||||
}
|
||||
int ret = pm_device_action_run(gps_uart_dev,
|
||||
on ? PM_DEVICE_ACTION_RESUME
|
||||
: PM_DEVICE_ACTION_SUSPEND);
|
||||
|
||||
@@ -534,8 +534,23 @@ void LoRaRadioBase::startReceive()
|
||||
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 =
|
||||
|
||||
/* Theoretical per-cycle deaf budget, then derate by
|
||||
* CONFIG_..._MARGIN_PCT. The budget assumes the sleep
|
||||
* clock and wake transition are exact; in reality the
|
||||
* chip sleep timer runs on an RC oscillator that drifts
|
||||
* several % over temperature and the wake transition is a
|
||||
* "may vary" datasheet figure. Either overshoot pushes
|
||||
* real deaf time past the budget and drops phase-edge
|
||||
* packets (strength-independent DC loss). Deraging the
|
||||
* whole budget is slightly stricter than deraging sleep
|
||||
* alone, which is the safe direction. */
|
||||
const uint32_t deaf_budget_us =
|
||||
(uint32_t)(P - 2 * D - 1) * sym_us;
|
||||
const uint32_t deaf_us = deaf_budget_us -
|
||||
(uint32_t)(((uint64_t)deaf_budget_us *
|
||||
CONFIG_ZEPHCORE_LORA_DC_MARGIN_PCT) /
|
||||
100U);
|
||||
|
||||
if (deaf_us > trans_us + 2000) {
|
||||
const uint32_t sleep_us = deaf_us - trans_us;
|
||||
|
||||
@@ -32,6 +32,16 @@ LOG_MODULE_REGISTER(lr11xx_lora, CONFIG_LORA_LOG_LEVEL);
|
||||
#define LR11XX_DIO1_WQ_STACK_SIZE 2560
|
||||
K_THREAD_STACK_DEFINE(lr11xx_dio1_wq_stack, LR11XX_DIO1_WQ_STACK_SIZE);
|
||||
|
||||
/* Wedge-recovery watchdog — its own low-priority queue so the confirm poll
|
||||
* (a BUSY-high dwell of up to LR11XX_WEDGE_CONFIRM_MS) never blocks DIO1 RX
|
||||
* processing. */
|
||||
#define LR11XX_WEDGE_WQ_STACK_SIZE 1024
|
||||
K_THREAD_STACK_DEFINE(lr11xx_wedge_wq_stack, LR11XX_WEDGE_WQ_STACK_SIZE);
|
||||
|
||||
#define LR11XX_WEDGE_CHECK_MS 3000 /* base cadence between health checks */
|
||||
#define LR11XX_WEDGE_IDLE_MS 12000 /* only probe after this much DIO1 silence */
|
||||
#define LR11XX_WEDGE_CONFIRM_MS 250 /* continuous BUSY-high past this = wedged */
|
||||
|
||||
/* ── Driver data structures ─────────────────────────────────────────── */
|
||||
|
||||
struct lr11xx_config {
|
||||
@@ -120,6 +130,16 @@ struct lr11xx_data {
|
||||
* bit is cleared and TX released. All accesses under spi_mutex. */
|
||||
uint32_t preamble_seen_at_ms;
|
||||
|
||||
/* Wedge-recovery watchdog: the LR1110 can rarely be left BUSY-high with
|
||||
* DIO1 low (a command racing the autonomous SetRxDutyCycle sleep phase) —
|
||||
* no IRQ ever fires, so the event-driven driver never re-arms and the node
|
||||
* goes permanently deaf. last_dio1_ms records the last proof-of-life (a
|
||||
* handled DIO1 event); the watchdog re-arms RX via a hardware reset when
|
||||
* BUSY stays continuously high past any legitimate DC cycle with no DIO1. */
|
||||
uint32_t last_dio1_ms;
|
||||
struct k_work_delayable wedge_work;
|
||||
struct k_work_q wedge_wq;
|
||||
|
||||
/* RX data buffer — filled in DIO1 handler, passed to callback */
|
||||
uint8_t rx_buf[256];
|
||||
};
|
||||
@@ -303,12 +323,27 @@ static void lr11xx_apply_modem_config(struct lr11xx_data *data,
|
||||
0);
|
||||
}
|
||||
|
||||
/* ── RX duty cycle — disabled on LR1110 ─────────────────────────────── */
|
||||
/* ── RX duty cycle ──────────────────────────────────────────────────── */
|
||||
|
||||
/* LR1110 SetRxDutyCycle is fundamentally broken: both MODE_RX and
|
||||
* MODE_CAD fail to detect in-progress preambles, dropping 23-40% of
|
||||
* packets depending on the sleep fraction. The SX1262 handles this
|
||||
* correctly. LR1110 always uses continuous RX instead. */
|
||||
/* SetRxDutyCycle(MODE_RX) works the same way as the SX126x: on a positive
|
||||
* over-the-air (preamble) detection the chip auto-recomputes its RX timeout
|
||||
* to 2*rx_period + sleep_period and stays in RX for the whole packet (SWDR001
|
||||
* lr11xx_radio.h step 2). Because that extension is native, the LR1110 needs
|
||||
* NO StopTimerOnPreamble and NO parked-RX watchdog: a false detection lets the
|
||||
* bounded 2*rx+sleep timer expire and re-arm via the normal RX-timeout path,
|
||||
* so there is no infinite-park failure mode to recover from (unlike the
|
||||
* SX126x, whose StopTimerOnPreamble freezes the timer). Window sizing —
|
||||
* including the shared clock/transition safety margin — is owned by the
|
||||
* adapter (LoRaRadioBase::startReceive); the driver only re-arms with the
|
||||
* stored timing.
|
||||
*
|
||||
* The earlier "fundamentally broken, 23-40% loss" verdict was a window-sizing
|
||||
* bug in the adapter, not a chip defect. One item is still HW-unverified: the
|
||||
* chip's sleep-with-retention warm wakes should keep the boosted RX gain
|
||||
* (the SX126x needs an explicit retention-list write for this; the LR11xx SDK
|
||||
* exposes no such list, implying it is automatic — measure sensitivity across
|
||||
* cycles on a live board to confirm). We re-apply SetRxBoosted on every host
|
||||
* restart regardless, as cheap insurance. */
|
||||
|
||||
/* ── Start RX (internal) ────────────────────────────────────────────── */
|
||||
|
||||
@@ -421,6 +456,9 @@ static void lr11xx_dio1_work_handler(struct k_work *work)
|
||||
void *ctx = &data->hal_ctx;
|
||||
bool rx_restarted = false;
|
||||
|
||||
/* Proof of life for the wedge watchdog: the chip generated an IRQ. */
|
||||
data->last_dio1_ms = k_uptime_get_32();
|
||||
|
||||
k_mutex_lock(&data->spi_mutex, K_FOREVER);
|
||||
|
||||
/* Read IRQ status then clear ALL bits. Must use ALL_MASK because
|
||||
@@ -453,18 +491,21 @@ static void lr11xx_dio1_work_handler(struct k_work *work)
|
||||
}
|
||||
|
||||
/* ── RX done ──
|
||||
* Gated on no error bits: a CRC-failed packet asserts RX_DONE and
|
||||
* CRC_ERROR together on this chip family (same as SX126x — see the
|
||||
* sx126x patch's "they co-fire here"; LBM's radio_planner likewise
|
||||
* checks errors before RX_DONE), so an ungated done-first read
|
||||
* would deliver corrupted payloads as valid. A good packet
|
||||
* coalesced with an earlier HEADER_ERROR in the same handler
|
||||
* window is dropped too — the header error may have shifted the
|
||||
* RX buffer pointer (errata handling below), making the read
|
||||
* suspect. The error branch below owns the window instead. */
|
||||
* Deliver on RX_DONE unless it is a GENUINE reception failure:
|
||||
* - CRC_ERROR: a CRC-failed packet asserts RX_DONE+CRC_ERROR together
|
||||
* on this chip family, so an ungated read would deliver corruption.
|
||||
* - HEADER_ERROR with NO valid header (SYNC_WORD_HEADER_VALID clear):
|
||||
* no good header decoded, the buffer is suspect.
|
||||
* A HEADER_ERROR that coincides with a VALID header is a foreign/colliding
|
||||
* signal's error while OUR packet decoded fine — RadioLib's readData keeps
|
||||
* the packet in exactly this case (CRC_ERR || (HDR_ERR && !HDR_VALID)), and
|
||||
* the SX126x path never routes HEADER_ERROR at all. Dropping these good
|
||||
* packets was the LR1110 under-load packet-loss bug (verified 2026-07-24:
|
||||
* ~7-10% loss vs SX1262, load-dependent, size-skewed). */
|
||||
if ((irq & LR11XX_SYSTEM_IRQ_RX_DONE) &&
|
||||
!(irq & (LR11XX_SYSTEM_IRQ_CRC_ERROR |
|
||||
LR11XX_SYSTEM_IRQ_HEADER_ERROR))) {
|
||||
!(irq & LR11XX_SYSTEM_IRQ_CRC_ERROR) &&
|
||||
!((irq & LR11XX_SYSTEM_IRQ_HEADER_ERROR) &&
|
||||
!(irq & LR11XX_SYSTEM_IRQ_SYNC_WORD_HEADER_VALID))) {
|
||||
lr11xx_radio_rx_buffer_status_t rx_stat;
|
||||
lr11xx_radio_get_rx_buffer_status(ctx, &rx_stat);
|
||||
|
||||
@@ -477,6 +518,19 @@ static void lr11xx_dio1_work_handler(struct k_work *work)
|
||||
rx_stat.buffer_start_pointer,
|
||||
rx_stat.pld_len_in_bytes);
|
||||
|
||||
/* Buffer-shift errata, header-error variant: if a foreign
|
||||
* HEADER_ERROR coalesced with this good packet, a standby
|
||||
* must reset the +4 buffer_start_pointer drift so it cannot
|
||||
* corrupt the NEXT packet's read. The payload is already
|
||||
* captured above, so resetting now keeps the packet AND the
|
||||
* errata protection. (A plain RX_DONE with no header error
|
||||
* gets the same reset from the STDBY_RC rx/tx fallback; this
|
||||
* makes the coalesced case explicit, not fallback-reliant.) */
|
||||
if (irq & LR11XX_SYSTEM_IRQ_HEADER_ERROR) {
|
||||
lr11xx_system_set_standby(ctx,
|
||||
LR11XX_SYSTEM_STANDBY_CFG_RC);
|
||||
}
|
||||
|
||||
/* LR1110 errata: RX buffer base shifts 4 bytes per
|
||||
* received packet. Without clearing, after ~64
|
||||
* packets the offset wraps the 256-byte buffer and
|
||||
@@ -559,32 +613,38 @@ static void lr11xx_dio1_work_handler(struct k_work *work)
|
||||
}
|
||||
}
|
||||
|
||||
/* ── CRC / Header error ──
|
||||
* Plain `CRC || HDR` — no SYNC_WORD_HEADER_VALID gate (RadioLib
|
||||
* readData's false-alarm filter). IRQ status is bulk-cleared on
|
||||
* every handler entry, so a set HEADER_ERROR always belongs to
|
||||
* this window; a SYNC_VALID bit latched by another packet in the
|
||||
* same window must not suppress the errata standby below. */
|
||||
if (irq & (LR11XX_SYSTEM_IRQ_CRC_ERROR |
|
||||
LR11XX_SYSTEM_IRQ_HEADER_ERROR)) {
|
||||
/* ── CRC / header error ──
|
||||
* A HEADER_ERROR that coincides with a VALID header but no RX_DONE means a
|
||||
* foreign/colliding signal errored while OUR good packet is still
|
||||
* mid-reception. Do NOT abort it: the old reflex standby()+restart here
|
||||
* dropped the in-flight packet, and doing that on every foreign header
|
||||
* error under load is what cost the LR1110 packets (the SX126x never routes
|
||||
* HEADER_ERROR for exactly this reason). Leave the chip in RX; the good
|
||||
* packet's own RX_DONE delivers it above. */
|
||||
if ((irq & LR11XX_SYSTEM_IRQ_HEADER_ERROR) &&
|
||||
(irq & LR11XX_SYSTEM_IRQ_SYNC_WORD_HEADER_VALID) &&
|
||||
!(irq & LR11XX_SYSTEM_IRQ_RX_DONE)) {
|
||||
LOG_DBG("RX: foreign HDR err during valid header — not aborting");
|
||||
rx_restarted = true; /* reception continues; skip safety-net restart */
|
||||
} else if (irq & (LR11XX_SYSTEM_IRQ_CRC_ERROR |
|
||||
LR11XX_SYSTEM_IRQ_HEADER_ERROR)) {
|
||||
LOG_DBG("RX error: CRC=%d HDR=%d RXDONE=%d",
|
||||
(irq & LR11XX_SYSTEM_IRQ_CRC_ERROR) ? 1 : 0,
|
||||
(irq & LR11XX_SYSTEM_IRQ_HEADER_ERROR) ? 1 : 0,
|
||||
(irq & LR11XX_SYSTEM_IRQ_RX_DONE) ? 1 : 0);
|
||||
|
||||
/* LR1110 errata: a header error makes the chip's reported
|
||||
* buffer_start_pointer stale by +4 for every subsequent
|
||||
* packet (stacking) until a standby resets the shift.
|
||||
* Arduino MeshCore's CustomLR1110 calls standby() on every
|
||||
* header error unconditionally — mirror that. */
|
||||
/* LR1110 errata: a real header error (no valid header) drifts the
|
||||
* reported buffer_start_pointer +4 per subsequent packet until a
|
||||
* standby resets it (Arduino MeshCore's CustomLR1110 standbys on
|
||||
* header error). Only for a genuine header error — a valid header
|
||||
* is handled above and must never trigger this abort. */
|
||||
if (irq & LR11XX_SYSTEM_IRQ_HEADER_ERROR) {
|
||||
lr11xx_system_set_standby(ctx,
|
||||
LR11XX_SYSTEM_STANDBY_CFG_RC);
|
||||
}
|
||||
|
||||
/* Drop whatever the failed (or coalesced) packet left in
|
||||
* the RX buffer — RadioLib clears it on the CRC-error read
|
||||
* path too. */
|
||||
/* Drop whatever the failed packet left in the RX buffer — RadioLib
|
||||
* clears it on the CRC-error read path too. */
|
||||
lr11xx_regmem_clear_rxbuffer(ctx);
|
||||
|
||||
if (!data->tx_active) {
|
||||
@@ -958,8 +1018,28 @@ int16_t lr11xx_get_rssi_inst(const struct device *dev)
|
||||
struct lr11xx_data *data = dev->data;
|
||||
int8_t rssi;
|
||||
|
||||
k_mutex_lock(&data->spi_mutex, K_FOREVER);
|
||||
lr11xx_radio_get_rssi_inst(&data->hal_ctx, &rssi);
|
||||
/* GPIO BUSY read FIRST — bail before the HAL's wait_on_busy stalls (3 s)
|
||||
* on the autonomous duty-cycle sleep phase. Issuing GetRssiInst (0x0205)
|
||||
* into a chip parked in SetRxDutyCycle sleep-with-retention wedges it
|
||||
* BUSY-high permanently (root cause of the 2026-07-24 "T1000 goes deaf"
|
||||
* wedge). -128 is the busy/contended sentinel the noise-floor sampler
|
||||
* expects (LoRaRadioBase::triggerNoiseFloorCalibrate retries next tick).
|
||||
* Direct parity with sx126x_get_rssi_inst. */
|
||||
if (gpio_pin_get_dt(&data->hal_ctx.busy)) {
|
||||
return -128;
|
||||
}
|
||||
if (k_mutex_lock(&data->spi_mutex, K_NO_WAIT) != 0) {
|
||||
return -128;
|
||||
}
|
||||
if (gpio_pin_get_dt(&data->hal_ctx.busy)) {
|
||||
k_mutex_unlock(&data->spi_mutex);
|
||||
return -128;
|
||||
}
|
||||
|
||||
if (lr11xx_radio_get_rssi_inst(&data->hal_ctx, &rssi) != LR11XX_STATUS_OK) {
|
||||
k_mutex_unlock(&data->spi_mutex);
|
||||
return -128;
|
||||
}
|
||||
k_mutex_unlock(&data->spi_mutex);
|
||||
|
||||
return (int16_t)rssi;
|
||||
@@ -987,6 +1067,16 @@ bool lr11xx_is_receiving(const struct device *dev)
|
||||
{
|
||||
struct lr11xx_data *data = dev->data;
|
||||
|
||||
/* GPIO BUSY read FIRST: during the autonomous duty-cycle sleep phase the
|
||||
* chip is asleep (BUSY high) and by definition not mid-packet, so report
|
||||
* "not receiving" WITHOUT issuing get_irq_status — which, like GetRssiInst,
|
||||
* can wedge the chip BUSY-high if issued into DC sleep. Same guard as
|
||||
* lr11xx_get_rssi_inst; this path runs on every TX gate so it is the other
|
||||
* async command that could hit the sleep window. */
|
||||
if (gpio_pin_get_dt(&data->hal_ctx.busy)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Non-blocking — skip if SPI busy */
|
||||
if (k_mutex_lock(&data->spi_mutex, K_NO_WAIT) != 0) {
|
||||
return false;
|
||||
@@ -1416,6 +1506,66 @@ static int lr11xx_hw_init(struct lr11xx_data *data,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ── Wedge-recovery watchdog ────────────────────────────────────────── */
|
||||
|
||||
/* Detects the "BUSY stuck high, DIO1 silent" wedge and re-arms RX with a
|
||||
* hardware reset. False-positive free by construction: a healthy chip —
|
||||
* continuous RX or autonomous DC cycling — always drops BUSY low within one
|
||||
* duty-cycle period, so a *continuous* BUSY-high dwell longer than any
|
||||
* legitimate cycle, with no DIO1 event for >12 s, can only be a genuine wedge.
|
||||
* Passive: reads the BUSY GPIO only (no SPI), so it cannot itself disturb the
|
||||
* chip or race the autonomous DC state machine. Runs on its own queue at the
|
||||
* DIO1 priority; the confirm poll yields every 2 ms so a real packet arriving
|
||||
* mid-confirm is still processed promptly on the DIO1 queue. */
|
||||
static void lr11xx_wedge_watchdog_handler(struct k_work *work)
|
||||
{
|
||||
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
|
||||
struct lr11xx_data *data = CONTAINER_OF(dwork, struct lr11xx_data,
|
||||
wedge_work);
|
||||
const struct lr11xx_config *cfg = data->dev->config;
|
||||
|
||||
/* Only monitor steady RX. */
|
||||
if (!data->in_rx_mode || data->tx_active || !data->configured) {
|
||||
goto rearm;
|
||||
}
|
||||
|
||||
/* Recent DIO1 activity = chip provably alive. */
|
||||
if ((k_uptime_get_32() - data->last_dio1_ms) < LR11XX_WEDGE_IDLE_MS) {
|
||||
goto rearm;
|
||||
}
|
||||
|
||||
/* Idle: either quiet RF (chip still DC-cycling) or a wedge. Poll BUSY
|
||||
* continuously for one confirm window — a healthy chip shows a low edge
|
||||
* within a cycle; a wedge stays high the whole window. */
|
||||
{
|
||||
uint32_t start = k_uptime_get_32();
|
||||
bool saw_low = false;
|
||||
|
||||
while ((k_uptime_get_32() - start) < LR11XX_WEDGE_CONFIRM_MS) {
|
||||
if (!gpio_pin_get_dt(&data->hal_ctx.busy)) {
|
||||
saw_low = true;
|
||||
break;
|
||||
}
|
||||
k_msleep(2);
|
||||
}
|
||||
if (saw_low) {
|
||||
goto rearm; /* alive */
|
||||
}
|
||||
}
|
||||
|
||||
LOG_ERR("LR11xx wedge (BUSY stuck, DIO1 silent >%ums) — hardware reset",
|
||||
LR11XX_WEDGE_IDLE_MS);
|
||||
k_mutex_lock(&data->spi_mutex, K_FOREVER);
|
||||
lr11xx_hardware_reset(data, cfg);
|
||||
lr11xx_start_rx(data, cfg);
|
||||
k_mutex_unlock(&data->spi_mutex);
|
||||
data->last_dio1_ms = k_uptime_get_32();
|
||||
|
||||
rearm:
|
||||
k_work_schedule_for_queue(&data->wedge_wq, &data->wedge_work,
|
||||
K_MSEC(LR11XX_WEDGE_CHECK_MS));
|
||||
}
|
||||
|
||||
/* ── Driver init (lightweight — runs at POST_KERNEL) ────────────────── */
|
||||
|
||||
static int lr11xx_lora_init(const struct device *dev)
|
||||
@@ -1437,6 +1587,17 @@ static int lr11xx_lora_init(const struct device *dev)
|
||||
K_PRIO_COOP(7), NULL);
|
||||
k_thread_name_set(&data->dio1_wq.thread, "lr11xx_dio1");
|
||||
|
||||
/* Wedge-recovery watchdog on its own queue (see handler). Same priority
|
||||
* as DIO1 so it never preempts RX; its confirm poll yields every 2 ms. */
|
||||
k_work_init_delayable(&data->wedge_work, lr11xx_wedge_watchdog_handler);
|
||||
k_work_queue_start(&data->wedge_wq, lr11xx_wedge_wq_stack,
|
||||
K_THREAD_STACK_SIZEOF(lr11xx_wedge_wq_stack),
|
||||
K_PRIO_COOP(7), NULL);
|
||||
k_thread_name_set(&data->wedge_wq.thread, "lr11xx_wedge");
|
||||
data->last_dio1_ms = k_uptime_get_32();
|
||||
k_work_schedule_for_queue(&data->wedge_wq, &data->wedge_work,
|
||||
K_MSEC(LR11XX_WEDGE_CHECK_MS));
|
||||
|
||||
/* Check SPI bus */
|
||||
if (!spi_is_ready_dt(&cfg->bus)) {
|
||||
LOG_ERR("SPI bus not ready");
|
||||
|
||||
@@ -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..5315e5d80ef 100644
|
||||
index 30243ba5dc7..f0794661515 100644
|
||||
--- a/drivers/lora/native/sx126x/sx126x.c
|
||||
+++ b/drivers/lora/native/sx126x/sx126x.c
|
||||
@@ -6,14 +6,21 @@
|
||||
@@ -282,17 +282,31 @@ index 30243ba5dc7..5315e5d80ef 100644
|
||||
sx126x_hal_set_rf_switch(dev, enable, tx);
|
||||
}
|
||||
}
|
||||
@@ -455,6 +573,215 @@ static int sx126x_reconnect_rf_gpios(const struct device *dev)
|
||||
@@ -455,6 +573,229 @@ 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
|
||||
+/* Watchdog sampling period, scaled to the active preset. The two-strike
|
||||
+ * design needs the period to exceed the longest legitimate preamble-detect ->
|
||||
+ * header gap so two consecutive parked sightings can never both fall inside
|
||||
+ * one real packet's gap (which would abort a live reception). A fixed value
|
||||
+ * has to be sized for the slowest preset (SF12/BW62.5, ~1.9 s) -- ~15x larger
|
||||
+ * than needed at SF8/BW62.5, so every false-preamble park then pins the radio
|
||||
+ * in full RX for up to two periods, wasting the duty-cycle power savings.
|
||||
+ * Scale it instead: 2x the (preamble+8)-symbol grace covers the gap with
|
||||
+ * margin at every preset (SF8/BW62.5 ~0.3 s, SF12/BW62.5 ~3.1 s), floored so
|
||||
+ * fast presets don't poll the SPI bus raw. Never costs packets -- a parked
|
||||
+ * radio is still receiving; the period only bounds how long it stays parked. */
|
||||
+#define SX126X_DC_WATCHDOG_MIN_MS 250
|
||||
+
|
||||
+static uint32_t sx126x_dc_watchdog_ms(struct sx126x_data *data)
|
||||
+{
|
||||
+ uint32_t ms = 2U * sx126x_preamble_grace_ms(data);
|
||||
+
|
||||
+ return (ms < SX126X_DC_WATCHDOG_MIN_MS) ? SX126X_DC_WATCHDOG_MIN_MS : ms;
|
||||
+}
|
||||
+
|
||||
+/* 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
|
||||
@@ -317,7 +331,7 @@ index 30243ba5dc7..5315e5d80ef 100644
|
||||
+{
|
||||
+ data->dc_watchdog_strikes = 0;
|
||||
+ k_work_reschedule_for_queue(&data->dio1_wq, &data->dc_watchdog_work,
|
||||
+ K_MSEC(SX126X_DC_WATCHDOG_MS));
|
||||
+ K_MSEC(sx126x_dc_watchdog_ms(data)));
|
||||
+}
|
||||
+
|
||||
+/* -- Lightweight RX restart -- */
|
||||
@@ -492,13 +506,13 @@ index 30243ba5dc7..5315e5d80ef 100644
|
||||
+
|
||||
+rearm:
|
||||
+ k_work_schedule_for_queue(&data->dio1_wq, &data->dc_watchdog_work,
|
||||
+ K_MSEC(SX126X_DC_WATCHDOG_MS));
|
||||
+ K_MSEC(sx126x_dc_watchdog_ms(data)));
|
||||
+}
|
||||
+
|
||||
static int sx126x_set_sleep(const struct device *dev)
|
||||
{
|
||||
struct sx126x_data *data = dev->data;
|
||||
@@ -533,8 +860,16 @@ static void sx126x_handle_irq_rx_done(const struct device *dev, uint16_t irq_sta
|
||||
@@ -533,8 +874,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;
|
||||
@@ -515,7 +529,7 @@ index 30243ba5dc7..5315e5d80ef 100644
|
||||
/* Get received packet info */
|
||||
ret = sx126x_get_rx_buffer_status(dev, &payload_len, &offset);
|
||||
if (ret < 0) {
|
||||
@@ -544,9 +879,26 @@ static void sx126x_handle_irq_rx_done(const struct device *dev, uint16_t irq_sta
|
||||
@@ -544,9 +893,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);
|
||||
|
||||
@@ -545,7 +559,7 @@ index 30243ba5dc7..5315e5d80ef 100644
|
||||
result.status = -EIO;
|
||||
} else {
|
||||
/* Read payload into shared buffer */
|
||||
@@ -564,20 +916,34 @@ static void sx126x_handle_irq_rx_done(const struct device *dev, uint16_t irq_sta
|
||||
@@ -564,20 +930,34 @@ static void sx126x_handle_irq_rx_done(const struct device *dev, uint16_t irq_sta
|
||||
}
|
||||
}
|
||||
|
||||
@@ -592,7 +606,7 @@ index 30243ba5dc7..5315e5d80ef 100644
|
||||
}
|
||||
} else {
|
||||
/* Sync mode */
|
||||
@@ -589,8 +955,69 @@ static void sx126x_handle_irq_rx_done(const struct device *dev, uint16_t irq_sta
|
||||
@@ -589,8 +969,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;
|
||||
@@ -662,7 +676,7 @@ index 30243ba5dc7..5315e5d80ef 100644
|
||||
sx126x_set_sleep(dev);
|
||||
|
||||
if (data->tx_async_signal != NULL) {
|
||||
@@ -633,10 +1060,46 @@ static void sx126x_irq_work_handler(struct k_work *work)
|
||||
@@ -633,10 +1074,46 @@ static void sx126x_irq_work_handler(struct k_work *work)
|
||||
sx126x_handle_irq_rx_done(dev, irq_status);
|
||||
}
|
||||
|
||||
@@ -709,7 +723,7 @@ index 30243ba5dc7..5315e5d80ef 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 +1145,7 @@ static int sx126x_lora_config(const struct device *dev,
|
||||
@@ -682,7 +1159,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,
|
||||
@@ -718,7 +732,7 @@ index 30243ba5dc7..5315e5d80ef 100644
|
||||
if (ret < 0) {
|
||||
goto out;
|
||||
}
|
||||
@@ -698,6 +1161,29 @@ static int sx126x_lora_config(const struct device *dev,
|
||||
@@ -698,6 +1175,29 @@ static int sx126x_lora_config(const struct device *dev,
|
||||
goto out;
|
||||
}
|
||||
|
||||
@@ -748,7 +762,7 @@ index 30243ba5dc7..5315e5d80ef 100644
|
||||
/* Set sync word */
|
||||
ret = sx126x_set_sync_word(dev, config->public_network);
|
||||
if (ret < 0) {
|
||||
@@ -721,6 +1207,9 @@ out:
|
||||
@@ -721,6 +1221,9 @@ out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -758,7 +772,7 @@ index 30243ba5dc7..5315e5d80ef 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 +1227,27 @@ static int sx126x_lora_send_async(const struct device *dev,
|
||||
@@ -738,11 +1241,27 @@ static int sx126x_lora_send_async(const struct device *dev,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
@@ -788,7 +802,7 @@ index 30243ba5dc7..5315e5d80ef 100644
|
||||
k_mutex_lock(&data->lock, K_FOREVER);
|
||||
|
||||
ret = sx126x_ensure_ready(dev);
|
||||
@@ -752,6 +1257,59 @@ static int sx126x_lora_send_async(const struct device *dev,
|
||||
@@ -752,6 +1271,59 @@ static int sx126x_lora_send_async(const struct device *dev,
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -848,7 +862,7 @@ index 30243ba5dc7..5315e5d80ef 100644
|
||||
data->tx_async_signal = async;
|
||||
k_msgq_purge(&data->tx_msgq);
|
||||
|
||||
@@ -777,6 +1335,31 @@ static int sx126x_lora_send_async(const struct device *dev,
|
||||
@@ -777,6 +1349,31 @@ static int sx126x_lora_send_async(const struct device *dev,
|
||||
/* Enable antenna and set TX path */
|
||||
sx126x_set_rf_path(dev, true, true);
|
||||
|
||||
@@ -880,7 +894,7 @@ index 30243ba5dc7..5315e5d80ef 100644
|
||||
/* Start transmission with 10 second timeout */
|
||||
ret = sx126x_set_tx(dev, 10000);
|
||||
if (ret < 0) {
|
||||
@@ -847,6 +1430,8 @@ static int sx126x_lora_recv(const struct device *dev, uint8_t *data_buf,
|
||||
@@ -847,6 +1444,8 @@ static int sx126x_lora_recv(const struct device *dev, uint8_t *data_buf,
|
||||
}
|
||||
|
||||
data->rx_cb = NULL;
|
||||
@@ -889,7 +903,7 @@ index 30243ba5dc7..5315e5d80ef 100644
|
||||
k_msgq_purge(&data->rx_msgq);
|
||||
|
||||
/* Set packet parameters for variable length reception */
|
||||
@@ -918,6 +1503,8 @@ static int sx126x_lora_recv_async(const struct device *dev,
|
||||
@@ -918,6 +1517,8 @@ static int sx126x_lora_recv_async(const struct device *dev,
|
||||
/* Stop async reception */
|
||||
data->rx_cb = NULL;
|
||||
data->rx_cb_user_data = NULL;
|
||||
@@ -898,7 +912,7 @@ index 30243ba5dc7..5315e5d80ef 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 +1519,19 @@ static int sx126x_lora_recv_async(const struct device *dev,
|
||||
@@ -932,6 +1533,19 @@ static int sx126x_lora_recv_async(const struct device *dev,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
@@ -918,7 +932,7 @@ index 30243ba5dc7..5315e5d80ef 100644
|
||||
if (!atomic_cas(&data->state, SX126X_REST_STATE, SX126X_STATE_RX)) {
|
||||
LOG_ERR("Busy");
|
||||
k_mutex_unlock(&data->lock);
|
||||
@@ -945,8 +1545,18 @@ static int sx126x_lora_recv_async(const struct device *dev,
|
||||
@@ -945,8 +1559,18 @@ static int sx126x_lora_recv_async(const struct device *dev,
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -937,7 +951,7 @@ index 30243ba5dc7..5315e5d80ef 100644
|
||||
|
||||
/* Set packet parameters */
|
||||
ret = sx126x_set_packet_params(dev,
|
||||
@@ -959,6 +1569,7 @@ static int sx126x_lora_recv_async(const struct device *dev,
|
||||
@@ -959,6 +1583,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;
|
||||
@@ -945,7 +959,7 @@ index 30243ba5dc7..5315e5d80ef 100644
|
||||
sx126x_set_sleep(dev);
|
||||
k_mutex_unlock(&data->lock);
|
||||
return ret;
|
||||
@@ -971,11 +1582,21 @@ static int sx126x_lora_recv_async(const struct device *dev,
|
||||
@@ -971,11 +1596,21 @@ static int sx126x_lora_recv_async(const struct device *dev,
|
||||
ret = sx126x_set_rx(dev, 0);
|
||||
if (ret < 0) {
|
||||
data->rx_cb = NULL;
|
||||
@@ -967,7 +981,7 @@ index 30243ba5dc7..5315e5d80ef 100644
|
||||
k_mutex_unlock(&data->lock);
|
||||
return 0;
|
||||
}
|
||||
@@ -1051,7 +1672,7 @@ static int sx126x_lora_test_cw(const struct device *dev, uint32_t frequency,
|
||||
@@ -1051,7 +1686,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,
|
||||
@@ -976,7 +990,7 @@ index 30243ba5dc7..5315e5d80ef 100644
|
||||
if (ret < 0) {
|
||||
sx126x_set_sleep(dev);
|
||||
k_mutex_unlock(&data->lock);
|
||||
@@ -1083,14 +1704,671 @@ static int sx126x_lora_test_cw(const struct device *dev, uint32_t frequency,
|
||||
@@ -1083,14 +1718,671 @@ static int sx126x_lora_test_cw(const struct device *dev, uint32_t frequency,
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1655,7 +1669,7 @@ index 30243ba5dc7..5315e5d80ef 100644
|
||||
};
|
||||
|
||||
#ifdef CONFIG_PM_DEVICE
|
||||
@@ -1112,6 +2390,7 @@ static int sx126x_pm_action(const struct device *dev,
|
||||
@@ -1112,6 +2404,7 @@ static int sx126x_pm_action(const struct device *dev,
|
||||
static int sx126x_init(const struct device *dev)
|
||||
{
|
||||
struct sx126x_data *data = dev->data;
|
||||
@@ -1663,7 +1677,7 @@ index 30243ba5dc7..5315e5d80ef 100644
|
||||
int ret;
|
||||
|
||||
/* Initialize data structures */
|
||||
@@ -1121,9 +2400,29 @@ static int sx126x_init(const struct device *dev)
|
||||
@@ -1121,9 +2414,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);
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
diff --git a/drivers/serial/uart_nrfx_uarte.c b/drivers/serial/uart_nrfx_uarte.c
|
||||
index 60e8c23cd17..e78ba2af1fe 100644
|
||||
--- a/drivers/serial/uart_nrfx_uarte.c
|
||||
+++ b/drivers/serial/uart_nrfx_uarte.c
|
||||
@@ -3011,11 +3011,26 @@ static int uarte_pm_suspend(const struct device *dev)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
+ bool rxto;
|
||||
+
|
||||
nrf_uarte_task_trigger(uarte, NRF_UARTE_TASK_STOPRX);
|
||||
- while (!nrf_uarte_event_check(uarte, NRF_UARTE_EVENT_RXTO)) {
|
||||
- /* Busy wait for event to register */
|
||||
- Z_SPIN_DELAY(2);
|
||||
- }
|
||||
+ /* ZephCore: bound the RXTO wait. Upstream spins here
|
||||
+ * forever. In interrupt-driven mode the RX runs on a
|
||||
+ * 1-byte buffer with no ENDRX_STARTRX short, so the
|
||||
+ * receiver stops after each byte until the ISR re-arms
|
||||
+ * it -- and the ENDRX interrupt is disabled just above.
|
||||
+ * If a byte finishes in that window the receiver is
|
||||
+ * already stopped and un-rearmed, STOPRX then produces
|
||||
+ * no RXTO, and this loop never exits, hanging the caller
|
||||
+ * (the main mesh thread on GPS standby -> whole node
|
||||
+ * wedges: no LoRa/USB/BLE service until reboot). 4 ms is
|
||||
+ * orders of magnitude past the normal sub-microsecond
|
||||
+ * RXTO latency; on timeout we fall through to the
|
||||
+ * unconditional nrf_uarte_disable() below, which
|
||||
+ * force-stops the receiver regardless. */
|
||||
+ NRFX_WAIT_FOR(nrf_uarte_event_check(uarte, NRF_UARTE_EVENT_RXTO),
|
||||
+ 2000, 2, rxto);
|
||||
+ (void)rxto;
|
||||
nrf_uarte_event_clear(uarte, NRF_UARTE_EVENT_RXSTARTED);
|
||||
nrf_uarte_event_clear(uarte, NRF_UARTE_EVENT_RXTO);
|
||||
nrf_uarte_event_clear(uarte, NRF_UARTE_EVENT_ENDRX);
|
||||
Reference in New Issue
Block a user