mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-09-01 21:08:19 +00:00
rework sx and lr1110, improve BLE sync
This commit is contained in:
+10
-17
@@ -14,21 +14,19 @@ cmake_minimum_required(VERSION 3.20.0)
|
||||
# Directory layout:
|
||||
# patches/zephyr/*.patch - unified diffs applied to the Zephyr tree
|
||||
# patches/zephyr-new/ - new files copied to the Zephyr tree (no upstream)
|
||||
# patches/loramac-node/*.patch - unified diffs for modules/lib/loramac-node
|
||||
# patches/espressif/*.patch - unified diffs for modules/hal/espressif
|
||||
#
|
||||
# Zephyr patches:
|
||||
# 0001-lora-lr11xx-build - CMakeLists.txt + Kconfig (lr11xx subdirectory)
|
||||
# 0002-lora-sx12xx-common - RX error callback, bandwidth mapping, fast TX/RX
|
||||
# 0003-lora-sx126x-native - PA module RF switch fix (dio2-tx-enable)
|
||||
# 0004-lora-sx126x-standalone - DIO1 IRQ error logging
|
||||
# 0003-lora-sx126x-native - native driver: DIO1 WQ, duty cycle, CRC fix,
|
||||
# extension API, RF switch fix
|
||||
# 0005-gnss-air530z-easy - EASY ephemeris prediction (PMTK869) + Kconfig
|
||||
# 0006-blobs-py - west blobs command fix
|
||||
# New files (copied, not patched):
|
||||
# drivers/lora/lr11xx/* - LR11xx Zephyr LoRa driver
|
||||
# drivers/lora/native/sx126x/sx126x_ext.h - SX126x extension API header
|
||||
# dts/bindings/lora/semtech,lr1110.yaml - LR1110 DTS binding
|
||||
# Module patches:
|
||||
# loramac-node: restore 10-element Bandwidths[] + LDRO fix
|
||||
# espressif: MCUboot config flexibility (mbedtls, validation, sector count)
|
||||
#
|
||||
|
||||
@@ -108,16 +106,6 @@ if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/patches/zephyr-new)
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
# Apply unified diff patches to loramac-node module
|
||||
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/patches/loramac-node)
|
||||
message(STATUS "Applying ZephCore patches to loramac-node...")
|
||||
zephcore_apply_patches(
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/patches/loramac-node"
|
||||
"${MODULES_DIR}/lib/loramac-node"
|
||||
"loramac-node"
|
||||
)
|
||||
endif()
|
||||
|
||||
# Apply unified diff patches to espressif module
|
||||
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/patches/espressif)
|
||||
message(STATUS "Applying ZephCore patches to espressif...")
|
||||
@@ -367,11 +355,16 @@ if(CONFIG_ZEPHCORE_RADIO_LR1110)
|
||||
${ZEPHYR_DIR}/drivers/lora/lr11xx
|
||||
)
|
||||
else()
|
||||
# Default: SX126x via Zephyr LoRa driver
|
||||
message(STATUS "ZephCore Radio: SX126x (Zephyr LoRa driver)")
|
||||
# Default: SX126x via native Zephyr LoRa driver
|
||||
message(STATUS "ZephCore Radio: SX126x (native Zephyr driver)")
|
||||
target_sources(app PRIVATE
|
||||
adapters/radio/SX126xRadio.cpp
|
||||
)
|
||||
# Extension API header (sx126x_ext.h) is in the Zephyr driver tree
|
||||
get_filename_component(ZEPHYR_DIR_SX ${CMAKE_CURRENT_SOURCE_DIR}/../zephyr ABSOLUTE)
|
||||
target_include_directories(app PRIVATE
|
||||
${ZEPHYR_DIR_SX}/drivers/lora/native/sx126x
|
||||
)
|
||||
endif()
|
||||
|
||||
# ========== Role-Specific Sources ==========
|
||||
|
||||
@@ -43,6 +43,11 @@ LOG_MODULE_REGISTER(zephcore_ble, CONFIG_ZEPHCORE_BLE_LOG_LEVEL);
|
||||
/* TX timeout watchdog - reset ble_tx_in_progress if callback never fires */
|
||||
#define BLE_TX_TIMEOUT_MS 2000
|
||||
|
||||
/* Congestion overflow retry interval — when the TX queue is full, the
|
||||
* stuck frame retries at this cadence. Slow enough to not hammer the
|
||||
* BLE stack when the link is marginal, fast enough to recover quickly. */
|
||||
#define BLE_TX_OVERFLOW_RETRY_MS 250
|
||||
|
||||
/* Advertising intervals (Apple Accessory Design Guidelines) */
|
||||
#define BT_ADV_INTERVAL_FAST CONFIG_ZEPHCORE_BLE_ADV_FAST_INTERVAL
|
||||
#define BT_ADV_INTERVAL_SLOW CONFIG_ZEPHCORE_BLE_ADV_SLOW_INTERVAL
|
||||
@@ -78,6 +83,23 @@ K_MSGQ_DEFINE(ble_recv_queue, sizeof(struct frame), FRAME_QUEUE_SIZE, 4);
|
||||
static struct frame tx_retry_frame;
|
||||
static bool tx_retry_pending = false;
|
||||
|
||||
/* TX congestion control — flow-control mechanism for queue-full conditions.
|
||||
*
|
||||
* When the TX queue is full, instead of blocking or dropping:
|
||||
* 1. Set ble_tx_congested flag → callers (contact iteration, etc.) stop sending
|
||||
* 2. Save the stuck frame to overflow_frame → retried every 250ms
|
||||
* 3. When queue drains below low water mark (1/3) → clear congestion
|
||||
* 4. On disconnect → clear everything
|
||||
*
|
||||
* Water marks (with default FRAME_QUEUE_SIZE=12):
|
||||
* - Contact iteration pauses: 2/3 = 8 frames (high water)
|
||||
* - Congestion mode enters: 12/12 = full
|
||||
* - Congestion mode clears: 1/3 = 4 frames (low water)
|
||||
*/
|
||||
static bool ble_tx_congested;
|
||||
static struct frame overflow_frame;
|
||||
static bool overflow_pending;
|
||||
|
||||
/* Connection state */
|
||||
static struct bt_conn *current_conn;
|
||||
static bool nus_notif_enabled;
|
||||
@@ -95,6 +117,9 @@ static bool adv_is_slow = false;
|
||||
/* Runtime BLE passkey */
|
||||
static uint32_t ble_passkey = CONFIG_ZEPHCORE_BLE_PASSKEY;
|
||||
|
||||
/* NUS TX characteristic attribute — resolved at init, avoids hard-coded offset */
|
||||
static const struct bt_gatt_attr *nus_tx_attr;
|
||||
|
||||
/* ========== Forward declarations ========== */
|
||||
|
||||
static void ble_tx_complete_cb(struct bt_conn *conn, void *user_data);
|
||||
@@ -140,9 +165,11 @@ STRUCT_SECTION_ITERABLE(bt_nus_inst, secure_nus) = {
|
||||
|
||||
static void tx_drain_work_fn(struct k_work *work);
|
||||
static void adv_slow_work_fn(struct k_work *work);
|
||||
static void overflow_retry_work_fn(struct k_work *work);
|
||||
|
||||
K_WORK_DELAYABLE_DEFINE(tx_drain_work, tx_drain_work_fn);
|
||||
K_WORK_DELAYABLE_DEFINE(adv_slow_work, adv_slow_work_fn);
|
||||
K_WORK_DELAYABLE_DEFINE(overflow_retry_work, overflow_retry_work_fn);
|
||||
|
||||
/* ========== TX completion callback ========== */
|
||||
|
||||
@@ -165,7 +192,7 @@ static void ble_tx_complete_cb(struct bt_conn *conn, void *user_data)
|
||||
static int secure_nus_send(struct bt_conn *conn, const void *data, uint16_t len)
|
||||
{
|
||||
struct bt_gatt_notify_params params = {
|
||||
.attr = &secure_nus_svc.attrs[2], /* TX characteristic value (not declaration) */
|
||||
.attr = nus_tx_attr,
|
||||
.data = data,
|
||||
.len = len,
|
||||
.func = ble_tx_complete_cb,
|
||||
@@ -303,12 +330,15 @@ static void disconnected(struct bt_conn *conn, uint8_t reason)
|
||||
LOG_INF("active_iface = IFACE_NONE");
|
||||
}
|
||||
|
||||
/* Clear queues and retry state */
|
||||
/* Clear queues, retry state, and congestion */
|
||||
k_msgq_purge(&ble_send_queue);
|
||||
k_msgq_purge(&ble_recv_queue);
|
||||
tx_retry_pending = false;
|
||||
overflow_pending = false;
|
||||
ble_tx_congested = false;
|
||||
|
||||
k_work_cancel_delayable(&tx_drain_work);
|
||||
k_work_cancel_delayable(&overflow_retry_work);
|
||||
|
||||
/* Notify main of BLE disconnection */
|
||||
if (ble_cbs && ble_cbs->on_disconnected) {
|
||||
@@ -379,6 +409,17 @@ static void security_changed(struct bt_conn *conn, bt_security_t level, enum bt_
|
||||
|
||||
static bool le_param_req(struct bt_conn *conn, struct bt_le_conn_param *param)
|
||||
{
|
||||
/* Enforce our configured connection parameters — reject anything
|
||||
* outside our preferred range. The phone will fall back to our
|
||||
* PPCP (Peripheral Preferred Connection Parameters) on rejection. */
|
||||
if (param->interval_min < BLE_DEFAULT_MIN_INTERVAL ||
|
||||
param->interval_max > BLE_DEFAULT_MAX_INTERVAL) {
|
||||
LOG_WRN("Rejecting peer conn params: interval %u-%u "
|
||||
"(our range: %u-%u)",
|
||||
param->interval_min, param->interval_max,
|
||||
BLE_DEFAULT_MIN_INTERVAL, BLE_DEFAULT_MAX_INTERVAL);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -464,6 +505,37 @@ static struct bt_conn_auth_info_cb auth_info_cb = {
|
||||
.pairing_failed = pairing_failed,
|
||||
};
|
||||
|
||||
/* ========== TX congestion overflow retry ========== */
|
||||
|
||||
static void overflow_retry_work_fn(struct k_work *work)
|
||||
{
|
||||
ARG_UNUSED(work);
|
||||
|
||||
if (!overflow_pending) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Abandon overflow if connection is gone */
|
||||
if (!current_conn || active_iface == ZEPHCORE_IFACE_NONE) {
|
||||
overflow_pending = false;
|
||||
ble_tx_congested = false;
|
||||
LOG_INF("overflow cleared (disconnected)");
|
||||
return;
|
||||
}
|
||||
|
||||
if (k_msgq_put(&ble_send_queue, &overflow_frame, K_NO_WAIT) == 0) {
|
||||
overflow_pending = false;
|
||||
LOG_INF("overflow frame queued hdr=0x%02x, kicking drain",
|
||||
overflow_frame.buf[0]);
|
||||
kick_tx_drain();
|
||||
/* Congestion flag cleared by tx_drain at low water mark */
|
||||
} else {
|
||||
/* Still full — retry at reduced rate */
|
||||
LOG_DBG("overflow retry: queue still full, retry in 250ms");
|
||||
k_work_schedule(&overflow_retry_work, K_MSEC(BLE_TX_OVERFLOW_RETRY_MS));
|
||||
}
|
||||
}
|
||||
|
||||
/* ========== TX drain work ========== */
|
||||
|
||||
static void kick_tx_drain(void)
|
||||
@@ -503,6 +575,14 @@ static void tx_drain_work_fn(struct k_work *work)
|
||||
return;
|
||||
}
|
||||
|
||||
/* Take a reference snapshot — prevents use-after-free if
|
||||
* disconnected() fires from another context between our check
|
||||
* and use of the connection pointer. */
|
||||
struct bt_conn *conn = bt_conn_ref(current_conn);
|
||||
if (!conn) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Re-entrancy guard - only one TX in flight at a time */
|
||||
if (ble_tx_in_progress) {
|
||||
/* TX timeout watchdog - if callback never fired, reset state */
|
||||
@@ -512,6 +592,7 @@ static void tx_drain_work_fn(struct k_work *work)
|
||||
/* Fall through to try next TX */
|
||||
} else {
|
||||
LOG_DBG("tx_drain[BLE]: TX in progress, callback will chain");
|
||||
bt_conn_unref(conn);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -521,15 +602,17 @@ static void tx_drain_work_fn(struct k_work *work)
|
||||
LOG_INF("tx_drain[BLE]: retrying len=%u hdr=0x%02x", (unsigned)tx_retry_frame.len, tx_retry_frame.buf[0]);
|
||||
ble_tx_in_progress = true;
|
||||
ble_tx_start_time = k_uptime_get();
|
||||
err = secure_nus_send(current_conn, tx_retry_frame.buf, tx_retry_frame.len);
|
||||
err = secure_nus_send(conn, tx_retry_frame.buf, tx_retry_frame.len);
|
||||
if (err == 0) {
|
||||
tx_retry_pending = false;
|
||||
LOG_INF("tx_drain[BLE]: retry success");
|
||||
bt_conn_unref(conn);
|
||||
return; /* Callback will chain to next */
|
||||
} else if (err == -EAGAIN || err == -ENOMEM) {
|
||||
ble_tx_in_progress = false;
|
||||
LOG_DBG("tx_drain[BLE]: retry still busy, wait %dms", BLE_TX_RETRY_MS);
|
||||
k_work_schedule(&tx_drain_work, K_MSEC(BLE_TX_RETRY_MS));
|
||||
bt_conn_unref(conn);
|
||||
return;
|
||||
} else {
|
||||
ble_tx_in_progress = false;
|
||||
@@ -541,24 +624,41 @@ static void tx_drain_work_fn(struct k_work *work)
|
||||
|
||||
/* Get next frame from queue */
|
||||
if (k_msgq_get(&ble_send_queue, &f, K_NO_WAIT) != 0) {
|
||||
/* TX queue empty - signal idle */
|
||||
/* TX queue empty — clear congestion and signal idle */
|
||||
if (ble_tx_congested) {
|
||||
ble_tx_congested = false;
|
||||
LOG_INF("tx_drain: congestion cleared (queue empty)");
|
||||
}
|
||||
if (ble_cbs && ble_cbs->on_tx_idle) {
|
||||
ble_cbs->on_tx_idle();
|
||||
}
|
||||
bt_conn_unref(conn);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Clear congestion at low water mark (1/3 of queue) — gives headroom
|
||||
* before hitting full again. Hysteresis: ON at full, OFF at 1/3. */
|
||||
if (ble_tx_congested) {
|
||||
uint32_t used = k_msgq_num_used_get(&ble_send_queue);
|
||||
if (used <= FRAME_QUEUE_SIZE / 3) {
|
||||
ble_tx_congested = false;
|
||||
LOG_INF("tx_drain: congestion cleared (queue=%u/%u)",
|
||||
used, (unsigned)FRAME_QUEUE_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
LOG_DBG("tx_drain[BLE]: sending len=%u hdr=0x%02x queue=%u",
|
||||
(unsigned)f.len, f.buf[0], k_msgq_num_used_get(&ble_send_queue));
|
||||
|
||||
/* Mark TX in progress before calling notify_cb */
|
||||
ble_tx_in_progress = true;
|
||||
ble_tx_start_time = k_uptime_get();
|
||||
err = secure_nus_send(current_conn, f.buf, f.len);
|
||||
err = secure_nus_send(conn, f.buf, f.len);
|
||||
|
||||
if (err == 0) {
|
||||
/* Success - callback will chain to next */
|
||||
LOG_DBG("tx_drain[BLE]: queued for TX");
|
||||
bt_conn_unref(conn);
|
||||
return;
|
||||
} else if (err == -EAGAIN || err == -ENOMEM) {
|
||||
/* BLE buffer full - save for retry */
|
||||
@@ -567,11 +667,13 @@ static void tx_drain_work_fn(struct k_work *work)
|
||||
tx_retry_pending = true;
|
||||
LOG_DBG("tx_drain[BLE]: BLE busy (err=%d), saved for retry", err);
|
||||
k_work_schedule(&tx_drain_work, K_MSEC(BLE_TX_RETRY_MS));
|
||||
bt_conn_unref(conn);
|
||||
return;
|
||||
} else {
|
||||
/* Other error - drop frame */
|
||||
ble_tx_in_progress = false;
|
||||
LOG_WRN("tx_drain[BLE]: send failed err=%d, dropped frame", err);
|
||||
bt_conn_unref(conn);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -677,6 +779,13 @@ static void adv_slow_work_fn(struct k_work *work)
|
||||
void zephcore_ble_init(const struct ble_callbacks *cbs)
|
||||
{
|
||||
ble_cbs = cbs;
|
||||
|
||||
/* Resolve NUS TX characteristic attribute once — avoids hard-coded
|
||||
* array offset in secure_nus_send(). attrs[2] = TX char value
|
||||
* (attrs[0]=service, attrs[1]=TX char decl, attrs[2]=TX char value,
|
||||
* attrs[3]=CCC, attrs[4]=RX char decl, attrs[5]=RX char value). */
|
||||
nus_tx_attr = &secure_nus_svc.attrs[2];
|
||||
|
||||
bt_conn_auth_cb_register(&auth_cb);
|
||||
bt_conn_auth_info_cb_register(&auth_info_cb);
|
||||
}
|
||||
@@ -716,8 +825,36 @@ size_t zephcore_ble_send(const uint8_t *data, uint16_t len)
|
||||
memcpy(f.buf, data, len);
|
||||
|
||||
if (k_msgq_put(&ble_send_queue, &f, K_NO_WAIT) != 0) {
|
||||
LOG_WRN("queue full!");
|
||||
return 0;
|
||||
/* Queue full — enter congestion mode.
|
||||
*
|
||||
* Instead of blocking (would stall LoRa) or dropping (loses
|
||||
* frames), we signal congestion so all senders stop, then
|
||||
* save this frame and retry at a reduced 250ms cadence until
|
||||
* the queue drains or the connection drops.
|
||||
*
|
||||
* Callers check zephcore_ble_is_congested() and hold off:
|
||||
* - contact_iter_work: pauses iteration
|
||||
* - main thread (sendPush): pushes are best-effort signals,
|
||||
* actual message data is safe in the offline queue
|
||||
*/
|
||||
if (!ble_tx_congested) {
|
||||
LOG_WRN("TX queue full (%u/%u), entering congestion",
|
||||
k_msgq_num_used_get(&ble_send_queue),
|
||||
(unsigned)FRAME_QUEUE_SIZE);
|
||||
ble_tx_congested = true;
|
||||
}
|
||||
|
||||
/* Save to overflow — retried at 250ms intervals.
|
||||
* If overflow already pending, replace with newest frame
|
||||
* (push notifications are idempotent MSG_WAITING signals). */
|
||||
if (overflow_pending) {
|
||||
LOG_DBG("overflow replaced: 0x%02x → 0x%02x",
|
||||
overflow_frame.buf[0], data[0]);
|
||||
}
|
||||
overflow_frame = f;
|
||||
overflow_pending = true;
|
||||
k_work_schedule(&overflow_retry_work, K_MSEC(BLE_TX_OVERFLOW_RETRY_MS));
|
||||
return len; /* Accepted into overflow — will be retried */
|
||||
}
|
||||
|
||||
LOG_DBG("queued len=%u hdr=0x%02x queue=%u",
|
||||
@@ -759,6 +896,11 @@ bool zephcore_ble_is_connected(void)
|
||||
return current_conn != NULL;
|
||||
}
|
||||
|
||||
bool zephcore_ble_is_congested(void)
|
||||
{
|
||||
return ble_tx_congested;
|
||||
}
|
||||
|
||||
void zephcore_ble_set_passkey(uint32_t passkey)
|
||||
{
|
||||
if (passkey >= 100000 && passkey <= 999999) {
|
||||
|
||||
@@ -67,6 +67,12 @@ bool zephcore_ble_is_active(void);
|
||||
*/
|
||||
bool zephcore_ble_is_connected(void);
|
||||
|
||||
/**
|
||||
* Check if BLE TX is congested (queue full, overflow retrying).
|
||||
* Callers should stop sending until this clears.
|
||||
*/
|
||||
bool zephcore_ble_is_congested(void);
|
||||
|
||||
/**
|
||||
* Set the BLE passkey at runtime.
|
||||
*/
|
||||
|
||||
@@ -229,8 +229,7 @@ static bool configParamsEqual(const struct lora_modem_config &a,
|
||||
const struct lora_modem_config &b)
|
||||
{
|
||||
/* CRITICAL: a.tx == b.tx MUST be compared — without it, switching
|
||||
* RX→TX skips RadioSetTxConfig(), leaving TxTimeout=0 which causes
|
||||
* the loramac-node software timeout to fire after 1ms and break TX. */
|
||||
* RX→TX skips lora_config() for TX params, breaking transmit. */
|
||||
return a.frequency == b.frequency &&
|
||||
a.bandwidth == b.bandwidth &&
|
||||
a.datarate == b.datarate &&
|
||||
@@ -242,6 +241,25 @@ static bool configParamsEqual(const struct lora_modem_config &a,
|
||||
a.public_network == b.public_network;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if only the TX/RX direction changed (all radio params identical).
|
||||
* Used to skip the full lora_config() call on TX↔RX transitions when
|
||||
* the driver already has valid TX and RX configs from previous calls.
|
||||
*/
|
||||
static bool onlyDirectionDiffers(const struct lora_modem_config &a,
|
||||
const struct lora_modem_config &b)
|
||||
{
|
||||
return a.frequency == b.frequency &&
|
||||
a.bandwidth == b.bandwidth &&
|
||||
a.datarate == b.datarate &&
|
||||
a.coding_rate == b.coding_rate &&
|
||||
a.preamble_len == b.preamble_len &&
|
||||
a.tx_power == b.tx_power &&
|
||||
a.iq_inverted == b.iq_inverted &&
|
||||
a.public_network == b.public_network &&
|
||||
a.tx != b.tx;
|
||||
}
|
||||
|
||||
void LoRaRadioBase::configureRx()
|
||||
{
|
||||
struct lora_modem_config cfg;
|
||||
@@ -252,6 +270,18 @@ void LoRaRadioBase::configureRx()
|
||||
return;
|
||||
}
|
||||
|
||||
/* Fast path: if only the TX/RX direction changed, skip the full
|
||||
* hwConfigure → lora_config() call. The driver already has a valid
|
||||
* RX config (RadioSetRxConfig) from a previous cycle — Radio.Rx(0)
|
||||
* in hwStartReceive() will use those register values directly.
|
||||
* This avoids the modem_acquire → modem_release → Radio.Sleep()
|
||||
* round-trip that wastes ~5 ms on every TX→RX transition. */
|
||||
if (_config_cached && onlyDirectionDiffers(cfg, _last_cfg)) {
|
||||
LOG_DBG("configureRx: direction-only change, skip hwConfigure");
|
||||
_last_cfg = cfg;
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_DBG("configureRx: freq=%u bw=%d sf=%d cr=%d pwr=%d",
|
||||
cfg.frequency, (int)cfg.bandwidth, (int)cfg.datarate,
|
||||
(int)cfg.coding_rate, cfg.tx_power);
|
||||
@@ -271,6 +301,15 @@ void LoRaRadioBase::configureTx()
|
||||
return;
|
||||
}
|
||||
|
||||
/* Fast path: direction-only change (RX→TX). The driver already
|
||||
* has a valid TX config (RadioSetTxConfig with TxTimeout=4000)
|
||||
* from a previous cycle — Radio.Send() will use those values. */
|
||||
if (_config_cached && onlyDirectionDiffers(cfg, _last_cfg)) {
|
||||
LOG_DBG("configureTx: direction-only change, skip hwConfigure");
|
||||
_last_cfg = cfg;
|
||||
return;
|
||||
}
|
||||
|
||||
hwConfigure(cfg);
|
||||
_last_cfg = cfg;
|
||||
_config_cached = true;
|
||||
|
||||
@@ -1,93 +1,21 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* SX126x hardware hooks for LoRaRadioBase.
|
||||
* SX126x hardware hooks for LoRaRadioBase — native Zephyr driver.
|
||||
*/
|
||||
|
||||
#include "SX126xRadio.h"
|
||||
#include <mesh/LoRaConfig.h>
|
||||
#include <zephyr/kernel.h>
|
||||
|
||||
/*
|
||||
* Access loramac-node SX126x functions for advanced features.
|
||||
* These are defined in the Zephyr LoRa driver (sx126x.c/radio.c).
|
||||
*/
|
||||
/* Native SX126x driver extension API */
|
||||
extern "C" {
|
||||
uint16_t SX126xGetIrqStatus(void);
|
||||
int8_t SX126xGetRssiInst(void);
|
||||
uint32_t SX126xGetRandom(void);
|
||||
|
||||
#define RADIO_CALIBRATEIMAGE 0x98
|
||||
void SX126xWriteCommand(uint8_t opcode, uint8_t *buffer, uint16_t size);
|
||||
|
||||
/* Radio struct for direct access to SetRxDutyCycle (SX126x only) */
|
||||
struct LoRaMacRadio_s {
|
||||
void (*Init)(void *events);
|
||||
int (*GetStatus)(void);
|
||||
void (*SetModem)(int modem);
|
||||
void (*SetChannel)(uint32_t freq);
|
||||
bool (*IsChannelFree)(uint32_t freq, uint32_t rxBandwidth, int16_t rssiThresh, uint32_t maxCarrierSenseTime);
|
||||
uint32_t (*Random)(void);
|
||||
void (*SetRxConfig)(int modem, uint32_t bandwidth, uint32_t datarate, uint8_t coderate,
|
||||
uint32_t bandwidthAfc, uint16_t preambleLen, uint16_t symbTimeout,
|
||||
bool fixLen, uint8_t payloadLen, bool crcOn, bool freqHopOn,
|
||||
uint8_t hopPeriod, bool iqInverted, bool rxContinuous);
|
||||
void (*SetTxConfig)(int modem, int8_t power, uint32_t fdev, uint32_t bandwidth,
|
||||
uint32_t datarate, uint8_t coderate, uint16_t preambleLen,
|
||||
bool fixLen, bool crcOn, bool freqHopOn, uint8_t hopPeriod,
|
||||
bool iqInverted, uint32_t timeout);
|
||||
bool (*CheckRfFrequency)(uint32_t frequency);
|
||||
uint32_t (*TimeOnAir)(int modem, uint32_t bandwidth, uint32_t datarate, uint8_t coderate,
|
||||
uint16_t preambleLen, bool fixLen, uint8_t payloadLen, bool crcOn);
|
||||
void (*Send)(uint8_t *buffer, uint8_t size);
|
||||
void (*Sleep)(void);
|
||||
void (*Standby)(void);
|
||||
void (*Rx)(uint32_t timeout);
|
||||
void (*StartCad)(void);
|
||||
void (*SetTxContinuousWave)(uint32_t freq, int8_t power, uint16_t time);
|
||||
int16_t (*Rssi)(int modem);
|
||||
void (*Write)(uint32_t addr, uint8_t data);
|
||||
uint8_t (*Read)(uint32_t addr);
|
||||
void (*WriteBuffer)(uint32_t addr, uint8_t *buffer, uint8_t size);
|
||||
void (*ReadBuffer)(uint32_t addr, uint8_t *buffer, uint8_t size);
|
||||
void (*SetMaxPayloadLength)(int modem, uint8_t max);
|
||||
void (*SetPublicNetwork)(bool enable);
|
||||
uint32_t (*GetWakeupTime)(void);
|
||||
void (*IrqProcess)(void);
|
||||
void (*RxBoosted)(uint32_t timeout);
|
||||
void (*SetRxDutyCycle)(uint32_t rxTime, uint32_t sleepTime);
|
||||
};
|
||||
extern const struct LoRaMacRadio_s Radio __asm__("Radio");
|
||||
#include "sx126x_ext.h"
|
||||
}
|
||||
|
||||
/* IRQ status bits */
|
||||
static const uint16_t kIrqHeaderValid = 0x0010;
|
||||
static const uint16_t kIrqPreambleDetected = 0x0004;
|
||||
|
||||
/* RX gain register */
|
||||
#define REG_RX_GAIN 0x08AC
|
||||
#define RX_GAIN_POWER_SAVE 0x94
|
||||
#define RX_GAIN_BOOSTED 0x96
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_REGISTER(zephcore_lora, CONFIG_ZEPHCORE_LORA_LOG_LEVEL);
|
||||
|
||||
namespace mesh {
|
||||
|
||||
/**
|
||||
* SX126x-specific: Calibrate image rejection for a minimal 4 MHz range.
|
||||
*/
|
||||
static void calibrateImageMinimal(uint32_t freq_hz)
|
||||
{
|
||||
uint16_t freq_mhz = freq_hz / 1000000;
|
||||
uint8_t cal_low = (uint8_t)(freq_mhz / 4);
|
||||
uint8_t cal_high = cal_low + 1;
|
||||
|
||||
uint8_t calFreq[2] = { cal_low, cal_high };
|
||||
SX126xWriteCommand(RADIO_CALIBRATEIMAGE, calFreq, 2);
|
||||
|
||||
LOG_DBG("SX126x image calibration: %u-%u MHz", cal_low * 4, cal_high * 4);
|
||||
}
|
||||
|
||||
K_THREAD_STACK_DEFINE(sx126x_tx_wait_stack, TX_WAIT_THREAD_STACK_SIZE);
|
||||
|
||||
SX126xRadio::SX126xRadio(const struct device *lora_dev, MainBoard &board,
|
||||
@@ -110,8 +38,6 @@ void SX126xRadio::hwConfigure(const struct lora_modem_config &cfg)
|
||||
int ret = lora_config(_dev, const_cast<struct lora_modem_config *>(&cfg));
|
||||
if (ret < 0) {
|
||||
LOG_ERR("lora_config failed: %d", ret);
|
||||
} else {
|
||||
calibrateImageMinimal(cfg.frequency);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,9 +52,11 @@ void SX126xRadio::hwStartReceive()
|
||||
_in_recv_mode = true;
|
||||
|
||||
if (_rx_boost_enabled) {
|
||||
::Radio.Write(REG_RX_GAIN, RX_GAIN_BOOSTED);
|
||||
sx126x_set_rx_boost(_dev, true);
|
||||
}
|
||||
if (_rx_duty_cycle_enabled) {
|
||||
sx126x_set_rx_duty_cycle(_dev, true);
|
||||
}
|
||||
applyRxDutyCycleIfEnabled();
|
||||
}
|
||||
|
||||
void SX126xRadio::hwCancelReceive()
|
||||
@@ -144,30 +72,22 @@ int SX126xRadio::hwSendAsync(uint8_t *buf, uint32_t len,
|
||||
|
||||
int16_t SX126xRadio::hwGetCurrentRSSI()
|
||||
{
|
||||
return SX126xGetRssiInst();
|
||||
return sx126x_get_rssi_inst(_dev);
|
||||
}
|
||||
|
||||
bool SX126xRadio::hwIsPreambleDetected()
|
||||
{
|
||||
uint16_t irq = SX126xGetIrqStatus();
|
||||
return (irq & kIrqHeaderValid) || (irq & kIrqPreambleDetected);
|
||||
return sx126x_is_receiving(_dev);
|
||||
}
|
||||
|
||||
void SX126xRadio::hwSetRxBoost(bool enable)
|
||||
{
|
||||
::Radio.Write(REG_RX_GAIN, enable ? RX_GAIN_BOOSTED : RX_GAIN_POWER_SAVE);
|
||||
sx126x_set_rx_boost(_dev, enable);
|
||||
}
|
||||
|
||||
void SX126xRadio::hwSetRxDutyCycle(bool enable)
|
||||
{
|
||||
if (enable) {
|
||||
applyRxDutyCycleIfEnabled();
|
||||
} else {
|
||||
::Radio.Rx(0);
|
||||
if (_rx_boost_enabled) {
|
||||
::Radio.Write(REG_RX_GAIN, RX_GAIN_BOOSTED);
|
||||
}
|
||||
}
|
||||
sx126x_set_rx_duty_cycle(_dev, enable);
|
||||
}
|
||||
|
||||
void SX126xRadio::hwResetAGC()
|
||||
@@ -178,67 +98,4 @@ void SX126xRadio::hwResetAGC()
|
||||
}
|
||||
}
|
||||
|
||||
/* ── SX126x RX Duty Cycle — RadioLib Algorithm ───────────────────────── */
|
||||
|
||||
void SX126xRadio::applyRxDutyCycleIfEnabled()
|
||||
{
|
||||
if (!_rx_duty_cycle_enabled || !_in_recv_mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t sf = _prefs ? _prefs->sf : LoRaConfig::SPREADING_FACTOR;
|
||||
float bw_khz = _prefs ? _prefs->bw : (float)LoRaConfig::BANDWIDTH;
|
||||
uint16_t preamble_len = LoRaConfig::PREAMBLE_LEN;
|
||||
|
||||
uint16_t min_symbols = (sf >= 7) ? RADIOLIB_MIN_SYMBOLS_SF7_PLUS
|
||||
: RADIOLIB_MIN_SYMBOLS_SF6_LESS;
|
||||
|
||||
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);
|
||||
_rx_duty_cycle_enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t symbol_us = (uint32_t)((float)(1 << sf) * 1000.0f / bw_khz);
|
||||
|
||||
/* Shave 2 symbols off the sleep period so the wake window always overlaps
|
||||
with enough preamble for detection, even in the worst-case arrival timing.
|
||||
Without this margin the math lands at exactly zero — any TCXO drift,
|
||||
crystal error, or processing latency causes missed packets. */
|
||||
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;
|
||||
|
||||
uint32_t preamble_total_us = (preamble_len + 1) * symbol_us;
|
||||
int32_t wake_calc1 = ((int32_t)preamble_total_us -
|
||||
((int32_t)sleep_period_us - RADIOLIB_TCXO_DELAY_US)) / 2;
|
||||
uint32_t wake_calc2 = (min_symbols + 1) * symbol_us;
|
||||
|
||||
uint32_t wake_period_us = (wake_calc1 > 0 && (uint32_t)wake_calc1 > wake_calc2)
|
||||
? (uint32_t)wake_calc1 : wake_calc2;
|
||||
|
||||
/* SetRxDutyCycle takes times in 15.625us steps */
|
||||
uint32_t rx_time = (wake_period_us * 64) / 1000;
|
||||
uint32_t sleep_time = (sleep_period_us * 64) / 1000;
|
||||
|
||||
if (rx_time < 64) rx_time = 64;
|
||||
if (sleep_time < 64) sleep_time = 64;
|
||||
|
||||
::Radio.SetRxDutyCycle(rx_time, sleep_time);
|
||||
|
||||
uint32_t rx_ms = (rx_time * 1000) / 64000;
|
||||
uint32_t sleep_ms = (sleep_time * 1000) / 64000;
|
||||
uint32_t total_ms = rx_ms + sleep_ms;
|
||||
uint32_t preamble_ms = (preamble_len * symbol_us) / 1000;
|
||||
|
||||
uint32_t bw_int = (uint32_t)bw_khz;
|
||||
uint32_t bw_frac = (uint32_t)((bw_khz - bw_int) * 10);
|
||||
LOG_DBG("RX duty cycle: SF%d BW%u.%u sym=%uus preamble=%ums",
|
||||
sf, bw_int, bw_frac, symbol_us, preamble_ms);
|
||||
LOG_DBG(" -> rx=%ums sleep=%ums period=%ums (minSym=%d sleepSym=%d)",
|
||||
rx_ms, sleep_ms, total_ms, min_symbols, sleep_symbols);
|
||||
}
|
||||
|
||||
} /* namespace mesh */
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* ZephCore Radio adapter for SX126x (SX1261/SX1262/SX1268) using Zephyr LoRa driver
|
||||
* ZephCore Radio adapter for SX126x (SX1261/SX1262/SX1268) using native Zephyr driver
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "LoRaRadioBase.h"
|
||||
|
||||
/* SX126x-specific: RX Duty Cycle power saving - uses RadioLib algorithm */
|
||||
#define RADIOLIB_MIN_SYMBOLS_SF7_PLUS 8
|
||||
#define RADIOLIB_MIN_SYMBOLS_SF6_LESS 12
|
||||
#define RADIOLIB_TCXO_DELAY_US 1000 /* ~1ms startup overhead */
|
||||
|
||||
namespace mesh {
|
||||
|
||||
class SX126xRadio : public LoRaRadioBase {
|
||||
@@ -33,10 +28,6 @@ protected:
|
||||
void hwSetRxBoost(bool enable) override;
|
||||
void hwSetRxDutyCycle(bool enable) override;
|
||||
void hwResetAGC() override;
|
||||
|
||||
private:
|
||||
/* SX126x-specific: RadioLib duty cycle timing algorithm */
|
||||
void applyRxDutyCycleIfEnabled();
|
||||
};
|
||||
|
||||
} /* namespace mesh */
|
||||
|
||||
@@ -17,11 +17,9 @@
|
||||
#define DEFAULT_NOISE_FLOOR 0 /* first calibration accepts all samples */
|
||||
|
||||
/* --- RX ring buffer ---
|
||||
* Sized to be effectively drop-proof: even at the fastest LoRa settings
|
||||
* (SF7/BW500, ~5ms per packet), 32 slots buffer 160ms+ of back-to-back
|
||||
* arrivals. The main loop drain takes microseconds per packet, so
|
||||
* overflow should never occur in practice. Cost: 32 × 260 = ~8.3 KB. */
|
||||
#define RX_RING_SIZE 32
|
||||
* 8 slots buffer ~40ms+ of back-to-back arrivals at SF7/BW500.
|
||||
* Main loop drains in microseconds per packet. Cost: 8 × 260 = ~2 KB. */
|
||||
#define RX_RING_SIZE 8
|
||||
|
||||
/* --- TX wait thread --- */
|
||||
#define TX_WAIT_THREAD_STACK_SIZE 1024
|
||||
|
||||
@@ -35,6 +35,7 @@ CONFIG_REBOOT=y
|
||||
|
||||
# ========== LoRa ==========
|
||||
CONFIG_LORA=y
|
||||
CONFIG_LORA_MODULE_BACKEND_NATIVE=y
|
||||
CONFIG_REGULATOR=y
|
||||
|
||||
# ========== Display Subsystem ==========
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
diff --git a/src/radio/sx126x/radio.c b/src/radio/sx126x/radio.c
|
||||
index c4a40dab..bf6b4c9b 100644
|
||||
--- a/src/radio/sx126x/radio.c
|
||||
+++ b/src/radio/sx126x/radio.c
|
||||
@@ -421,7 +421,26 @@ const FskBandwidth_t FskBandwidths[] =
|
||||
{ 500000, 0x00 }, // Invalid Bandwidth
|
||||
};
|
||||
|
||||
-const RadioLoRaBandwidths_t Bandwidths[] = { LORA_BW_125, LORA_BW_250, LORA_BW_500 };
|
||||
+/*
|
||||
+ * ZephCore patch: restore full 10-element Bandwidths array.
|
||||
+ * Upstream Zephyr reduced this to {125, 250, 500} but the Zephyr LoRa API
|
||||
+ * now defines enums for all bandwidths (BW_7_KHZ through BW_500_KHZ).
|
||||
+ * sx12xx_common.c maps these enums to indices into this array, so all 10
|
||||
+ * entries must be present to avoid out-of-bounds access.
|
||||
+ */
|
||||
+const RadioLoRaBandwidths_t Bandwidths[] =
|
||||
+{
|
||||
+ LORA_BW_007,
|
||||
+ LORA_BW_010,
|
||||
+ LORA_BW_015,
|
||||
+ LORA_BW_020,
|
||||
+ LORA_BW_031,
|
||||
+ LORA_BW_041,
|
||||
+ LORA_BW_062,
|
||||
+ LORA_BW_125,
|
||||
+ LORA_BW_250,
|
||||
+ LORA_BW_500,
|
||||
+};
|
||||
|
||||
uint8_t MaxPayloadLength = 0xFF;
|
||||
|
||||
@@ -702,8 +721,14 @@ void RadioSetRxConfig( RadioModems_t modem, uint32_t bandwidth,
|
||||
SX126x.ModulationParams.Params.LoRa.Bandwidth = Bandwidths[bandwidth];
|
||||
SX126x.ModulationParams.Params.LoRa.CodingRate = ( RadioLoRaCodingRates_t )coderate;
|
||||
|
||||
- if( ( ( bandwidth == 0 ) && ( ( datarate == 11 ) || ( datarate == 12 ) ) ) ||
|
||||
- ( ( bandwidth == 1 ) && ( datarate == 12 ) ) )
|
||||
+ /*
|
||||
+ * ZephCore patch: LDRO check updated for 10-element Bandwidths[].
|
||||
+ * Enable LDRO when symbol duration > 16ms.
|
||||
+ * Old indices (0=125k,1=250k) → new (7=125k,8=250k,6=62.5k, etc.)
|
||||
+ */
|
||||
+ if( ( ( bandwidth == 7 ) && ( ( datarate == 11 ) || ( datarate == 12 ) ) ) ||
|
||||
+ ( ( bandwidth == 8 ) && ( datarate == 12 ) ) ||
|
||||
+ ( ( bandwidth <= 6 ) && ( datarate >= 10 ) ) )
|
||||
{
|
||||
SX126x.ModulationParams.Params.LoRa.LowDatarateOptimize = 0x01;
|
||||
}
|
||||
@@ -809,8 +834,10 @@ void RadioSetTxConfig( RadioModems_t modem, int8_t power, uint32_t fdev,
|
||||
SX126x.ModulationParams.Params.LoRa.Bandwidth = Bandwidths[bandwidth];
|
||||
SX126x.ModulationParams.Params.LoRa.CodingRate= ( RadioLoRaCodingRates_t )coderate;
|
||||
|
||||
- if( ( ( bandwidth == 0 ) && ( ( datarate == 11 ) || ( datarate == 12 ) ) ) ||
|
||||
- ( ( bandwidth == 1 ) && ( datarate == 12 ) ) )
|
||||
+ /* ZephCore patch: LDRO check updated for 10-element Bandwidths[] */
|
||||
+ if( ( ( bandwidth == 7 ) && ( ( datarate == 11 ) || ( datarate == 12 ) ) ) ||
|
||||
+ ( ( bandwidth == 8 ) && ( datarate == 12 ) ) ||
|
||||
+ ( ( bandwidth <= 6 ) && ( datarate >= 10 ) ) )
|
||||
{
|
||||
SX126x.ModulationParams.Params.LoRa.LowDatarateOptimize = 0x01;
|
||||
}
|
||||
@@ -946,8 +973,10 @@ static uint32_t RadioGetLoRaTimeOnAirNumerator( uint32_t bandwidth,
|
||||
}
|
||||
}
|
||||
|
||||
- if( ( ( bandwidth == 0 ) && ( ( datarate == 11 ) || ( datarate == 12 ) ) ) ||
|
||||
- ( ( bandwidth == 1 ) && ( datarate == 12 ) ) )
|
||||
+ /* ZephCore patch: LDRO check updated for 10-element Bandwidths[] */
|
||||
+ if( ( ( bandwidth == 7 ) && ( ( datarate == 11 ) || ( datarate == 12 ) ) ) ||
|
||||
+ ( ( bandwidth == 8 ) && ( datarate == 12 ) ) ||
|
||||
+ ( ( bandwidth <= 6 ) && ( datarate >= 10 ) ) )
|
||||
{
|
||||
lowDatareOptimize = true;
|
||||
}
|
||||
@@ -27,6 +27,11 @@
|
||||
|
||||
LOG_MODULE_REGISTER(lr11xx_lora, CONFIG_LORA_LOG_LEVEL);
|
||||
|
||||
/* Dedicated DIO1 work queue — keeps LoRa interrupt processing off the
|
||||
* system work queue so USB/BLE/timer work items cannot delay packet RX. */
|
||||
#define LR11XX_DIO1_WQ_STACK_SIZE 1536
|
||||
K_THREAD_STACK_DEFINE(lr11xx_dio1_wq_stack, LR11XX_DIO1_WQ_STACK_SIZE);
|
||||
|
||||
/* ── Driver data structures ─────────────────────────────────────────── */
|
||||
|
||||
struct lr11xx_config {
|
||||
@@ -65,8 +70,9 @@ struct lr11xx_data {
|
||||
/* Async TX state */
|
||||
struct k_poll_signal *tx_signal;
|
||||
|
||||
/* DIO1 work queue */
|
||||
/* DIO1 work — runs on dedicated queue, not system work queue */
|
||||
struct k_work dio1_work;
|
||||
struct k_work_q dio1_wq;
|
||||
|
||||
/* Radio state */
|
||||
volatile bool tx_active;
|
||||
@@ -175,9 +181,9 @@ static void lr11xx_hardware_reset(struct lr11xx_data *data,
|
||||
|
||||
lr11xx_system_calibrate(ctx, 0x3F);
|
||||
|
||||
/* Tight ±2 MHz image calibration around actual frequency */
|
||||
uint16_t freq_mhz = data->modem_cfg.frequency / 1000000;
|
||||
uint16_t cal_low = (freq_mhz / 4) * 4;
|
||||
lr11xx_system_calibrate_image_in_mhz(ctx, cal_low, cal_low + 4);
|
||||
lr11xx_system_calibrate_image_in_mhz(ctx, freq_mhz - 2, freq_mhz + 2);
|
||||
|
||||
lr11xx_radio_set_pkt_type(ctx, LR11XX_RADIO_PKT_TYPE_LORA);
|
||||
|
||||
@@ -197,11 +203,16 @@ static void lr11xx_apply_modem_config(struct lr11xx_data *data,
|
||||
|
||||
lr11xx_radio_set_rf_freq(ctx, mc->frequency);
|
||||
|
||||
/* LDRO must be enabled when symbol time > 16.38ms (SF11+/BW125 etc) */
|
||||
uint32_t bw_hz = (uint32_t)(bw_enum_to_khz(mc->bandwidth) * 1000.0f);
|
||||
uint32_t symbol_time_us = ((1U << (uint8_t)mc->datarate) * 1000000U) / bw_hz;
|
||||
uint8_t ldro = (symbol_time_us > 16380) ? 1 : 0;
|
||||
|
||||
lr11xx_radio_mod_params_lora_t mod = {
|
||||
.sf = (lr11xx_radio_lora_sf_t)mc->datarate,
|
||||
.bw = bw_enum_to_lr11xx(mc->bandwidth),
|
||||
.cr = cr_enum_to_lr11xx(mc->coding_rate),
|
||||
.ldro = 0,
|
||||
.ldro = ldro,
|
||||
};
|
||||
lr11xx_radio_set_lora_mod_params(ctx, &mod);
|
||||
|
||||
@@ -238,7 +249,35 @@ static void lr11xx_apply_modem_config(struct lr11xx_data *data,
|
||||
0);
|
||||
}
|
||||
|
||||
/* ── RX duty cycle (RadioLib algorithm) ─────────────────────────────── */
|
||||
/* ── CAD det_peak lookup (from Semtech ral_lr11xx reference) ─────────── */
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
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. */
|
||||
|
||||
static void lr11xx_apply_rx_duty_cycle(struct lr11xx_data *data)
|
||||
{
|
||||
@@ -248,43 +287,56 @@ static void lr11xx_apply_rx_duty_cycle(struct lr11xx_data *data)
|
||||
uint8_t sf = (uint8_t)mc->datarate;
|
||||
float bw_khz = bw_enum_to_khz(mc->bandwidth);
|
||||
uint16_t preamble_len = mc->preamble_len;
|
||||
uint16_t min_symbols = (sf >= 7) ? 8 : 12;
|
||||
|
||||
int16_t sleep_symbols = (int16_t)preamble_len - (int16_t)min_symbols;
|
||||
if (sleep_symbols <= 0) {
|
||||
LOG_WRN("Preamble too short for duty cycle, using continuous RX");
|
||||
/* 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;
|
||||
|
||||
if (sleep_symbols < 2) {
|
||||
LOG_WRN("Preamble too short for CAD duty cycle (%d sym), "
|
||||
"continuous RX", preamble_len);
|
||||
data->rx_duty_cycle_enabled = false;
|
||||
lr11xx_radio_set_rx(ctx, 0xFFFFFF);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t symbol_us = (uint32_t)((float)(1 << sf) * 1000.0f / bw_khz);
|
||||
/* 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);
|
||||
|
||||
/* Shave 2 symbols off sleep so the wake window always has enough
|
||||
preamble overlap for detection. Without this the margin is zero
|
||||
and any TCXO drift or crystal error causes missed packets. */
|
||||
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;
|
||||
uint32_t preamble_total_us = (preamble_len + 1) * symbol_us;
|
||||
int32_t wake_calc1 = ((int32_t)preamble_total_us -
|
||||
((int32_t)sleep_period_us - 1000)) / 2;
|
||||
uint32_t wake_calc2 = (min_symbols + 1) * symbol_us;
|
||||
uint32_t wake_period_us = (wake_calc1 > 0 &&
|
||||
(uint32_t)wake_calc1 > wake_calc2)
|
||||
? (uint32_t)wake_calc1 : wake_calc2;
|
||||
uint32_t sleep_us = (uint16_t)sleep_symbols * symbol_us;
|
||||
|
||||
/* LR1110 API takes milliseconds */
|
||||
uint32_t rx_ms = (wake_period_us + 500) / 1000;
|
||||
uint32_t sleep_ms = (sleep_period_us + 500) / 1000;
|
||||
/* 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 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;
|
||||
|
||||
lr11xx_radio_set_rx_duty_cycle(ctx, rx_ms, sleep_ms,
|
||||
LR11XX_RADIO_RX_DUTY_CYCLE_MODE_RX);
|
||||
LR11XX_RADIO_RX_DUTY_CYCLE_MODE_CAD);
|
||||
|
||||
LOG_INF("RX duty cycle: rx=%ums sleep=%ums (SF%d BW%.0f)",
|
||||
rx_ms, sleep_ms, sf, (double)bw_khz);
|
||||
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);
|
||||
}
|
||||
|
||||
/* ── Start RX (internal) ────────────────────────────────────────────── */
|
||||
@@ -327,6 +379,49 @@ static void lr11xx_start_rx(struct lr11xx_data *data,
|
||||
data->tx_active = false;
|
||||
}
|
||||
|
||||
/* ── Lightweight RX restart (no modem reconfig) ─────────────────────── */
|
||||
|
||||
/* Used after RX done / CRC error / timeout — frequency/modulation unchanged,
|
||||
* skip most of lr11xx_apply_modem_config.
|
||||
* Full lr11xx_start_rx() kept for initial start and TX→RX.
|
||||
*
|
||||
* Packet params (preamble, pld_len=255) MUST be re-set on every restart:
|
||||
* the LR1110 can silently corrupt these registers after CRC/header errors
|
||||
* and through CAD→RX transitions, causing missed packets — especially when
|
||||
* small and large packets are interleaved on the mesh. Arduino MeshCore
|
||||
* applies the same workaround (CustomLR1110Wrapper::onSendFinished). */
|
||||
static void lr11xx_restart_rx(struct lr11xx_data *data)
|
||||
{
|
||||
void *ctx = &data->hal_ctx;
|
||||
struct lora_modem_config *mc = &data->modem_cfg;
|
||||
|
||||
lr11xx_system_clear_irq_status(ctx, LR11XX_SYSTEM_IRQ_ALL_MASK);
|
||||
|
||||
/* Re-apply packet params — preamble + pld_len=255 for RX */
|
||||
lr11xx_radio_pkt_params_lora_t pkt = {
|
||||
.preamble_len_in_symb = mc->preamble_len,
|
||||
.header_type = LR11XX_RADIO_LORA_PKT_EXPLICIT,
|
||||
.pld_len_in_bytes = 255,
|
||||
.crc = mc->packet_crc_disable ? LR11XX_RADIO_LORA_CRC_OFF
|
||||
: LR11XX_RADIO_LORA_CRC_ON,
|
||||
.iq = mc->iq_inverted ? LR11XX_RADIO_LORA_IQ_INVERTED
|
||||
: LR11XX_RADIO_LORA_IQ_STANDARD,
|
||||
};
|
||||
lr11xx_radio_set_lora_pkt_params(ctx, &pkt);
|
||||
|
||||
if (data->rx_duty_cycle_enabled) {
|
||||
lr11xx_apply_rx_duty_cycle(data);
|
||||
} else {
|
||||
lr11xx_radio_set_rx(ctx, 0xFFFFFF);
|
||||
}
|
||||
|
||||
if (data->rx_boost_enabled) {
|
||||
lr11xx_radio_cfg_rx_boosted(ctx, true);
|
||||
}
|
||||
|
||||
data->in_rx_mode = true;
|
||||
}
|
||||
|
||||
/* ── DIO1 IRQ handler (work queue, thread context) ──────────────────── */
|
||||
|
||||
static void lr11xx_dio1_callback(void *user_data);
|
||||
@@ -360,8 +455,18 @@ static void lr11xx_dio1_work_handler(struct k_work *work)
|
||||
rx_stat.buffer_start_pointer,
|
||||
rx_stat.pld_len_in_bytes);
|
||||
|
||||
/* Restart RX BEFORE callback (matches SX126x pattern) */
|
||||
lr11xx_start_rx(data, cfg);
|
||||
/* Lightweight RX restart — no reconfig needed */
|
||||
lr11xx_restart_rx(data);
|
||||
|
||||
/* When SNR < 0 the packet RSSI is dominated by
|
||||
* noise — use the signal-only RSSI estimate for a
|
||||
* more accurate reading on weak links. */
|
||||
int16_t rssi = pkt_stat.rssi_pkt_in_dbm;
|
||||
|
||||
if (pkt_stat.snr_pkt_in_db < 0 &&
|
||||
pkt_stat.signal_rssi_pkt_in_dbm > rssi) {
|
||||
rssi = pkt_stat.signal_rssi_pkt_in_dbm;
|
||||
}
|
||||
|
||||
k_mutex_unlock(&data->spi_mutex);
|
||||
|
||||
@@ -369,7 +474,7 @@ static void lr11xx_dio1_work_handler(struct k_work *work)
|
||||
if (data->async_rx_cb) {
|
||||
data->async_rx_cb(data->dev, data->rx_buf,
|
||||
rx_stat.pld_len_in_bytes,
|
||||
pkt_stat.rssi_pkt_in_dbm,
|
||||
rssi,
|
||||
pkt_stat.snr_pkt_in_db,
|
||||
data->async_rx_user_data);
|
||||
}
|
||||
@@ -377,7 +482,7 @@ static void lr11xx_dio1_work_handler(struct k_work *work)
|
||||
}
|
||||
|
||||
LOG_WRN("RX: invalid len %d", rx_stat.pld_len_in_bytes);
|
||||
lr11xx_start_rx(data, cfg);
|
||||
lr11xx_restart_rx(data);
|
||||
}
|
||||
|
||||
/* ── TX done ── */
|
||||
@@ -385,7 +490,7 @@ static void lr11xx_dio1_work_handler(struct k_work *work)
|
||||
LOG_INF("TX done");
|
||||
data->tx_active = false;
|
||||
|
||||
/* Restart RX */
|
||||
/* Full restart — modem was reconfigured for TX */
|
||||
lr11xx_start_rx(data, cfg);
|
||||
|
||||
/* Raise TX signal */
|
||||
@@ -396,10 +501,10 @@ static void lr11xx_dio1_work_handler(struct k_work *work)
|
||||
|
||||
/* ── Timeout ── */
|
||||
if (irq & LR11XX_SYSTEM_IRQ_TIMEOUT) {
|
||||
LOG_INF("Timeout IRQ — restarting RX (duty_cycle=%d)",
|
||||
LOG_DBG("Timeout IRQ — restarting RX (duty_cycle=%d)",
|
||||
data->rx_duty_cycle_enabled);
|
||||
if (!data->tx_active) {
|
||||
lr11xx_start_rx(data, cfg);
|
||||
lr11xx_restart_rx(data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -418,7 +523,7 @@ static void lr11xx_dio1_work_handler(struct k_work *work)
|
||||
}
|
||||
|
||||
if (!data->tx_active) {
|
||||
lr11xx_start_rx(data, cfg);
|
||||
lr11xx_restart_rx(data);
|
||||
}
|
||||
|
||||
k_mutex_unlock(&data->spi_mutex);
|
||||
@@ -438,7 +543,7 @@ static void lr11xx_dio1_work_handler(struct k_work *work)
|
||||
static void lr11xx_dio1_callback(void *user_data)
|
||||
{
|
||||
struct lr11xx_data *data = (struct lr11xx_data *)user_data;
|
||||
k_work_submit(&data->dio1_work);
|
||||
k_work_submit_to_queue(&data->dio1_wq, &data->dio1_work);
|
||||
}
|
||||
|
||||
/* Forward declaration — hw_init is defined after driver API functions */
|
||||
@@ -465,6 +570,13 @@ static int lr11xx_lora_config(const struct device *dev,
|
||||
memcpy(&data->modem_cfg, config, sizeof(*config));
|
||||
data->configured = true;
|
||||
|
||||
/* Tight ±2 MHz image calibration for the configured frequency */
|
||||
k_mutex_lock(&data->spi_mutex, K_FOREVER);
|
||||
uint16_t freq_mhz = config->frequency / 1000000;
|
||||
lr11xx_system_calibrate_image_in_mhz(&data->hal_ctx,
|
||||
freq_mhz - 2, freq_mhz + 2);
|
||||
k_mutex_unlock(&data->spi_mutex);
|
||||
|
||||
LOG_INF("config: %uHz SF%d BW%d CR%d pwr=%d tx=%d",
|
||||
config->frequency, config->datarate, config->bandwidth,
|
||||
config->coding_rate, config->tx_power, config->tx);
|
||||
@@ -797,6 +909,12 @@ static int lr11xx_lora_init(const struct device *dev)
|
||||
k_mutex_init(&data->spi_mutex);
|
||||
k_work_init(&data->dio1_work, lr11xx_dio1_work_handler);
|
||||
|
||||
/* Start dedicated DIO1 work queue at high priority */
|
||||
k_work_queue_start(&data->dio1_wq, lr11xx_dio1_wq_stack,
|
||||
K_THREAD_STACK_SIZEOF(lr11xx_dio1_wq_stack),
|
||||
K_PRIO_COOP(7), NULL);
|
||||
k_thread_name_set(&data->dio1_wq.thread, "lr11xx_dio1");
|
||||
|
||||
/* Check SPI bus */
|
||||
if (!spi_is_ready_dt(&cfg->bus)) {
|
||||
LOG_ERR("SPI bus not ready");
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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 duty cycle mode
|
||||
*
|
||||
* When enabled, the radio alternates between RX and sleep using the
|
||||
* RadioLib preamble detection algorithm. Saves ~60-70% RX current.
|
||||
*
|
||||
* @param dev LoRa device
|
||||
* @param enable true to enable, false for continuous RX
|
||||
*/
|
||||
void sx126x_set_rx_duty_cycle(const struct device *dev, bool enable);
|
||||
|
||||
/**
|
||||
* @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);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SX126X_EXT_H */
|
||||
@@ -1,173 +0,0 @@
|
||||
diff --git a/drivers/lora/loramac-node/sx12xx_common.c b/drivers/lora/loramac-node/sx12xx_common.c
|
||||
index 17689720dd2..85186893fc8 100644
|
||||
--- a/drivers/lora/loramac-node/sx12xx_common.c
|
||||
+++ b/drivers/lora/loramac-node/sx12xx_common.c
|
||||
@@ -3,6 +3,9 @@
|
||||
* Copyright (c) 2020 Grinn
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
+ *
|
||||
+ * ZEPHCORE PATCH: Modified sx12xx_ev_rx_error() to notify async callback
|
||||
+ * with NULL data on RX errors (CRC/header). This allows error counting.
|
||||
*/
|
||||
|
||||
#include <zephyr/drivers/gpio.h>
|
||||
@@ -38,6 +41,12 @@ static struct sx12xx_data {
|
||||
struct lora_modem_config tx_cfg;
|
||||
atomic_t modem_usage;
|
||||
struct sx12xx_rx_params rx_params;
|
||||
+ /* Fast TX↔RX switching: cache last full config to detect
|
||||
+ * direction-only changes and skip redundant SPI reconfigure. */
|
||||
+ struct lora_modem_config last_cfg;
|
||||
+ bool last_cfg_valid;
|
||||
+ bool tx_configured; /* RadioSetTxConfig has been called with current params */
|
||||
+ bool rx_configured; /* RadioSetRxConfig has been called with current params */
|
||||
} dev_data;
|
||||
|
||||
int __sx12xx_configure_pin(const struct gpio_dt_spec *gpio, gpio_flags_t flags)
|
||||
@@ -179,6 +188,13 @@ static void sx12xx_ev_rx_error(void)
|
||||
if (dev_data.async_rx_cb) {
|
||||
/* Start receiving again */
|
||||
Radio.Rx(0);
|
||||
+ /*
|
||||
+ * ZEPHCORE PATCH: Notify callback with NULL data to indicate
|
||||
+ * RX error (CRC mismatch, header error). This allows the
|
||||
+ * application to count receive errors for diagnostics.
|
||||
+ */
|
||||
+ dev_data.async_rx_cb(dev_data.dev, NULL, 0, 0, 0,
|
||||
+ dev_data.async_user_data);
|
||||
/* Don't run the synchronous code */
|
||||
return;
|
||||
}
|
||||
@@ -195,26 +211,24 @@ static void sx12xx_ev_rx_error(void)
|
||||
/**
|
||||
* @brief Convert Zephyr bandwidth enum to loramac-node bandwidth index
|
||||
*
|
||||
- * The loramac-node library expects bandwidth as an index (0, 1, 2) into its
|
||||
- * internal Bandwidths[] array, not the actual kHz value.
|
||||
- *
|
||||
- * @param bandwidth Zephyr lora_signal_bandwidth enum value
|
||||
- * @param bw_idx Pointer to store the resulting bandwidth index
|
||||
- * @return 0 on success, -EINVAL if bandwidth is not supported
|
||||
+ * The loramac-node library expects bandwidth as an index into its internal
|
||||
+ * Bandwidths[] array: {BW_007, BW_010, BW_015, BW_020, BW_031,
|
||||
+ * BW_041, BW_062, BW_125, BW_250, BW_500}
|
||||
*/
|
||||
static int sx12xx_get_bandwidth_idx(enum lora_signal_bandwidth bandwidth,
|
||||
uint32_t *bw_idx)
|
||||
{
|
||||
switch (bandwidth) {
|
||||
- case BW_125_KHZ:
|
||||
- *bw_idx = 0;
|
||||
- break;
|
||||
- case BW_250_KHZ:
|
||||
- *bw_idx = 1;
|
||||
- break;
|
||||
- case BW_500_KHZ:
|
||||
- *bw_idx = 2;
|
||||
- break;
|
||||
+ case BW_7_KHZ: *bw_idx = 0; break;
|
||||
+ case BW_10_KHZ: *bw_idx = 1; break;
|
||||
+ case BW_15_KHZ: *bw_idx = 2; break;
|
||||
+ case BW_20_KHZ: *bw_idx = 3; break;
|
||||
+ case BW_31_KHZ: *bw_idx = 4; break;
|
||||
+ case BW_41_KHZ: *bw_idx = 5; break;
|
||||
+ case BW_62_KHZ: *bw_idx = 6; break;
|
||||
+ case BW_125_KHZ: *bw_idx = 7; break;
|
||||
+ case BW_250_KHZ: *bw_idx = 8; break;
|
||||
+ case BW_500_KHZ: *bw_idx = 9; break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
@@ -225,7 +239,6 @@ uint32_t sx12xx_airtime(const struct device *dev, uint32_t data_len)
|
||||
{
|
||||
uint32_t bw_idx;
|
||||
|
||||
- /* Translate bandwidth to loramac-node index, default to 0 if invalid */
|
||||
if (sx12xx_get_bandwidth_idx(dev_data.tx_cfg.bandwidth, &bw_idx) < 0) {
|
||||
bw_idx = 0;
|
||||
}
|
||||
@@ -375,6 +388,21 @@ int sx12xx_lora_recv_async(const struct device *dev, lora_recv_cb cb, void *user
|
||||
return 0;
|
||||
}
|
||||
|
||||
+/* Check if only the TX/RX direction changed (all radio params identical). */
|
||||
+static bool sx12xx_only_direction_changed(const struct lora_modem_config *a,
|
||||
+ const struct lora_modem_config *b)
|
||||
+{
|
||||
+ return a->frequency == b->frequency &&
|
||||
+ a->bandwidth == b->bandwidth &&
|
||||
+ a->datarate == b->datarate &&
|
||||
+ a->coding_rate == b->coding_rate &&
|
||||
+ a->preamble_len == b->preamble_len &&
|
||||
+ a->tx_power == b->tx_power &&
|
||||
+ a->iq_inverted == b->iq_inverted &&
|
||||
+ a->public_network == b->public_network &&
|
||||
+ a->tx != b->tx;
|
||||
+}
|
||||
+
|
||||
int sx12xx_lora_config(const struct device *dev,
|
||||
struct lora_modem_config *config)
|
||||
{
|
||||
@@ -388,6 +416,39 @@ int sx12xx_lora_config(const struct device *dev,
|
||||
return ret;
|
||||
}
|
||||
|
||||
+ /* Fast path: if only TX↔RX direction changed and the target direction
|
||||
+ * was already configured once with the same params, skip the full
|
||||
+ * RadioSetTxConfig/RadioSetRxConfig (saves ~35ms of SPI traffic).
|
||||
+ * RadioSetTxConfig MUST have been called at least once to set
|
||||
+ * TxTimeout=4000; RadioSetRxConfig MUST have been called at least
|
||||
+ * once to set PayloadLength/SymbTimeout/IQ polarity workaround. */
|
||||
+ if (dev_data.last_cfg_valid &&
|
||||
+ sx12xx_only_direction_changed(config, &dev_data.last_cfg)) {
|
||||
+ if (config->tx && dev_data.tx_configured) {
|
||||
+ LOG_DBG("lora_config: fast TX switch (skip full reconfig)");
|
||||
+ if (!modem_acquire(&dev_data)) {
|
||||
+ return -EBUSY;
|
||||
+ }
|
||||
+ memcpy(&dev_data.tx_cfg, config, sizeof(dev_data.tx_cfg));
|
||||
+ dev_data.last_cfg = *config;
|
||||
+ modem_release(&dev_data);
|
||||
+ return 0;
|
||||
+ }
|
||||
+ if (!config->tx && dev_data.rx_configured) {
|
||||
+ LOG_DBG("lora_config: fast RX switch (skip full reconfig)");
|
||||
+ if (!modem_acquire(&dev_data)) {
|
||||
+ return -EBUSY;
|
||||
+ }
|
||||
+ dev_data.last_cfg = *config;
|
||||
+ modem_release(&dev_data);
|
||||
+ return 0;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ LOG_INF("lora_config: bw_enum=%d bw_idx=%u tx=%d freq=%u sf=%d",
|
||||
+ config->bandwidth, bw_idx, config->tx, config->frequency,
|
||||
+ config->datarate);
|
||||
+
|
||||
/* Ensure available, decremented after configuration */
|
||||
if (!modem_acquire(&dev_data)) {
|
||||
return -EBUSY;
|
||||
@@ -403,16 +464,21 @@ int sx12xx_lora_config(const struct device *dev,
|
||||
bw_idx, config->datarate,
|
||||
config->coding_rate, config->preamble_len,
|
||||
false, crc, 0, 0, config->iq_inverted, 4000);
|
||||
+ dev_data.tx_configured = true;
|
||||
} else {
|
||||
/* TODO: Get symbol timeout value from config parameters */
|
||||
Radio.SetRxConfig(MODEM_LORA, bw_idx,
|
||||
config->datarate, config->coding_rate,
|
||||
0, config->preamble_len, 10, false, 0,
|
||||
crc, false, 0, config->iq_inverted, true);
|
||||
+ dev_data.rx_configured = true;
|
||||
}
|
||||
|
||||
Radio.SetPublicNetwork(config->public_network);
|
||||
|
||||
+ dev_data.last_cfg = *config;
|
||||
+ dev_data.last_cfg_valid = true;
|
||||
+
|
||||
modem_release(&dev_data);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,8 +1,178 @@
|
||||
diff --git a/drivers/lora/native/sx126x/sx126x.c b/drivers/lora/native/sx126x/sx126x.c
|
||||
index 8e0ca45c271..828faaaf37f 100644
|
||||
index 8e0ca45c271..1816ff8f924 100644
|
||||
--- a/drivers/lora/native/sx126x/sx126x.c
|
||||
+++ b/drivers/lora/native/sx126x/sx126x.c
|
||||
@@ -451,7 +451,14 @@ static void sx126x_set_rf_path(const struct device *dev, bool enable, bool tx)
|
||||
@@ -9,10 +9,19 @@
|
||||
#include <zephyr/sys/byteorder.h>
|
||||
|
||||
#include "sx126x.h"
|
||||
+#include "sx126x_ext.h"
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_REGISTER(sx126x, CONFIG_LORA_LOG_LEVEL);
|
||||
|
||||
+/* Dedicated DIO1 work queue — keeps LoRa interrupt processing off the
|
||||
+ * system work queue so USB/BLE/timer work items cannot delay packet RX. */
|
||||
+#define SX126X_DIO1_WQ_STACK_SIZE 1536
|
||||
+K_THREAD_STACK_DEFINE(sx126x_dio1_wq_stack, SX126X_DIO1_WQ_STACK_SIZE);
|
||||
+
|
||||
+/* Register not in sx126x_regs.h — only used for §15.3 workaround */
|
||||
+#define SX126X_REG_EVT_CLR 0x0920
|
||||
+
|
||||
static uint8_t bandwidth_to_reg(enum lora_signal_bandwidth bw)
|
||||
{
|
||||
switch (bw) {
|
||||
@@ -172,22 +181,10 @@ static int sx126x_calibrate_image(const struct device *dev, uint32_t freq)
|
||||
{
|
||||
uint8_t buf[2];
|
||||
|
||||
- if (freq > 900000000) {
|
||||
- buf[0] = 0xE1;
|
||||
- buf[1] = 0xE9;
|
||||
- } else if (freq > 850000000) {
|
||||
- buf[0] = 0xD7;
|
||||
- buf[1] = 0xDB;
|
||||
- } else if (freq > 770000000) {
|
||||
- buf[0] = 0xC1;
|
||||
- buf[1] = 0xC5;
|
||||
- } else if (freq > 460000000) {
|
||||
- buf[0] = 0x75;
|
||||
- buf[1] = 0x81;
|
||||
- } else {
|
||||
- buf[0] = 0x6B;
|
||||
- buf[1] = 0x6F;
|
||||
- }
|
||||
+ /* Calibrate ±2 MHz around the actual frequency.
|
||||
+ * CalibrateImage bytes are in 4 MHz steps (freq_Hz / 4000000). */
|
||||
+ buf[0] = (uint8_t)((freq - 2000000) / 4000000);
|
||||
+ buf[1] = (uint8_t)((freq + 2000000) / 4000000);
|
||||
|
||||
return sx126x_hal_write_cmd(dev, SX126X_CMD_CALIBRATE_IMAGE, buf, 2);
|
||||
}
|
||||
@@ -267,6 +264,7 @@ static int sx126x_set_packet_params(const struct device *dev,
|
||||
uint8_t invert_iq)
|
||||
{
|
||||
uint8_t buf[6];
|
||||
+ int ret;
|
||||
|
||||
sys_put_be16(preamble_len, &buf[0]);
|
||||
buf[2] = header_type;
|
||||
@@ -274,7 +272,28 @@ static int sx126x_set_packet_params(const struct device *dev,
|
||||
buf[4] = crc_mode;
|
||||
buf[5] = invert_iq;
|
||||
|
||||
- return sx126x_hal_write_cmd(dev, SX126X_CMD_SET_PACKET_PARAMS, buf, 6);
|
||||
+ ret = sx126x_hal_write_cmd(dev, SX126X_CMD_SET_PACKET_PARAMS, buf, 6);
|
||||
+ if (ret < 0) {
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ /* §15.4 IQ Polarity workaround (datasheet errata).
|
||||
+ * After SetPacketParams, register 0x0736 bit 2 must be:
|
||||
+ * SET for standard IQ (non-inverted)
|
||||
+ * CLEAR for inverted IQ
|
||||
+ * Without this fix, inverted-IQ packets are not received. */
|
||||
+ uint8_t iq_val;
|
||||
+
|
||||
+ ret = sx126x_hal_read_regs(dev, SX126X_REG_IQ_POLARITY, &iq_val, 1);
|
||||
+ if (ret < 0) {
|
||||
+ return ret;
|
||||
+ }
|
||||
+ if (invert_iq == SX126X_LORA_IQ_INVERTED) {
|
||||
+ iq_val &= ~BIT(2);
|
||||
+ } else {
|
||||
+ iq_val |= BIT(2);
|
||||
+ }
|
||||
+ return sx126x_hal_write_regs(dev, SX126X_REG_IQ_POLARITY, &iq_val, 1);
|
||||
}
|
||||
|
||||
static int sx126x_set_sync_word(const struct device *dev, bool public_network)
|
||||
@@ -307,6 +326,7 @@ static int sx126x_set_tx(const struct device *dev, uint32_t timeout_ms)
|
||||
static int sx126x_set_rx(const struct device *dev, uint32_t timeout_ms)
|
||||
{
|
||||
uint32_t timeout;
|
||||
+ int ret;
|
||||
|
||||
if (timeout_ms == 0) {
|
||||
timeout = SX126X_RX_TIMEOUT_CONTINUOUS;
|
||||
@@ -317,7 +337,23 @@ static int sx126x_set_rx(const struct device *dev, uint32_t timeout_ms)
|
||||
uint8_t buf[3];
|
||||
|
||||
sys_put_be24(timeout, buf);
|
||||
- return sx126x_hal_write_cmd(dev, SX126X_CMD_SET_RX, buf, 3);
|
||||
+ ret = sx126x_hal_write_cmd(dev, SX126X_CMD_SET_RX, buf, 3);
|
||||
+ if (ret < 0) {
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ /* §15.3 Implicit header mode timeout workaround (datasheet errata).
|
||||
+ * After SetRx(), clear bit 0 of register 0x0920 to prevent the
|
||||
+ * RX timer from continuing to run after reception completes.
|
||||
+ * Applied unconditionally per Semtech reference implementation. */
|
||||
+ uint8_t evt;
|
||||
+
|
||||
+ ret = sx126x_hal_read_regs(dev, SX126X_REG_EVT_CLR, &evt, 1);
|
||||
+ if (ret < 0) {
|
||||
+ return ret;
|
||||
+ }
|
||||
+ evt &= ~BIT(0);
|
||||
+ return sx126x_hal_write_regs(dev, SX126X_REG_EVT_CLR, &evt, 1);
|
||||
}
|
||||
|
||||
static int sx126x_get_rx_buffer_status(const struct device *dev,
|
||||
@@ -341,12 +377,18 @@ static int sx126x_get_packet_status(const struct device *dev,
|
||||
uint8_t buf[3];
|
||||
int ret;
|
||||
|
||||
- ret = sx126x_hal_read_cmd(dev, SX126X_CMD_GET_PACKET_STATUS, buf, 2);
|
||||
+ ret = sx126x_hal_read_cmd(dev, SX126X_CMD_GET_PACKET_STATUS, buf, 3);
|
||||
if (ret == 0) {
|
||||
/* RSSI is -value/2 dBm */
|
||||
- *rssi = -((int16_t)buf[0] >> 1);
|
||||
+ int16_t pkt_rssi = -((int16_t)buf[0] >> 1);
|
||||
/* SNR is value/4 dB (signed) */
|
||||
*snr = ((int8_t)buf[1]) >> 2;
|
||||
+ /* SignalRssiPkt (byte 2) = LoRa signal RSSI excluding noise.
|
||||
+ * When SNR < 0 the packet RSSI is dominated by noise —
|
||||
+ * use the signal-only estimate for a more accurate reading. */
|
||||
+ int16_t sig_rssi = -((int16_t)buf[2] >> 1);
|
||||
+
|
||||
+ *rssi = (*snr < 0 && sig_rssi > pkt_rssi) ? sig_rssi : pkt_rssi;
|
||||
}
|
||||
|
||||
return ret;
|
||||
@@ -419,6 +461,21 @@ static int sx126x_chip_init(const struct device *dev)
|
||||
return ret;
|
||||
}
|
||||
|
||||
+ /* After TX/RX, fall back to STDBY_XOSC instead of STDBY_RC.
|
||||
+ * Keeps the crystal warm so the next SetRx skips the ~500 us
|
||||
+ * XOSC startup, reducing the deaf window between packets. */
|
||||
+ {
|
||||
+ uint8_t fallback = 0x40; /* STDBY_XOSC */
|
||||
+
|
||||
+ ret = sx126x_hal_write_cmd(dev,
|
||||
+ SX126X_CMD_SET_RX_TX_FALLBACK_MODE,
|
||||
+ &fallback, 1);
|
||||
+ if (ret < 0) {
|
||||
+ LOG_ERR("Set fallback mode failed: %d", ret);
|
||||
+ return ret;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
/* Configure IRQs on DIO1: TX done, RX done, timeout */
|
||||
uint16_t irq_mask = SX126X_IRQ_TX_DONE | SX126X_IRQ_RX_DONE |
|
||||
SX126X_IRQ_RX_TX_TIMEOUT | SX126X_IRQ_CRC_ERR;
|
||||
@@ -443,7 +500,7 @@ static void sx126x_dio1_callback(const struct device *dev)
|
||||
{
|
||||
struct sx126x_data *data = dev->data;
|
||||
|
||||
- k_work_submit(&data->irq_work);
|
||||
+ k_work_submit_to_queue(&data->dio1_wq, &data->irq_work);
|
||||
}
|
||||
|
||||
static void sx126x_set_rf_path(const struct device *dev, bool enable, bool tx)
|
||||
@@ -451,11 +508,108 @@ static void sx126x_set_rf_path(const struct device *dev, bool enable, bool tx)
|
||||
const struct sx126x_hal_config *config = dev->config;
|
||||
|
||||
sx126x_hal_set_antenna_enable(dev, enable);
|
||||
@@ -18,3 +188,319 @@ index 8e0ca45c271..828faaaf37f 100644
|
||||
sx126x_hal_set_rf_switch(dev, enable && tx);
|
||||
}
|
||||
}
|
||||
|
||||
+/* ── RX duty cycle (RadioLib algorithm) ─────────────────────────────── */
|
||||
+
|
||||
+#define SX126X_DC_MIN_SYMBOLS_SF7_PLUS 8
|
||||
+#define SX126X_DC_MIN_SYMBOLS_SF6_LESS 12
|
||||
+#define SX126X_DC_TCXO_DELAY_US 1000
|
||||
+
|
||||
+static void sx126x_apply_rx_duty_cycle(struct sx126x_data *data)
|
||||
+{
|
||||
+ const struct device *dev = data->dev;
|
||||
+ struct lora_modem_config *mc = &data->config;
|
||||
+
|
||||
+ uint8_t sf = (uint8_t)mc->datarate;
|
||||
+ uint32_t bw_hz = bandwidth_to_hz(mc->bandwidth);
|
||||
+ float bw_khz = (float)bw_hz / 1000.0f;
|
||||
+ uint16_t preamble_len = mc->preamble_len;
|
||||
+
|
||||
+ uint16_t min_symbols = (sf >= 7) ? SX126X_DC_MIN_SYMBOLS_SF7_PLUS
|
||||
+ : SX126X_DC_MIN_SYMBOLS_SF6_LESS;
|
||||
+
|
||||
+ 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;
|
||||
+ sx126x_set_rx(dev, 0);
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ uint32_t symbol_us = (uint32_t)((float)(1 << sf) * 1000.0f / bw_khz);
|
||||
+
|
||||
+ /* 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;
|
||||
+
|
||||
+ uint32_t preamble_total_us = (preamble_len + 1) * symbol_us;
|
||||
+ int32_t wake_calc1 = ((int32_t)preamble_total_us -
|
||||
+ ((int32_t)sleep_period_us - SX126X_DC_TCXO_DELAY_US)) / 2;
|
||||
+ uint32_t wake_calc2 = (min_symbols + 1) * symbol_us;
|
||||
+
|
||||
+ uint32_t wake_period_us = (wake_calc1 > 0 && (uint32_t)wake_calc1 > wake_calc2)
|
||||
+ ? (uint32_t)wake_calc1 : wake_calc2;
|
||||
+
|
||||
+ /* SetRxDutyCycle takes times in 15.625us steps (multiply by 64/1000) */
|
||||
+ uint32_t rx_time = (wake_period_us * 64) / 1000;
|
||||
+ uint32_t sleep_time = (sleep_period_us * 64) / 1000;
|
||||
+
|
||||
+ if (rx_time < 64) {
|
||||
+ rx_time = 64;
|
||||
+ }
|
||||
+ if (sleep_time < 64) {
|
||||
+ sleep_time = 64;
|
||||
+ }
|
||||
+
|
||||
+ /* SPI command: 3 bytes rx_period + 3 bytes sleep_period */
|
||||
+ uint8_t buf[6];
|
||||
+
|
||||
+ sys_put_be24(rx_time, &buf[0]);
|
||||
+ sys_put_be24(sleep_time, &buf[3]);
|
||||
+ sx126x_hal_write_cmd(dev, SX126X_CMD_SET_RX_DUTY_CYCLE, buf, 6);
|
||||
+
|
||||
+ uint32_t rx_ms = (rx_time * 1000) / 64000;
|
||||
+ uint32_t sleep_ms = (sleep_time * 1000) / 64000;
|
||||
+ LOG_DBG("RX duty cycle: SF%d rx=%ums sleep=%ums", sf, rx_ms, sleep_ms);
|
||||
+}
|
||||
+
|
||||
+/* ── Lightweight RX restart ─────────────────────────────────────────── */
|
||||
+
|
||||
+/* Restart RX as fast as possible — used for RX→RX transitions in the
|
||||
+ * IRQ handler. Sends SetRx(continuous) directly, skipping the §15.3
|
||||
+ * workaround (only needed for implicit-header timed RX). This saves
|
||||
+ * 2 SPI register transactions (~200 us) on every received packet. */
|
||||
+static void sx126x_restart_rx(const struct device *dev, struct sx126x_data *data)
|
||||
+{
|
||||
+ if (data->rx_duty_cycle_enabled) {
|
||||
+ sx126x_apply_rx_duty_cycle(data);
|
||||
+ } else {
|
||||
+ uint8_t buf[3];
|
||||
+
|
||||
+ sys_put_be24(SX126X_RX_TIMEOUT_CONTINUOUS, buf);
|
||||
+ sx126x_hal_write_cmd(dev, SX126X_CMD_SET_RX, buf, 3);
|
||||
+ }
|
||||
+
|
||||
+ if (data->rx_boost_enabled) {
|
||||
+ sx126x_set_rx_gain(dev, true);
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
static void sx126x_handle_irq_tx_done(const struct device *dev)
|
||||
{
|
||||
struct sx126x_data *data = dev->data;
|
||||
@@ -510,12 +664,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) {
|
||||
- /* Async mode - call callback and restart RX */
|
||||
- data->rx_cb(dev, data->rx_buf, result.len,
|
||||
- result.rssi, result.snr,
|
||||
- data->rx_cb_user_data);
|
||||
- /* Restart RX for continuous reception */
|
||||
- sx126x_set_rx(dev, 0);
|
||||
+ /* Restart RX FIRST — minimise the deaf window.
|
||||
+ * The callback (mesh processing) can take 100s of us;
|
||||
+ * doing it before restart would lose back-to-back packets.
|
||||
+ * Safe: we're on a cooperative work queue so the next
|
||||
+ * DIO1 work item won't preempt us, and the callback
|
||||
+ * copies rx_buf before returning. */
|
||||
+ sx126x_restart_rx(dev, data);
|
||||
+
|
||||
+ if (result.status < 0) {
|
||||
+ data->rx_cb(dev, NULL, 0,
|
||||
+ result.rssi, result.snr,
|
||||
+ data->rx_cb_user_data);
|
||||
+ } else {
|
||||
+ data->rx_cb(dev, data->rx_buf, result.len,
|
||||
+ result.rssi, result.snr,
|
||||
+ data->rx_cb_user_data);
|
||||
+ }
|
||||
} else {
|
||||
/* Sync mode */
|
||||
atomic_set(&data->state, SX126X_STATE_IDLE);
|
||||
@@ -621,6 +786,29 @@ static int sx126x_lora_config(const struct device *dev,
|
||||
goto out;
|
||||
}
|
||||
|
||||
+ /* §15.1 TX Modulation workaround (datasheet errata).
|
||||
+ * For 500 kHz BW, clear bit 2 of register 0x0889.
|
||||
+ * For all other bandwidths, set bit 2 (restore default). */
|
||||
+ {
|
||||
+ uint8_t txmod;
|
||||
+
|
||||
+ ret = sx126x_hal_read_regs(dev, SX126X_REG_TX_MODULATION,
|
||||
+ &txmod, 1);
|
||||
+ if (ret < 0) {
|
||||
+ goto out;
|
||||
+ }
|
||||
+ if (config->bandwidth == BW_500_KHZ) {
|
||||
+ txmod &= ~BIT(2);
|
||||
+ } else {
|
||||
+ txmod |= BIT(2);
|
||||
+ }
|
||||
+ ret = sx126x_hal_write_regs(dev, SX126X_REG_TX_MODULATION,
|
||||
+ &txmod, 1);
|
||||
+ if (ret < 0) {
|
||||
+ goto out;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
/* Set sync word */
|
||||
ret = sx126x_set_sync_word(dev, config->public_network);
|
||||
if (ret < 0) {
|
||||
@@ -691,6 +879,29 @@ static int sx126x_lora_send_async(const struct device *dev,
|
||||
/* Enable antenna and set TX path */
|
||||
sx126x_set_rf_path(dev, true, true);
|
||||
|
||||
+ /* §15.2 TX Clamp workaround (datasheet errata) — SX1262 only.
|
||||
+ * Set bits [4:1] of register 0x08D8 before SetTx to prevent
|
||||
+ * PA overshoot that can damage the device. */
|
||||
+ {
|
||||
+ const struct sx126x_hal_config *hal_cfg = dev->config;
|
||||
+
|
||||
+ if (!hal_cfg->is_sx1261) {
|
||||
+ uint8_t clamp;
|
||||
+
|
||||
+ ret = sx126x_hal_read_regs(dev, SX126X_REG_TX_CLAMP_CFG,
|
||||
+ &clamp, 1);
|
||||
+ if (ret < 0) {
|
||||
+ goto out_error;
|
||||
+ }
|
||||
+ clamp |= 0x1E;
|
||||
+ ret = sx126x_hal_write_regs(dev, SX126X_REG_TX_CLAMP_CFG,
|
||||
+ &clamp, 1);
|
||||
+ if (ret < 0) {
|
||||
+ goto out_error;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
/* Start transmission with 10 second timeout */
|
||||
ret = sx126x_set_tx(dev, 10000);
|
||||
if (ret < 0) {
|
||||
@@ -974,6 +1185,77 @@ static int sx126x_lora_test_cw(const struct device *dev, uint32_t frequency,
|
||||
return 0;
|
||||
}
|
||||
|
||||
+/* ── Extension API (sx126x_ext.h) ──────────────────────────────────── */
|
||||
+
|
||||
+int16_t sx126x_get_rssi_inst(const struct device *dev)
|
||||
+{
|
||||
+ struct sx126x_data *data = dev->data;
|
||||
+ uint8_t buf[1];
|
||||
+ int ret;
|
||||
+
|
||||
+ if (k_mutex_lock(&data->lock, K_NO_WAIT) != 0) {
|
||||
+ return -128;
|
||||
+ }
|
||||
+
|
||||
+ ret = sx126x_hal_read_cmd(dev, SX126X_CMD_GET_RSSI_INST, buf, 1);
|
||||
+ k_mutex_unlock(&data->lock);
|
||||
+
|
||||
+ if (ret < 0) {
|
||||
+ return -128;
|
||||
+ }
|
||||
+
|
||||
+ return -((int16_t)buf[0] >> 1);
|
||||
+}
|
||||
+
|
||||
+bool sx126x_is_receiving(const struct device *dev)
|
||||
+{
|
||||
+ struct sx126x_data *data = dev->data;
|
||||
+ uint16_t irq_status = 0;
|
||||
+
|
||||
+ if (k_mutex_lock(&data->lock, K_NO_WAIT) != 0) {
|
||||
+ return false;
|
||||
+ }
|
||||
+
|
||||
+ sx126x_get_irq_status(dev, &irq_status);
|
||||
+ k_mutex_unlock(&data->lock);
|
||||
+
|
||||
+ return (irq_status & (SX126X_IRQ_PREAMBLE_DETECTED |
|
||||
+ SX126X_IRQ_HEADER_VALID)) != 0;
|
||||
+}
|
||||
+
|
||||
+void sx126x_set_rx_duty_cycle(const struct device *dev, bool enable)
|
||||
+{
|
||||
+ struct sx126x_data *data = dev->data;
|
||||
+
|
||||
+ data->rx_duty_cycle_enabled = enable;
|
||||
+ LOG_DBG("RX duty cycle %s", enable ? "enabled" : "disabled");
|
||||
+
|
||||
+ /* If currently in RX, apply immediately */
|
||||
+ if (atomic_get(&data->state) == SX126X_STATE_RX) {
|
||||
+ k_mutex_lock(&data->lock, K_FOREVER);
|
||||
+ if (enable && data->config_valid) {
|
||||
+ sx126x_apply_rx_duty_cycle(data);
|
||||
+ } else {
|
||||
+ sx126x_set_rx(dev, 0);
|
||||
+ }
|
||||
+ if (data->rx_boost_enabled) {
|
||||
+ sx126x_set_rx_gain(dev, true);
|
||||
+ }
|
||||
+ k_mutex_unlock(&data->lock);
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+void sx126x_set_rx_boost(const struct device *dev, bool enable)
|
||||
+{
|
||||
+ struct sx126x_data *data = dev->data;
|
||||
+
|
||||
+ data->rx_boost_enabled = enable;
|
||||
+
|
||||
+ k_mutex_lock(&data->lock, K_FOREVER);
|
||||
+ sx126x_set_rx_gain(dev, enable);
|
||||
+ k_mutex_unlock(&data->lock);
|
||||
+}
|
||||
+
|
||||
static const struct lora_driver_api sx126x_lora_api = {
|
||||
.config = sx126x_lora_config,
|
||||
.send = sx126x_lora_send,
|
||||
@@ -999,6 +1281,14 @@ static int sx126x_init(const struct device *dev)
|
||||
data->dev = dev;
|
||||
atomic_set(&data->state, SX126X_STATE_IDLE);
|
||||
data->config_valid = false;
|
||||
+ data->rx_duty_cycle_enabled = false;
|
||||
+ data->rx_boost_enabled = false;
|
||||
+
|
||||
+ /* Start dedicated DIO1 work queue */
|
||||
+ k_work_queue_start(&data->dio1_wq, sx126x_dio1_wq_stack,
|
||||
+ K_THREAD_STACK_SIZEOF(sx126x_dio1_wq_stack),
|
||||
+ CONFIG_SYSTEM_WORKQUEUE_PRIORITY, NULL);
|
||||
+ k_thread_name_set(&data->dio1_wq.thread, "sx126x_dio1");
|
||||
|
||||
/* Initialize HAL */
|
||||
ret = sx126x_hal_init(dev);
|
||||
diff --git a/drivers/lora/native/sx126x/sx126x.h b/drivers/lora/native/sx126x/sx126x.h
|
||||
index 1490a010254..ef5b15502db 100644
|
||||
--- a/drivers/lora/native/sx126x/sx126x.h
|
||||
+++ b/drivers/lora/native/sx126x/sx126x.h
|
||||
@@ -59,7 +59,12 @@ struct sx126x_data {
|
||||
|
||||
/* Deferred work for interrupt handling */
|
||||
struct k_work irq_work;
|
||||
+ struct k_work_q dio1_wq;
|
||||
const struct device *dev;
|
||||
+
|
||||
+ /* Extension features (duty cycle, boost) */
|
||||
+ bool rx_duty_cycle_enabled;
|
||||
+ bool rx_boost_enabled;
|
||||
};
|
||||
|
||||
#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 bdf962e3343..de60d18b694 100644
|
||||
--- a/drivers/lora/native/sx126x/sx126x_hal.c
|
||||
+++ b/drivers/lora/native/sx126x/sx126x_hal.c
|
||||
@@ -101,8 +101,16 @@ bool sx126x_hal_is_busy(const struct device *dev)
|
||||
|
||||
int sx126x_hal_wait_busy(const struct device *dev, uint32_t timeout_ms)
|
||||
{
|
||||
+ /* Fast path: most SX126x commands finish BUSY in <650 us.
|
||||
+ * Busy-wait first to avoid the ~1 ms k_msleep() scheduler
|
||||
+ * penalty, which would otherwise dominate RX turnaround. */
|
||||
+ if (WAIT_FOR(!sx126x_hal_is_busy(dev), 1000, k_busy_wait(10))) {
|
||||
+ return 0;
|
||||
+ }
|
||||
+
|
||||
+ /* Slow path: long operations like calibration (several ms). */
|
||||
if (!WAIT_FOR(!sx126x_hal_is_busy(dev),
|
||||
- timeout_ms * 1000,
|
||||
+ (timeout_ms * 1000) - 1000,
|
||||
k_msleep(1))) {
|
||||
LOG_WRN("Busy timeout after %u ms", timeout_ms);
|
||||
return -ETIMEDOUT;
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
diff --git a/drivers/lora/loramac-node/sx126x_standalone.c b/drivers/lora/loramac-node/sx126x_standalone.c
|
||||
index c9b16965bc7..3a2ff8b20c9 100644
|
||||
--- a/drivers/lora/loramac-node/sx126x_standalone.c
|
||||
+++ b/drivers/lora/loramac-node/sx126x_standalone.c
|
||||
@@ -40,8 +40,11 @@ uint32_t sx126x_get_dio1_pin_state(struct sx126x_data *dev_data)
|
||||
|
||||
void sx126x_dio1_irq_enable(struct sx126x_data *dev_data)
|
||||
{
|
||||
- gpio_pin_interrupt_configure_dt(&sx126x_gpio_dio1,
|
||||
+ int ret = gpio_pin_interrupt_configure_dt(&sx126x_gpio_dio1,
|
||||
GPIO_INT_EDGE_TO_ACTIVE);
|
||||
+ if (ret != 0) {
|
||||
+ LOG_ERR("DIO1 irq enable FAILED: %d", ret);
|
||||
+ }
|
||||
}
|
||||
|
||||
void sx126x_dio1_irq_disable(struct sx126x_data *dev_data)
|
||||
@@ -216,9 +216,12 @@ static void contact_iter_work_fn(struct k_work *work)
|
||||
{
|
||||
ARG_UNUSED(work);
|
||||
#ifdef ZEPHCORE_LORA
|
||||
/* Check if send queue has space before continuing iteration
|
||||
* Match Arduino's isWriteBusy(): busy when queue >= 2/3 full (8 of 12)
|
||||
*/
|
||||
/* Don't iterate while BLE TX is congested — wait for drain to clear.
|
||||
* Also check 2/3 high-water mark (catches stray frames before full). */
|
||||
if (zephcore_ble_is_congested()) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t used = k_msgq_num_used_get(zephcore_ble_get_send_queue());
|
||||
uint32_t queue_size = CONFIG_ZEPHCORE_BLE_QUEUE_SIZE;
|
||||
bool has_space = (used < (queue_size * 2 / 3));
|
||||
|
||||
Reference in New Issue
Block a user