diff --git a/zephcore/patches/zephyr-new/drivers/lora/lr11xx/lr11xx_lora.c b/zephcore/patches/zephyr-new/drivers/lora/lr11xx/lr11xx_lora.c index 5647ec5..e20ddba 100644 --- a/zephcore/patches/zephyr-new/drivers/lora/lr11xx/lr11xx_lora.c +++ b/zephcore/patches/zephyr-new/drivers/lora/lr11xx/lr11xx_lora.c @@ -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); diff --git a/zephcore/patches/zephyr/0003-lora-sx126x-native.patch b/zephcore/patches/zephyr/0003-lora-sx126x-native.patch index c987b37..30f4fdc 100644 --- a/zephcore/patches/zephyr/0003-lora-sx126x-native.patch +++ b/zephcore/patches/zephyr/0003-lora-sx126x-native.patch @@ -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