mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-10-23 09:42:34 +00:00
Three driver fixes for RX duty cycle, all applied to the existing patch
stolen from Zephyr main:
1. Issue StopTimerOnPreamble=1 before SetRxDutyCycle so the chip's
timer is not reset on every preamble detect (per §13.1 of the
datasheet). Without this, duty cycle effectively never sleeps in
noisy RF and current draw spikes.
2. On IRQ_RX_TX_TIMEOUT during duty-cycle RX, re-arm via
sx126x_restart_rx() instead of falling through to set_sleep().
The old path silently killed duty cycle after the first preamble
false-positive.
3. On recv_duty_cycle(NULL) cancel, wake the radio before issuing
SetStandby — BUSY stays asserted during the sleep phase and the
standby command was being dropped.
Also adds a dc_timeout_restarts atomic counter incremented on the Fix 2
path, exposed end-to-end: sx126x_ext.h accessors → LoRaRadioBase vtable
→ SX126xRadio override → CommonCLICallbacks → RepeaterMesh. Query via
`get dc.restarts` on the repeater CLI; cleared by `clear stats`. High
values indicate a noisy environment or a too-loose preamble threshold.
(+increase ESP BT stack because future zephyr pin advance will trip that mine)
This commit is contained in:
1 parent
f685f6c095
commit
a3244e2dc2
11 files changed
+380
-232
No files matched your search
@@ -1,95 +1,116 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* SX126x native driver — extension API
|
||||
*
|
||||
* Functions extending the standard Zephyr lora_driver_api with
|
||||
* SX126x-specific features (duty cycle, RX boost, RSSI readout,
|
||||
* preamble detection).
|
||||
*/
|
||||
|
||||
#ifndef SX126X_EXT_H
|
||||
#define SX126X_EXT_H
|
||||
|
||||
#include <zephyr/device.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Get instantaneous RSSI (for noise floor calibration)
|
||||
*
|
||||
* Reads the current RSSI from the radio while in RX mode.
|
||||
* Uses non-blocking mutex — returns -128 if SPI is busy.
|
||||
*
|
||||
* @param dev LoRa device
|
||||
* @return RSSI in dBm, or -128 on error
|
||||
*/
|
||||
int16_t sx126x_get_rssi_inst(const struct device *dev);
|
||||
|
||||
/**
|
||||
* @brief Check if radio is actively receiving a packet
|
||||
*
|
||||
* Checks IRQ status for preamble/header detection.
|
||||
* Uses non-blocking mutex — returns false if SPI is busy.
|
||||
*
|
||||
* @param dev LoRa device
|
||||
* @return true if preamble or header detected
|
||||
*/
|
||||
bool sx126x_is_receiving(const struct device *dev);
|
||||
|
||||
/**
|
||||
* @brief Enable/disable RX boosted mode
|
||||
*
|
||||
* Boosted mode increases LNA gain for +3dB sensitivity at +2mA cost.
|
||||
*
|
||||
* @param dev LoRa device
|
||||
* @param enable true to enable boost
|
||||
*/
|
||||
void sx126x_set_rx_boost(const struct device *dev, bool enable);
|
||||
|
||||
/**
|
||||
* @brief Check if the radio chip is busy (cannot accept SPI commands)
|
||||
*
|
||||
* Reads the BUSY GPIO pin directly — no SPI, no blocking.
|
||||
* Returns true when the chip is in its duty-cycle sleep phase.
|
||||
* Safe to call at any time.
|
||||
*
|
||||
* @param dev LoRa device
|
||||
* @return true if BUSY pin is high (chip sleeping / processing)
|
||||
*/
|
||||
bool sx126x_is_chip_busy(const struct device *dev);
|
||||
|
||||
/**
|
||||
* @brief Apply undocumented register 0x8B5 RX improvement for Heltec V4
|
||||
*
|
||||
* Sets the LSB of register 0x8B5 which consistently improves RX reception
|
||||
* on boards with GC1109 or KCT8103L PA (Heltec V4/V4.3). Described by
|
||||
* Heltec engineer @Quency-D in MeshCore PR#1398.
|
||||
*
|
||||
* Must be called after the first lora_config() completes.
|
||||
*
|
||||
* @param dev LoRa device
|
||||
*/
|
||||
void sx126x_apply_heltec_reg_patch(const struct device *dev);
|
||||
|
||||
/**
|
||||
* @brief Reset AGC by performing warm sleep + full recalibration
|
||||
*
|
||||
* Warm sleep powers down the analog frontend (resets AGC gain state),
|
||||
* then Calibrate(0x7F) refreshes all blocks (ADC, PLL, image, oscillators).
|
||||
* Re-applies DIO2 RF switch, RX boosted gain, and image calibration afterward.
|
||||
*
|
||||
* Must be called while NOT actively receiving a packet.
|
||||
*
|
||||
* @param dev LoRa device
|
||||
*/
|
||||
void sx126x_reset_agc(const struct device *dev);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SX126X_EXT_H */
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* SX126x native driver — extension API
|
||||
*
|
||||
* Functions extending the standard Zephyr lora_driver_api with
|
||||
* SX126x-specific features (duty cycle, RX boost, RSSI readout,
|
||||
* preamble detection).
|
||||
*/
|
||||
|
||||
#ifndef SX126X_EXT_H
|
||||
#define SX126X_EXT_H
|
||||
|
||||
#include <zephyr/device.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Get instantaneous RSSI (for noise floor calibration)
|
||||
*
|
||||
* Reads the current RSSI from the radio while in RX mode.
|
||||
* Uses non-blocking mutex — returns -128 if SPI is busy.
|
||||
*
|
||||
* @param dev LoRa device
|
||||
* @return RSSI in dBm, or -128 on error
|
||||
*/
|
||||
int16_t sx126x_get_rssi_inst(const struct device *dev);
|
||||
|
||||
/**
|
||||
* @brief Check if radio is actively receiving a packet
|
||||
*
|
||||
* Checks IRQ status for preamble/header detection.
|
||||
* Uses non-blocking mutex — returns false if SPI is busy.
|
||||
*
|
||||
* @param dev LoRa device
|
||||
* @return true if preamble or header detected
|
||||
*/
|
||||
bool sx126x_is_receiving(const struct device *dev);
|
||||
|
||||
/**
|
||||
* @brief Enable/disable RX boosted mode
|
||||
*
|
||||
* Boosted mode increases LNA gain for +3dB sensitivity at +2mA cost.
|
||||
*
|
||||
* @param dev LoRa device
|
||||
* @param enable true to enable boost
|
||||
*/
|
||||
void sx126x_set_rx_boost(const struct device *dev, bool enable);
|
||||
|
||||
/**
|
||||
* @brief Check if the radio chip is busy (cannot accept SPI commands)
|
||||
*
|
||||
* Reads the BUSY GPIO pin directly — no SPI, no blocking.
|
||||
* Returns true when the chip is in its duty-cycle sleep phase.
|
||||
* Safe to call at any time.
|
||||
*
|
||||
* @param dev LoRa device
|
||||
* @return true if BUSY pin is high (chip sleeping / processing)
|
||||
*/
|
||||
bool sx126x_is_chip_busy(const struct device *dev);
|
||||
|
||||
/**
|
||||
* @brief Apply undocumented register 0x8B5 RX improvement for Heltec V4
|
||||
*
|
||||
* Sets the LSB of register 0x8B5 which consistently improves RX reception
|
||||
* on boards with GC1109 or KCT8103L PA (Heltec V4/V4.3). Described by
|
||||
* Heltec engineer @Quency-D in MeshCore PR#1398.
|
||||
*
|
||||
* Must be called after the first lora_config() completes.
|
||||
*
|
||||
* @param dev LoRa device
|
||||
*/
|
||||
void sx126x_apply_heltec_reg_patch(const struct device *dev);
|
||||
|
||||
/**
|
||||
* @brief Reset AGC by performing warm sleep + full recalibration
|
||||
*
|
||||
* Warm sleep powers down the analog frontend (resets AGC gain state),
|
||||
* then Calibrate(0x7F) refreshes all blocks (ADC, PLL, image, oscillators).
|
||||
* Re-applies DIO2 RF switch, RX boosted gain, and image calibration afterward.
|
||||
*
|
||||
* Must be called while NOT actively receiving a packet.
|
||||
*
|
||||
* @param dev LoRa device
|
||||
*/
|
||||
void sx126x_reset_agc(const struct device *dev);
|
||||
|
||||
/**
|
||||
* @brief Get duty-cycle preamble false-positive counter
|
||||
*
|
||||
* Returns the number of times duty-cycle RX tripped IRQ_RX_TX_TIMEOUT
|
||||
* and was silently re-armed. High values mean the preamble detector is
|
||||
* firing on noise/neighbour interference without a real packet arriving,
|
||||
* which inflates RX-on time beyond the nominal duty cycle and shortens
|
||||
* battery life.
|
||||
*
|
||||
* @param dev LoRa device
|
||||
* @return Cumulative re-arm count since last reset
|
||||
*/
|
||||
uint32_t sx126x_get_dc_timeout_restarts(const struct device *dev);
|
||||
|
||||
/**
|
||||
* @brief Reset the duty-cycle preamble false-positive counter to zero.
|
||||
*
|
||||
* @param dev LoRa device
|
||||
*/
|
||||
void sx126x_reset_dc_timeout_restarts(const struct device *dev);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SX126X_EXT_H */
|
||||
@@ -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..ddc4ce19660 100644
|
||||
index 30243ba5dc7..4f2254c1f79 100644
|
||||
--- a/drivers/lora/native/sx126x/sx126x.c
|
||||
+++ b/drivers/lora/native/sx126x/sx126x.c
|
||||
@@ -10,10 +10,19 @@
|
||||
@@ -262,7 +262,7 @@ index 30243ba5dc7..ddc4ce19660 100644
|
||||
sx126x_hal_set_rf_switch(dev, enable, tx);
|
||||
}
|
||||
}
|
||||
@@ -455,6 +523,58 @@ static int sx126x_reconnect_rf_gpios(const struct device *dev)
|
||||
@@ -455,6 +523,67 @@ static int sx126x_reconnect_rf_gpios(const struct device *dev)
|
||||
}
|
||||
#endif /* CONFIG_PM_DEVICE */
|
||||
|
||||
@@ -294,6 +294,15 @@ index 30243ba5dc7..ddc4ce19660 100644
|
||||
+ sx126x_set_dio2_as_rf_switch(dev, true);
|
||||
+ }
|
||||
+
|
||||
+ /* StopTimerOnPreamble: without it, the duty-cycle RX timer
|
||||
+ * keeps running even after a preamble is detected — a
|
||||
+ * preamble arriving at the tail of the RX window can get
|
||||
+ * cut off when the sleep phase begins. Must be re-issued
|
||||
+ * every time because Calibrate(ALL) above resets it. */
|
||||
+ uint8_t stop_on_preamble = 1;
|
||||
+ sx126x_hal_write_cmd(dev, SX126X_CMD_STOP_TIMER_ON_PREAMBLE,
|
||||
+ &stop_on_preamble, 1);
|
||||
+
|
||||
+ /* Re-apply duty cycle with stored timing */
|
||||
+ uint8_t dc_buf[6];
|
||||
+ sys_put_be24(data->dc_rx_time, &dc_buf[0]);
|
||||
@@ -321,7 +330,7 @@ index 30243ba5dc7..ddc4ce19660 100644
|
||||
static int sx126x_set_sleep(const struct device *dev)
|
||||
{
|
||||
struct sx126x_data *data = dev->data;
|
||||
@@ -566,19 +686,23 @@ static void sx126x_handle_irq_rx_done(const struct device *dev, uint16_t irq_sta
|
||||
@@ -566,19 +695,23 @@ static void sx126x_handle_irq_rx_done(const struct device *dev, uint16_t irq_sta
|
||||
|
||||
/* Handle async callback or signal sync receiver */
|
||||
if (data->rx_cb != NULL) {
|
||||
@@ -354,7 +363,28 @@ index 30243ba5dc7..ddc4ce19660 100644
|
||||
} else {
|
||||
/* Sync mode */
|
||||
sx126x_set_sleep(dev);
|
||||
@@ -637,6 +761,25 @@ static void sx126x_irq_work_handler(struct k_work *work)
|
||||
@@ -591,6 +724,20 @@ static void sx126x_handle_irq_timeout(const struct device *dev)
|
||||
struct sx126x_data *data = dev->data;
|
||||
|
||||
LOG_DBG("Timeout");
|
||||
+
|
||||
+ /* Duty-cycle RX: a preamble false-positive (noise, partial preamble
|
||||
+ * from another modulation) can fire RxTimeout — but the chip has left
|
||||
+ * duty cycle and is parked in STDBY_RC. Without this re-arm the
|
||||
+ * driver silently falls out of duty cycle after the first noisy
|
||||
+ * window and goes to sleep. Re-entering via sx126x_restart_rx()
|
||||
+ * preserves the stored rx/sleep timing and re-applies AGC reset +
|
||||
+ * StopTimerOnPreamble. */
|
||||
+ if (data->rx_duty_cycle_enabled && data->rx_cb != NULL) {
|
||||
+ atomic_inc(&data->dc_timeout_restarts);
|
||||
+ sx126x_restart_rx(dev, data);
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
sx126x_set_sleep(dev);
|
||||
|
||||
if (data->tx_async_signal != NULL) {
|
||||
@@ -637,6 +784,25 @@ static void sx126x_irq_work_handler(struct k_work *work)
|
||||
sx126x_handle_irq_timeout(dev);
|
||||
}
|
||||
|
||||
@@ -380,7 +410,7 @@ index 30243ba5dc7..ddc4ce19660 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);
|
||||
@@ -698,6 +841,29 @@ static int sx126x_lora_config(const struct device *dev,
|
||||
@@ -698,6 +864,29 @@ static int sx126x_lora_config(const struct device *dev,
|
||||
goto out;
|
||||
}
|
||||
|
||||
@@ -410,7 +440,7 @@ index 30243ba5dc7..ddc4ce19660 100644
|
||||
/* Set sync word */
|
||||
ret = sx126x_set_sync_word(dev, config->public_network);
|
||||
if (ret < 0) {
|
||||
@@ -721,6 +887,8 @@ out:
|
||||
@@ -721,6 +910,8 @@ out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -419,7 +449,7 @@ index 30243ba5dc7..ddc4ce19660 100644
|
||||
static int sx126x_lora_send_async(const struct device *dev,
|
||||
uint8_t *data_buf, uint32_t data_len,
|
||||
struct k_poll_signal *async)
|
||||
@@ -752,6 +920,34 @@ static int sx126x_lora_send_async(const struct device *dev,
|
||||
@@ -752,6 +943,34 @@ static int sx126x_lora_send_async(const struct device *dev,
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -454,7 +484,7 @@ index 30243ba5dc7..ddc4ce19660 100644
|
||||
data->tx_async_signal = async;
|
||||
k_msgq_purge(&data->tx_msgq);
|
||||
|
||||
@@ -777,6 +973,29 @@ static int sx126x_lora_send_async(const struct device *dev,
|
||||
@@ -777,6 +996,29 @@ static int sx126x_lora_send_async(const struct device *dev,
|
||||
/* Enable antenna and set TX path */
|
||||
sx126x_set_rf_path(dev, true, true);
|
||||
|
||||
@@ -484,7 +514,7 @@ index 30243ba5dc7..ddc4ce19660 100644
|
||||
/* Start transmission with 10 second timeout */
|
||||
ret = sx126x_set_tx(dev, 10000);
|
||||
if (ret < 0) {
|
||||
@@ -947,6 +1166,7 @@ static int sx126x_lora_recv_async(const struct device *dev,
|
||||
@@ -947,6 +1189,7 @@ static int sx126x_lora_recv_async(const struct device *dev,
|
||||
|
||||
data->rx_cb = cb;
|
||||
data->rx_cb_user_data = user_data;
|
||||
@@ -492,7 +522,7 @@ index 30243ba5dc7..ddc4ce19660 100644
|
||||
|
||||
/* Set packet parameters */
|
||||
ret = sx126x_set_packet_params(dev,
|
||||
@@ -1083,14 +1303,423 @@ static int sx126x_lora_test_cw(const struct device *dev, uint32_t frequency,
|
||||
@@ -1083,14 +1326,454 @@ static int sx126x_lora_test_cw(const struct device *dev, uint32_t frequency,
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -598,6 +628,20 @@ index 30243ba5dc7..ddc4ce19660 100644
|
||||
+ k_mutex_unlock(&data->lock);
|
||||
+}
|
||||
+
|
||||
+uint32_t sx126x_get_dc_timeout_restarts(const struct device *dev)
|
||||
+{
|
||||
+ struct sx126x_data *data = dev->data;
|
||||
+
|
||||
+ return (uint32_t)atomic_get(&data->dc_timeout_restarts);
|
||||
+}
|
||||
+
|
||||
+void sx126x_reset_dc_timeout_restarts(const struct device *dev)
|
||||
+{
|
||||
+ struct sx126x_data *data = dev->data;
|
||||
+
|
||||
+ atomic_set(&data->dc_timeout_restarts, 0);
|
||||
+}
|
||||
+
|
||||
+void sx126x_apply_heltec_reg_patch(const struct device *dev)
|
||||
+{
|
||||
+ struct sx126x_data *data = dev->data;
|
||||
@@ -780,7 +824,16 @@ index 30243ba5dc7..ddc4ce19660 100644
|
||||
+ if (cb == NULL) {
|
||||
+ data->rx_cb = NULL;
|
||||
+ data->rx_cb_user_data = NULL;
|
||||
+ data->rx_duty_cycle_enabled = false;
|
||||
+ if (atomic_cas(&data->state, SX126X_STATE_RX, SX126X_STATE_IDLE)) {
|
||||
+ /* If we're called during the duty-cycle sleep phase the
|
||||
+ * chip is actually asleep and BUSY is asserted — issuing
|
||||
+ * SetStandby straight away stalls the HAL in
|
||||
+ * wait_busy(). Poke CS via sx126x_hal_wakeup() first so
|
||||
+ * BUSY drops before we try to talk to the chip. */
|
||||
+ if (sx126x_hal_is_busy(dev)) {
|
||||
+ sx126x_hal_wakeup(dev);
|
||||
+ }
|
||||
+ sx126x_set_standby(dev, SX126X_STANDBY_RC);
|
||||
+ sx126x_set_sleep(dev);
|
||||
+ }
|
||||
@@ -884,6 +937,14 @@ index 30243ba5dc7..ddc4ce19660 100644
|
||||
+ data->dc_sleep_time = sleep_time;
|
||||
+ data->rx_duty_cycle_enabled = true;
|
||||
+
|
||||
+ /* StopTimerOnPreamble: keep the RX timer from expiring mid-packet
|
||||
+ * when a preamble is detected late in the RX window. Without it,
|
||||
+ * a packet whose preamble lands near the tail of the RX window
|
||||
+ * gets cut off by the sleep transition. */
|
||||
+ uint8_t stop_on_preamble = 1;
|
||||
+ sx126x_hal_write_cmd(dev, SX126X_CMD_STOP_TIMER_ON_PREAMBLE,
|
||||
+ &stop_on_preamble, 1);
|
||||
+
|
||||
+ uint8_t buf[6];
|
||||
+
|
||||
+ sys_put_be24(rx_time, &buf[0]);
|
||||
@@ -923,7 +984,7 @@ index 30243ba5dc7..ddc4ce19660 100644
|
||||
};
|
||||
|
||||
#ifdef CONFIG_PM_DEVICE
|
||||
@@ -1121,9 +1750,18 @@ static int sx126x_init(const struct device *dev)
|
||||
@@ -1121,9 +1804,19 @@ 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);
|
||||
@@ -933,6 +994,7 @@ index 30243ba5dc7..ddc4ce19660 100644
|
||||
data->config_valid = false;
|
||||
+ data->rx_duty_cycle_enabled = false;
|
||||
+ data->rx_boost_enabled = false;
|
||||
+ atomic_set(&data->dc_timeout_restarts, 0);
|
||||
+
|
||||
+ /* Start dedicated DIO1 work queue */
|
||||
+ k_work_queue_start(&data->dio1_wq, sx126x_dio1_wq_stack,
|
||||
@@ -943,10 +1005,10 @@ index 30243ba5dc7..ddc4ce19660 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..3aa0c7e588c 100644
|
||||
index 9dbf3f26586..79f34b67e19 100644
|
||||
--- a/drivers/lora/native/sx126x/sx126x.h
|
||||
+++ b/drivers/lora/native/sx126x/sx126x.h
|
||||
@@ -62,7 +62,21 @@ struct sx126x_data {
|
||||
@@ -62,7 +62,29 @@ struct sx126x_data {
|
||||
|
||||
/* Deferred work for interrupt handling */
|
||||
struct k_work irq_work;
|
||||
@@ -959,6 +1021,14 @@ index 9dbf3f26586..3aa0c7e588c 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) */
|
||||
+
|
||||
+ /* 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).
|
||||
+ * High values indicate a noisy RF environment or preamble-detect
|
||||
+ * threshold too loose; each event extends the real RX time past
|
||||
+ * the nominal rx_period, increasing current draw. */
|
||||
+ atomic_t dc_timeout_restarts;
|
||||
+
|
||||
+ /* CAD state */
|
||||
+ lora_cad_cb cad_cb;
|
||||
+ void *cad_user_data;
|
||||
|
||||
Reference in new issue
Block a user