From 225b59e0e52230d1ae1b02fe3364a1d1278c3d95 Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Fri, 27 Feb 2026 08:51:39 +0100 Subject: [PATCH] rx duty cycle rework --- zephcore/adapters/radio/LoRaRadioBase.cpp | 14 --- .../drivers/lora/lr11xx/lr11xx_lora.c | 104 +++++++----------- zephcore/src/main_companion.cpp | 6 +- zephcore/src/main_repeater.cpp | 6 +- 4 files changed, 44 insertions(+), 86 deletions(-) diff --git a/zephcore/adapters/radio/LoRaRadioBase.cpp b/zephcore/adapters/radio/LoRaRadioBase.cpp index 18e9eb4..7ed501a 100644 --- a/zephcore/adapters/radio/LoRaRadioBase.cpp +++ b/zephcore/adapters/radio/LoRaRadioBase.cpp @@ -143,10 +143,6 @@ void LoRaRadioBase::rxCallbackStatic(const struct device *dev, uint8_t *data, self->_packets_recv_errors++; LOG_DBG("RX error (CRC/header), total errors: %u", self->_packets_recv_errors); - /* Driver restarted with Radio.Rx(0) — restore duty cycle */ - if (self->_rx_duty_cycle_enabled) { - self->hwSetRxDutyCycle(true); - } return; } @@ -159,11 +155,6 @@ void LoRaRadioBase::rxCallbackStatic(const struct device *dev, uint8_t *data, if (next_head == self->_rx_tail) { LOG_WRN("RX ring full, dropping new packet"); self->_packets_recv_errors++; - /* Still restore duty cycle and notify — main loop should - * drain faster next time. */ - if (self->_rx_duty_cycle_enabled) { - self->hwSetRxDutyCycle(true); - } if (self->_rx_cb) { self->_rx_cb(self->_rx_cb_user_data); } @@ -182,11 +173,6 @@ void LoRaRadioBase::rxCallbackStatic(const struct device *dev, uint8_t *data, self->_last_snr = (float)snr; self->_packets_recv++; - /* Driver restarted with Radio.Rx(0) — restore duty cycle */ - if (self->_rx_duty_cycle_enabled) { - self->hwSetRxDutyCycle(true); - } - if (self->_rx_cb) { self->_rx_cb(self->_rx_cb_user_data); } 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 d4a864b..550e2c1 100644 --- a/zephcore/patches/zephyr-new/drivers/lora/lr11xx/lr11xx_lora.c +++ b/zephcore/patches/zephyr-new/drivers/lora/lr11xx/lr11xx_lora.c @@ -259,35 +259,16 @@ static void lr11xx_apply_modem_config(struct lr11xx_data *data, 0); } -/* ── CAD det_peak lookup (from Semtech ral_lr11xx reference) ─────────── */ +/* ── RX duty cycle (RadioLib algorithm, same as SX126x) ─────────────── */ -static uint8_t lr11xx_cad_det_peak(uint8_t sf, enum lora_signal_bandwidth bw) -{ - static const uint8_t peak_bw500[] = { - /* SF5 SF6 SF7 SF8 SF9 SF10 SF11 SF12 */ - 65, 70, 77, 85, 78, 80, 79, 82 - }; - static const uint8_t peak_bw250[] = { - 60, 61, 64, 72, 63, 71, 73, 75 - }; - static const uint8_t peak_bw125[] = { - 56, 52, 52, 58, 58, 62, 66, 68 - }; +/* Same algorithm as SX126x SetRxDutyCycle — standard RX during the wake + * window, full demodulation capability. Preamble detected → hardware + * extends timeout to receive the complete packet. No activity → back + * to sleep. Identical detection reliability to the SX126x driver. */ - if (sf < 5 || sf > 12) return 0x32; - uint8_t idx = sf - 5; - - if (bw >= BW_500_KHZ) return peak_bw500[idx]; - if (bw >= BW_250_KHZ) return peak_bw250[idx]; - return peak_bw125[idx]; -} - -/* ── RX duty cycle (hardware CAD mode) ──────────────────────────────── */ - -/* LR1110 native CAD duty cycle: on each wake the chip runs a fast CAD - * (~2 symbols) instead of full RX. Preamble detected → auto-RX with - * extended timeout. No activity → straight back to sleep. The entire - * loop runs in HW with zero IRQ overhead per cycle. */ +#define LR11XX_DC_MIN_SYMBOLS_SF7_PLUS 8 +#define LR11XX_DC_MIN_SYMBOLS_SF6_LESS 12 +#define LR11XX_DC_TCXO_DELAY_US 1000 static void lr11xx_apply_rx_duty_cycle(struct lr11xx_data *data) { @@ -298,55 +279,50 @@ static void lr11xx_apply_rx_duty_cycle(struct lr11xx_data *data) float bw_khz = bw_enum_to_khz(mc->bandwidth); uint16_t preamble_len = mc->preamble_len; - /* Margin must cover: - * 2 sym CAD scan duration (cad_symb_nb = 2 symbols) - * 4 sym demodulator preamble acquisition (~4.25 sym minimum) - * 4 sym RTC jitter + TCXO startup + state transitions - * ───── - * 10 sym total — matches the SX126x driver margin and ensures - * ≥6 symbols of preamble remain after worst-case wake. - * - * The original margin of 4 left only ~2 usable symbols — any TCXO - * startup or timer jitter caused missed packets. */ - uint32_t symbol_us = (uint32_t)((float)(1 << sf) * 1000.0f / bw_khz); - int16_t margin = 10; - int16_t sleep_symbols = (int16_t)preamble_len - margin; + uint16_t min_symbols = (sf >= 7) ? LR11XX_DC_MIN_SYMBOLS_SF7_PLUS + : LR11XX_DC_MIN_SYMBOLS_SF6_LESS; - if (sleep_symbols < 2) { - LOG_WRN("Preamble too short for CAD duty cycle (%d sym), " - "continuous RX", preamble_len); + int16_t sleep_symbols = (int16_t)preamble_len - (int16_t)min_symbols; + if (sleep_symbols <= 0) { + LOG_WRN("Preamble too short for duty cycle (need >%d, have %d)", + min_symbols, preamble_len); data->rx_duty_cycle_enabled = false; lr11xx_radio_set_rx(ctx, 0xFFFFFF); return; } - /* CAD params: 2 symbols, Semtech det_peak table, auto-RX on detect */ - uint8_t det_peak = lr11xx_cad_det_peak(sf, mc->bandwidth); - lr11xx_radio_cad_params_t cad = { - .cad_symb_nb = 1, /* enum: 1 = 2 symbols */ - .cad_detect_peak = det_peak, - .cad_detect_min = 10, - .cad_exit_mode = LR11XX_RADIO_CAD_EXIT_MODE_RX, - .cad_timeout = 0, - }; - lr11xx_radio_set_cad_params(ctx, &cad); + uint32_t symbol_us = (uint32_t)((float)(1 << sf) * 1000.0f / bw_khz); - uint32_t sleep_us = (uint16_t)sleep_symbols * symbol_us; + /* Shave 2 symbols off sleep for timing margin */ + int16_t sleep_symbols_safe = sleep_symbols - 2; + if (sleep_symbols_safe < 1) { + sleep_symbols_safe = 1; + } + uint32_t sleep_period_us = (uint16_t)sleep_symbols_safe * symbol_us; - /* RX period: on CAD detect, chip enters RX for (2*rx + sleep). - * Must cover remaining preamble + sync word + header. */ - uint32_t rx_us = (preamble_len + 1) * symbol_us; + uint32_t preamble_total_us = (preamble_len + 1) * symbol_us; + int32_t wake_calc1 = ((int32_t)preamble_total_us - + ((int32_t)sleep_period_us - LR11XX_DC_TCXO_DELAY_US)) / 2; + uint32_t wake_calc2 = (min_symbols + 1) * symbol_us; - uint32_t rx_ms = (rx_us + 500) / 1000; - uint32_t sleep_ms = (sleep_us + 500) / 1000; - if (rx_ms < 1) rx_ms = 1; - if (sleep_ms < 1) sleep_ms = 1; + uint32_t wake_period_us = (wake_calc1 > 0 && (uint32_t)wake_calc1 > wake_calc2) + ? (uint32_t)wake_calc1 : wake_calc2; + + /* LR1110 API takes milliseconds (converted to RTC steps internally) */ + uint32_t rx_ms = (wake_period_us + 500) / 1000; + uint32_t sleep_ms = (sleep_period_us + 500) / 1000; + if (rx_ms < 1) { + rx_ms = 1; + } + if (sleep_ms < 1) { + sleep_ms = 1; + } lr11xx_radio_set_rx_duty_cycle(ctx, rx_ms, sleep_ms, - LR11XX_RADIO_RX_DUTY_CYCLE_MODE_CAD); + LR11XX_RADIO_RX_DUTY_CYCLE_MODE_RX); - LOG_INF("RX CAD duty cycle: rx=%ums sleep=%ums peak=%u (SF%d BW%.0f)", - rx_ms, sleep_ms, det_peak, sf, (double)bw_khz); + LOG_INF("RX duty cycle: rx=%ums sleep=%ums (SF%d BW%.0f)", + rx_ms, sleep_ms, sf, (double)bw_khz); } /* ── Start RX (internal) ────────────────────────────────────────────── */ diff --git a/zephcore/src/main_companion.cpp b/zephcore/src/main_companion.cpp index 0bba147..989fd48 100644 --- a/zephcore/src/main_companion.cpp +++ b/zephcore/src/main_companion.cpp @@ -634,11 +634,9 @@ int main(void) } } - /* Apply RX boost from prefs. Duty cycle is disabled — CAD-based - * duty cycling misses weak packets that continuous RX would catch. - * Arduino reference uses continuous RX for all boards. */ + /* Apply RX boost and duty cycle from prefs */ lora_radio.setRxBoost(companion_mesh.prefs.rx_boost != 0); - lora_radio.enableRxDutyCycle(false); + lora_radio.enableRxDutyCycle(companion_mesh.prefs.rx_duty_cycle != 0); /* Initialize mesh event object */ k_event_init(&mesh_events); diff --git a/zephcore/src/main_repeater.cpp b/zephcore/src/main_repeater.cpp index f1db475..22809e6 100644 --- a/zephcore/src/main_repeater.cpp +++ b/zephcore/src/main_repeater.cpp @@ -474,11 +474,9 @@ int main(void) } } - /* Apply RX boost from prefs. Duty cycle is disabled — CAD-based - * duty cycling misses weak packets that continuous RX would catch. - * Arduino reference uses continuous RX for all boards. */ + /* Apply RX boost and duty cycle from prefs */ lora_radio.setRxBoost(prefs->rx_boost != 0); - lora_radio.enableRxDutyCycle(false); + lora_radio.enableRxDutyCycle(prefs->rx_duty_cycle != 0); /* Feed initial UI state from loaded prefs */ ui_set_node_name(prefs->node_name);