diff --git a/zephcore/CMakeLists.txt b/zephcore/CMakeLists.txt index 9e76f66..cfde60e 100644 --- a/zephcore/CMakeLists.txt +++ b/zephcore/CMakeLists.txt @@ -121,6 +121,16 @@ if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/patches/zephyr) ) endif() +# Apply patches to loramac-node module +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/patches/modules/loramac-node) + message(STATUS "Applying ZephCore patches to loramac-node...") + zephcore_apply_patches( + "${CMAKE_CURRENT_SOURCE_DIR}/patches/modules/loramac-node" + "${MODULES_DIR}/lib/loramac-node" + "loramac-node" + ) +endif() + # Copy new files into Zephyr tree if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/patches/zephyr-new) file(GLOB_RECURSE ZEPHCORE_NEW_FILES diff --git a/zephcore/adapters/radio/LoRaRadioBase.cpp b/zephcore/adapters/radio/LoRaRadioBase.cpp index d92dd72..295adfc 100644 --- a/zephcore/adapters/radio/LoRaRadioBase.cpp +++ b/zephcore/adapters/radio/LoRaRadioBase.cpp @@ -29,6 +29,7 @@ LoRaRadioBase::LoRaRadioBase(const struct device *lora_dev, MainBoard &board, _rx_duty_cycle_enabled(IS_ENABLED(CONFIG_ZEPHCORE_LORA_RX_DUTY_CYCLE)), _rx_boost_enabled(true), _tx_power_reduction_db(0), + _loramac_node(false), _config_cached(false), _rx_cb(nullptr), _rx_cb_user_data(nullptr), _tx_done_cb(nullptr), _tx_done_cb_user_data(nullptr), @@ -272,8 +273,12 @@ void LoRaRadioBase::configureRx() * 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)) { + * round-trip that wastes ~5 ms on every TX→RX transition. + * + * Not used for loramac-node: Radio.SetTxConfig() and Radio.SetRxConfig() + * configure completely disjoint internal state (including TxTimeout). + * Skipping either on a direction change leaves that state uninitialized. */ + if (!_loramac_node && _config_cached && onlyDirectionDiffers(cfg, _last_cfg)) { LOG_DBG("configureRx: direction-only change, skip hwConfigure"); _last_cfg = cfg; return; @@ -300,8 +305,9 @@ void LoRaRadioBase::configureTx() /* 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)) { + * from a previous cycle — Radio.Send() will use those values. + * Not used for loramac-node (see configureRx comment above). */ + if (!_loramac_node && _config_cached && onlyDirectionDiffers(cfg, _last_cfg)) { LOG_DBG("configureTx: direction-only change, skip hwConfigure"); _last_cfg = cfg; return; diff --git a/zephcore/adapters/radio/LoRaRadioBase.h b/zephcore/adapters/radio/LoRaRadioBase.h index 6c367ab..2d9cca9 100644 --- a/zephcore/adapters/radio/LoRaRadioBase.h +++ b/zephcore/adapters/radio/LoRaRadioBase.h @@ -90,6 +90,13 @@ protected: /** GPIO-only BUSY check (no SPI). Default false for chips without duty-cycle sleep. */ virtual bool hwIsChipBusy() { return false; } + /* Set to true by subclasses using the loramac-node driver backend. + * Disables the direction-only fast path in configureTx()/configureRx(): + * loramac-node calls Radio.SetTxConfig() and Radio.SetRxConfig() which + * configure completely disjoint internal state — skipping either leaves + * TxTimeout/RxConfig uninitialized in the loramac-node library. */ + bool _loramac_node; + /* ── Shared helpers available to subclasses ────────────────── */ void buildModemConfig(struct lora_modem_config &cfg, bool tx); diff --git a/zephcore/adapters/radio/SX127xRadio.cpp b/zephcore/adapters/radio/SX127xRadio.cpp index 33ee35d..e9ee4de 100644 --- a/zephcore/adapters/radio/SX127xRadio.cpp +++ b/zephcore/adapters/radio/SX127xRadio.cpp @@ -32,6 +32,10 @@ SX127xRadio::SX127xRadio(const struct device *lora_dev, MainBoard &board, { /* SX127x has no RX boost feature — start with boost disabled */ _rx_boost_enabled = false; + /* loramac-node requires full lora_config() on every TX/RX direction + * change — Radio.SetTxConfig() and Radio.SetRxConfig() configure + * completely disjoint internal state in the loramac-node library. */ + _loramac_node = true; } void SX127xRadio::begin() @@ -91,8 +95,18 @@ void SX127xRadio::hwResetAGC() { /* The loramac-node SX127x driver manages AGC recalibration internally * (RadioSetRxConfig re-programs all gain registers on every RX config - * call). No explicit AGC reset is needed or possible via the - * standard API. The base class will restart RX after this call. */ + * call). No explicit AGC reset is needed or possible via the standard + * API. */ +} + +void SX127xRadio::resetAGC() +{ + /* hwResetAGC() is a no-op, so skip the base-class resetAGC() entirely. + * The base class calls startReceive() after hwResetAGC(), but the + * loramac-node modem mutex (STATE_BUSY during async RX) causes + * lora_recv_async() to return -EBUSY, setting _in_recv_mode = 0 and + * corrupting the state machine. The loramac-node driver self-manages + * AGC, so nothing needs to happen here. */ } } /* namespace mesh */ diff --git a/zephcore/adapters/radio/SX127xRadio.h b/zephcore/adapters/radio/SX127xRadio.h index 1d8e3eb..1e9ef7b 100644 --- a/zephcore/adapters/radio/SX127xRadio.h +++ b/zephcore/adapters/radio/SX127xRadio.h @@ -43,6 +43,12 @@ protected: /* SX127x loramac-node driver manages AGC automatically. No-op. */ void hwResetAGC() override; + /* Override public resetAGC(): hwResetAGC() is a no-op, and the + * loramac-node modem mutex makes the base-class startReceive() call + * fail with -EBUSY (modem STATE_BUSY during async RX), which would + * set _in_recv_mode = 0 and corrupt the state machine. */ + void resetAGC() override; + /* SX127x has no BUSY pin. Default (false) from base is correct. */ /* bool hwIsChipBusy() — inherited, returns false */ }; diff --git a/zephcore/patches/modules/loramac-node/0001-sx1276-bw62k5.patch b/zephcore/patches/modules/loramac-node/0001-sx1276-bw62k5.patch new file mode 100644 index 0000000..ce4a1c2 --- /dev/null +++ b/zephcore/patches/modules/loramac-node/0001-sx1276-bw62k5.patch @@ -0,0 +1,85 @@ +diff --git a/src/radio/sx1276/sx1276.c b/src/radio/sx1276/sx1276.c +index 7dd9b122..3ea5320d 100644 +--- a/src/radio/sx1276/sx1276.c ++++ b/src/radio/sx1276/sx1276.c +@@ -523,12 +523,14 @@ void SX1276SetRxConfig( RadioModems_t modem, uint32_t bandwidth, + break; + case MODEM_LORA: + { +- if( bandwidth > 2 ) ++ if( bandwidth > 3 ) + { +- // Fatal error: When using LoRa modem only bandwidths 125, 250 and 500 kHz are supported ++ // Fatal error: When using LoRa modem only bandwidths 62.5, 125, 250 and 500 kHz are supported + while( 1 ); + } +- bandwidth += 7; ++ // Map loramac-node index to SX1276 BW register value: ++ // 0=125kHz->7, 1=250kHz->8, 2=500kHz->9, 3=62.5kHz->6 ++ bandwidth = ( bandwidth == 3 ) ? 6 : ( bandwidth + 7 ); + SX1276.Settings.LoRa.Bandwidth = bandwidth; + SX1276.Settings.LoRa.Datarate = datarate; + SX1276.Settings.LoRa.Coderate = coderate; +@@ -550,7 +552,9 @@ void SX1276SetRxConfig( RadioModems_t modem, uint32_t bandwidth, + datarate = 6; + } + +- if( ( ( bandwidth == 7 ) && ( ( datarate == 11 ) || ( datarate == 12 ) ) ) || ++ // BW 62.5kHz (reg 6): LDRO required for SF10/11/12 (symbol time >16ms) ++ if( ( ( bandwidth == 6 ) && ( ( datarate == 10 ) || ( datarate == 11 ) || ( datarate == 12 ) ) ) || ++ ( ( bandwidth == 7 ) && ( ( datarate == 11 ) || ( datarate == 12 ) ) ) || + ( ( bandwidth == 8 ) && ( datarate == 12 ) ) ) + { + SX1276.Settings.LoRa.LowDatarateOptimize = 0x01; +@@ -687,12 +691,14 @@ void SX1276SetTxConfig( RadioModems_t modem, int8_t power, uint32_t fdev, + case MODEM_LORA: + { + SX1276.Settings.LoRa.Power = power; +- if( bandwidth > 2 ) ++ if( bandwidth > 3 ) + { +- // Fatal error: When using LoRa modem only bandwidths 125, 250 and 500 kHz are supported ++ // Fatal error: When using LoRa modem only bandwidths 62.5, 125, 250 and 500 kHz are supported + while( 1 ); + } +- bandwidth += 7; ++ // Map loramac-node index to SX1276 BW register value: ++ // 0=125kHz->7, 1=250kHz->8, 2=500kHz->9, 3=62.5kHz->6 ++ bandwidth = ( bandwidth == 3 ) ? 6 : ( bandwidth + 7 ); + SX1276.Settings.LoRa.Bandwidth = bandwidth; + SX1276.Settings.LoRa.Datarate = datarate; + SX1276.Settings.LoRa.Coderate = coderate; +@@ -712,7 +718,9 @@ void SX1276SetTxConfig( RadioModems_t modem, int8_t power, uint32_t fdev, + { + datarate = 6; + } +- if( ( ( bandwidth == 7 ) && ( ( datarate == 11 ) || ( datarate == 12 ) ) ) || ++ // BW 62.5kHz (reg 6): LDRO required for SF10/11/12 (symbol time >16ms) ++ if( ( ( bandwidth == 6 ) && ( ( datarate == 10 ) || ( datarate == 11 ) || ( datarate == 12 ) ) ) || ++ ( ( bandwidth == 7 ) && ( ( datarate == 11 ) || ( datarate == 12 ) ) ) || + ( ( bandwidth == 8 ) && ( datarate == 12 ) ) ) + { + SX1276.Settings.LoRa.LowDatarateOptimize = 0x01; +@@ -1437,6 +1445,9 @@ static uint32_t SX1276GetLoRaBandwidthInHz( uint32_t bw ) + case 2: // 500 kHz + bandwidthInHz = 500000UL; + break; ++ case 3: // 62.5 kHz ++ bandwidthInHz = 62500UL; ++ break; + } + + return bandwidthInHz; +@@ -1475,8 +1486,11 @@ static uint32_t SX1276GetLoRaTimeOnAirNumerator( uint32_t bandwidth, + } + } + ++ // index 0=125kHz: LDRO for SF11/12; index 1=250kHz: LDRO for SF12 ++ // index 3=62.5kHz: LDRO for SF10/11/12 (symbol time >16ms) + if( ( ( bandwidth == 0 ) && ( ( datarate == 11 ) || ( datarate == 12 ) ) ) || +- ( ( bandwidth == 1 ) && ( datarate == 12 ) ) ) ++ ( ( bandwidth == 1 ) && ( datarate == 12 ) ) || ++ ( ( bandwidth == 3 ) && ( ( datarate == 10 ) || ( datarate == 11 ) || ( datarate == 12 ) ) ) ) + { + lowDatareOptimize = true; + }