radio driver fixes/enhancements

This commit is contained in:
liquidraver
2026-07-10 18:50:54 +02:00
parent 2232e579c2
commit 3566bf39e9
2 changed files with 156 additions and 39 deletions
@@ -104,6 +104,18 @@ struct lr11xx_data {
* the LR1110 is hung — trigger a hardware reset. */
int dio1_stuck_count;
/* Timestamp (k_uptime_get_32(), ms) of the first sighting of a
* latched PREAMBLE_DETECTED by lr11xx_is_receiving() in the current
* RX cycle; 0 = none tracked. Ported from the SX126x preamble-grace
* logic: PREAMBLE_DETECTED is not DIO1-routed but latches in the IRQ
* status register, and the chip never auto-clears it on a foreign
* sync word — without a software bound a foreign/noise preamble pins
* the TX gate until the next DIO1 bulk-clear or the dispatcher's 4 s
* CAD-fail recovery. Real packets latch SYNC_WORD_HEADER_VALID
* within the grace window; after grace expires with no header, the
* bit is cleared and TX released. All accesses under spi_mutex. */
uint32_t preamble_seen_at_ms;
/* RX data buffer — filled in DIO1 handler, passed to callback */
uint8_t rx_buf[256];
};
@@ -256,8 +268,11 @@ static void lr11xx_apply_modem_config(struct lr11xx_data *data,
lr11xx_radio_set_lora_sync_word(ctx, mc->public_network ? 0x34 : 0x12);
if (tx_mode) {
lr11xx_radio_set_tx_params(ctx, mc->tx_power,
LR11XX_RADIO_RAMP_48_US);
/* PA config BEFORE TX params — the power byte in SetTxParams
* is interpreted against the currently selected PA (Semtech
* convention; RadioLib LR11x0::setOutputPower orders it the
* same). Reversed order only bites the first TX after a
* (hardware) reset, when the chip default PA is still live. */
lr11xx_radio_pa_cfg_t pa = {
.pa_sel = LR11XX_RADIO_PA_SEL_HP,
.pa_reg_supply = LR11XX_RADIO_PA_REG_SUPPLY_VBAT,
@@ -265,6 +280,8 @@ static void lr11xx_apply_modem_config(struct lr11xx_data *data,
.pa_hp_sel = cfg->pa_hp_sel,
};
lr11xx_radio_set_pa_cfg(ctx, &pa);
lr11xx_radio_set_tx_params(ctx, mc->tx_power,
LR11XX_RADIO_RAMP_48_US);
}
lr11xx_system_set_dio_irq_params(
@@ -306,6 +323,7 @@ static void lr11xx_start_rx(struct lr11xx_data *data,
}
lr11xx_system_clear_irq_status(ctx, LR11XX_SYSTEM_IRQ_ALL_MASK);
data->preamble_seen_at_ms = 0;
/* Apply modem config for RX */
lr11xx_apply_modem_config(data, cfg, false);
@@ -366,6 +384,7 @@ 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);
data->preamble_seen_at_ms = 0;
if (data->rx_duty_cycle_enabled &&
data->dc_rx_ms != 0 && data->dc_sleep_ms != 0) {
@@ -767,12 +786,15 @@ static int lr11xx_lora_send_async(const struct device *dev,
/* Clear IRQ, enable DIO1 */
lr11xx_system_clear_irq_status(ctx, LR11XX_SYSTEM_IRQ_ALL_MASK);
data->preamble_seen_at_ms = 0;
lr11xx_hal_enable_dio1_irq(&data->hal_ctx);
/* Store signal and start TX */
/* 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. */
data->tx_signal = async;
data->tx_active = true;
lr11xx_radio_set_tx(ctx, 5000);
lr11xx_radio_set_tx(ctx, 10000);
k_mutex_unlock(&data->spi_mutex);
return 0;
@@ -915,6 +937,24 @@ int16_t lr11xx_get_rssi_inst(const struct device *dev)
return (int16_t)rssi;
}
/* Grace period for the PREAMBLE_DETECTED -> SYNC_WORD_HEADER_VALID gap,
* SF/BW-aware — same formula as the SX126x driver: (preamble_len + 8)
* symbols covers worst-case preamble remainder + sync word + header
* decode with margin. */
static uint32_t lr11xx_preamble_grace_ms(struct lr11xx_data *data)
{
uint8_t sf = (uint8_t)data->modem_cfg.datarate;
uint32_t bw_hz = (uint32_t)(bw_enum_to_khz(data->modem_cfg.bandwidth) * 1000.0f);
uint16_t preamble = data->modem_cfg.preamble_len;
if (bw_hz == 0 || sf < 5 || sf > 12) {
return 1000; /* safe default ~1 s if config is uninitialised */
}
uint64_t us = ((uint64_t)(preamble + 8U) << sf) * 1000000ULL / bw_hz;
return (uint32_t)((us + 999U) / 1000U);
}
bool lr11xx_is_receiving(const struct device *dev)
{
struct lr11xx_data *data = dev->data;
@@ -924,12 +964,49 @@ bool lr11xx_is_receiving(const struct device *dev)
return false;
}
lr11xx_system_irq_mask_t irq;
lr11xx_system_irq_mask_t irq = 0;
lr11xx_system_get_irq_status(&data->hal_ctx, &irq);
k_mutex_unlock(&data->spi_mutex);
return (irq & (LR11XX_SYSTEM_IRQ_PREAMBLE_DETECTED |
LR11XX_SYSTEM_IRQ_SYNC_WORD_HEADER_VALID)) != 0;
/* Header landed: payload phase in progress. The bit stays latched
* until the terminal DIO1 event bulk-clears it, so this covers the
* whole packet. */
if (irq & LR11XX_SYSTEM_IRQ_SYNC_WORD_HEADER_VALID) {
k_mutex_unlock(&data->spi_mutex);
return true;
}
/* PREAMBLE_DETECTED with SF-aware grace (SX126x-parity). Within
* grace, keep reporting busy so a real packet has time to land its
* header; after grace with no header, assume foreign sync word,
* clear the bit and release TX. */
if (irq & LR11XX_SYSTEM_IRQ_PREAMBLE_DETECTED) {
uint32_t now = k_uptime_get_32();
uint32_t seen = data->preamble_seen_at_ms;
if (seen == 0) {
data->preamble_seen_at_ms = (now == 0) ? 1U : now;
k_mutex_unlock(&data->spi_mutex);
return true;
}
if ((now - seen) < lr11xx_preamble_grace_ms(data)) {
k_mutex_unlock(&data->spi_mutex);
return true;
}
/* Grace expired. Clear CMD_ERROR along with the preamble
* bit — the LR1110 sets CMD_ERROR whenever ClearIrq is
* called with a mask that excludes it (all FW versions). */
lr11xx_system_clear_irq_status(&data->hal_ctx,
LR11XX_SYSTEM_IRQ_PREAMBLE_DETECTED |
LR11XX_SYSTEM_IRQ_CMD_ERROR);
data->preamble_seen_at_ms = 0;
k_mutex_unlock(&data->spi_mutex);
return false;
}
/* No preamble, no header: nothing in flight. */
data->preamble_seen_at_ms = 0;
k_mutex_unlock(&data->spi_mutex);
return false;
}
uint32_t lr11xx_get_wakeup_time_us(const struct device *dev)
@@ -1072,6 +1149,7 @@ static int lr11xx_do_cad(struct lr11xx_data *data)
lr11xx_radio_set_cad_params(ctx, &cad);
lr11xx_system_clear_irq_status(ctx, LR11XX_SYSTEM_IRQ_ALL_MASK);
data->preamble_seen_at_ms = 0;
data->cad_active = true;
lr11xx_radio_set_cad(ctx);
@@ -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..69eefee6e0f 100644
index 30243ba5dc7..ef06d9273ae 100644
--- a/drivers/lora/native/sx126x/sx126x.c
+++ b/drivers/lora/native/sx126x/sx126x.c
@@ -6,14 +6,21 @@
@@ -878,7 +878,7 @@ index 30243ba5dc7..69eefee6e0f 100644
/* Start transmission with 10 second timeout */
ret = sx126x_set_tx(dev, 10000);
if (ret < 0) {
@@ -847,6 +1426,8 @@ static int sx126x_lora_recv(const struct device *dev, uint8_t *data_buf,
@@ -847,6 +1428,8 @@ static int sx126x_lora_recv(const struct device *dev, uint8_t *data_buf,
}
data->rx_cb = NULL;
@@ -887,7 +887,7 @@ index 30243ba5dc7..69eefee6e0f 100644
k_msgq_purge(&data->rx_msgq);
/* Set packet parameters for variable length reception */
@@ -918,6 +1499,8 @@ static int sx126x_lora_recv_async(const struct device *dev,
@@ -918,6 +1501,8 @@ static int sx126x_lora_recv_async(const struct device *dev,
/* Stop async reception */
data->rx_cb = NULL;
data->rx_cb_user_data = NULL;
@@ -896,7 +896,7 @@ index 30243ba5dc7..69eefee6e0f 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 +1515,19 @@ static int sx126x_lora_recv_async(const struct device *dev,
@@ -932,6 +1517,19 @@ static int sx126x_lora_recv_async(const struct device *dev,
return -EINVAL;
}
@@ -916,7 +916,7 @@ index 30243ba5dc7..69eefee6e0f 100644
if (!atomic_cas(&data->state, SX126X_REST_STATE, SX126X_STATE_RX)) {
LOG_ERR("Busy");
k_mutex_unlock(&data->lock);
@@ -945,8 +1541,18 @@ static int sx126x_lora_recv_async(const struct device *dev,
@@ -945,8 +1543,18 @@ static int sx126x_lora_recv_async(const struct device *dev,
return ret;
}
@@ -935,7 +935,7 @@ index 30243ba5dc7..69eefee6e0f 100644
/* Set packet parameters */
ret = sx126x_set_packet_params(dev,
@@ -959,6 +1565,7 @@ static int sx126x_lora_recv_async(const struct device *dev,
@@ -959,6 +1567,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;
@@ -943,7 +943,7 @@ index 30243ba5dc7..69eefee6e0f 100644
sx126x_set_sleep(dev);
k_mutex_unlock(&data->lock);
return ret;
@@ -971,11 +1578,21 @@ static int sx126x_lora_recv_async(const struct device *dev,
@@ -971,11 +1580,21 @@ static int sx126x_lora_recv_async(const struct device *dev,
ret = sx126x_set_rx(dev, 0);
if (ret < 0) {
data->rx_cb = NULL;
@@ -965,7 +965,7 @@ index 30243ba5dc7..69eefee6e0f 100644
k_mutex_unlock(&data->lock);
return 0;
}
@@ -1051,7 +1668,7 @@ static int sx126x_lora_test_cw(const struct device *dev, uint32_t frequency,
@@ -1051,7 +1670,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,
@@ -974,7 +974,7 @@ index 30243ba5dc7..69eefee6e0f 100644
if (ret < 0) {
sx126x_set_sleep(dev);
k_mutex_unlock(&data->lock);
@@ -1083,14 +1700,595 @@ static int sx126x_lora_test_cw(const struct device *dev, uint32_t frequency,
@@ -1083,14 +1702,595 @@ static int sx126x_lora_test_cw(const struct device *dev, uint32_t frequency,
return 0;
}
@@ -1208,20 +1208,20 @@ index 30243ba5dc7..69eefee6e0f 100644
+
+/* -- Driver API: CAD -- */
+
+/* Recommended cad_detect_peak values per SF for 2-symbol CAD.
+ * From Semtech SX1261/62/68 datasheet AN1200.48. */
+/* Recommended cad_detect_peak for the SX126x is SF + 13 (Semtech
+ * DS.SX1261-2.W.APP p92; RadioLib setCad default; Meshtastic pairs the
+ * same value with 2-symbol CAD per AN1200.48). The DetPeak scale is
+ * chip-family-specific: LR11xx wants ~48-68 for the same job -- never
+ * copy values across families (56-68 here once made CAD near-blind and
+ * the LBT gate inert). */
+static uint8_t sx126x_cad_detect_peak(uint8_t sf)
+{
+ switch (sf) {
+ case 5: case 6: return 56;
+ case 7: return 56;
+ case 8: return 58;
+ case 9: return 58;
+ case 10: return 60;
+ case 11: return 64;
+ case 12: return 68;
+ default: return 60;
+ if (sf < 5) {
+ sf = 5;
+ } else if (sf > 12) {
+ sf = 12;
+ }
+ return sf + 13;
+}
+
+static int sx126x_do_cad(const struct device *dev, struct sx126x_data *data)
@@ -1577,7 +1577,7 @@ index 30243ba5dc7..69eefee6e0f 100644
};
#ifdef CONFIG_PM_DEVICE
@@ -1112,6 +2310,7 @@ static int sx126x_pm_action(const struct device *dev,
@@ -1112,6 +2312,7 @@ static int sx126x_pm_action(const struct device *dev,
static int sx126x_init(const struct device *dev)
{
struct sx126x_data *data = dev->data;
@@ -1585,7 +1585,7 @@ index 30243ba5dc7..69eefee6e0f 100644
int ret;
/* Initialize data structures */
@@ -1121,9 +2320,29 @@ static int sx126x_init(const struct device *dev)
@@ -1121,9 +2322,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);
@@ -1690,13 +1690,54 @@ index 9dbf3f26586..2598b56d048 100644
#endif /* ZEPHYR_DRIVERS_LORA_SX126X_SX126X_INTERNAL_H_ */
diff --git a/drivers/lora/native/sx126x/sx126x_hal.c b/drivers/lora/native/sx126x/sx126x_hal.c
index 34f7089584f..c28f2659fce 100644
index 34f7089584f..262f5b55aa8 100644
--- a/drivers/lora/native/sx126x/sx126x_hal.c
+++ b/drivers/lora/native/sx126x/sx126x_hal.c
@@ -231,6 +231,19 @@ int sx126x_hal_configure_tx_params(const struct device *dev, int8_t power,
tx_power = CLAMP(power, SX1262_MIN_POWER, SX1262_MAX_POWER);
}
@@ -220,15 +220,54 @@ int sx126x_hal_configure_tx_params(const struct device *dev, int8_t power,
}
tx_power = CLAMP(power, SX1261_MIN_POWER, SX1261_MAX_POWER_TX_PARAM);
} else {
- /* SX1262: High power PA, up to +22 dBm */
- ret = sx126x_hal_set_pa_config(dev, SX1262_PA_DUTY_CYCLE,
- SX1262_HP_MAX,
+ /* SX1262: High power PA, up to +22 dBm.
+ *
+ * Measured-optimal PaConfig per requested power instead of the
+ * datasheet's fixed (dutyCycle=4, hpMax=7): same RF output at
+ * lower PA supply current below +22 dBm. Table from RadioLib
+ * PR #1662 (github.com/radiolib-org/power-tests, SX1262).
+ * Index = clamped power + 9 (range -9..+22); pa_val is the
+ * SetTxParams byte belonging to that PaConfig -- it is NOT
+ * the requested dBm and must be sent verbatim. At +22 dBm
+ * the entry {4, 7, 22} equals the old fixed config. */
+ static const struct {
+ uint8_t pa_duty_cycle;
+ uint8_t hp_max;
+ int8_t pa_val;
+ } pa_opt[32] = {
+ { 2, 2, -5 }, { 2, 1, 0 }, { 1, 1, 3 }, { 1, 2, 0 },
+ { 1, 1, 6 }, { 1, 2, 3 }, { 2, 2, 2 }, { 4, 1, 6 },
+ { 1, 1, 11 }, { 2, 1, 11 }, { 1, 1, 14 }, { 2, 1, 14 },
+ { 1, 1, 20 }, { 1, 1, 22 }, { 2, 2, 11 }, { 3, 1, 21 },
+ { 1, 2, 17 }, { 4, 2, 13 }, { 1, 2, 20 }, { 1, 2, 22 },
+ { 2, 2, 21 }, { 3, 2, 21 }, { 1, 4, 19 }, { 1, 4, 20 },
+ { 3, 3, 20 }, { 2, 5, 19 }, { 1, 6, 22 }, { 2, 5, 22 },
+ { 3, 5, 22 }, { 3, 6, 22 }, { 4, 6, 22 }, { 4, 7, 22 },
+ };
+ int8_t req = CLAMP(power, SX1262_MIN_POWER, SX1262_MAX_POWER);
+ uint8_t idx = (uint8_t)(req - SX1262_MIN_POWER);
+
+ ret = sx126x_hal_set_pa_config(dev, pa_opt[idx].pa_duty_cycle,
+ pa_opt[idx].hp_max,
SX126X_DEVICE_SEL_SX1262,
SX126X_PA_LUT);
if (ret < 0) {
return ret;
}
- tx_power = CLAMP(power, SX1262_MIN_POWER, SX1262_MAX_POWER);
+ tx_power = pa_opt[idx].pa_val;
+ }
+
+ /* SetPaConfig does not auto-update OCP on the standalone SX126x
+ * (cold-reset default is 60 mA / 0x18). The SX1262 high-power PA
+ * needs ~118 mA at +22 dBm, so OCP must be raised to 140 mA / 0x38
@@ -1708,11 +1749,9 @@ index 34f7089584f..c28f2659fce 100644
+ ret = sx126x_hal_write_regs(dev, SX126X_REG_OCP, &ocp_value, 1);
+ if (ret < 0) {
+ return ret;
+ }
+
uint8_t buf[2] = { (uint8_t)tx_power, ramp_time };
}
return sx126x_hal_write_cmd(dev, SX126X_CMD_SET_TX_PARAMS, buf, 2);
uint8_t buf[2] = { (uint8_t)tx_power, ramp_time };
diff --git a/drivers/lora/native/sx126x/sx126x_hal_common.c b/drivers/lora/native/sx126x/sx126x_hal_common.c
index b2dfc0d75b5..8ab8a0f5738 100644
--- a/drivers/lora/native/sx126x/sx126x_hal_common.c