mirror of
https://github.com/alexhopeoconnor/firmware.git
synced 2026-10-04 03:18:10 +10:00
fix(lr2021): live LF↔HF reconfigure via full begin() (#11279)
* fix(lr2021): full begin() on live LF/HF reconfigure.Avoid RadioLib -706 / assert when live-switching Sub-GHz ↔ LORA_24. * fix(lr2021): align band-hop begin() with init() robustnessMirror. RF-switch GPIOs, SPI/TCXO retries, and log CRC/RX-gain errors. * chore: trunk fmt LR20x0Interface bandHop line wrap * fix(lr2021): harden live band reconfigure (companion #1) * fix(lr2021): reject invalid freq in band-hop path select Require requestedMHz > 0 in isLr20x0BandHop, expose lr20x0ReconfigurePathfor FullBegin vs incremental selection, and extend native radio tests. --------- Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
This commit is contained in:
co-authored by
Thomas Göttgens
parent
74b9f6ff02
commit
08722da3d9
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
constexpr bool isLr20x0HighBand(float frequencyMHz)
|
||||
{
|
||||
return frequencyMHz > 1500.0f;
|
||||
}
|
||||
|
||||
constexpr bool isLr20x0BandHop(float previousMHz, float requestedMHz)
|
||||
{
|
||||
return previousMHz > 0.0f && requestedMHz > 0.0f &&
|
||||
isLr20x0HighBand(previousMHz) != isLr20x0HighBand(requestedMHz);
|
||||
}
|
||||
|
||||
// Path taken by LR20x0Interface::reconfigure() for a frequency change.
|
||||
enum class Lr20x0ReconfigurePath : uint8_t { Incremental, FullBegin };
|
||||
|
||||
constexpr Lr20x0ReconfigurePath lr20x0ReconfigurePath(float previousMHz, float requestedMHz)
|
||||
{
|
||||
return isLr20x0BandHop(previousMHz, requestedMHz) ? Lr20x0ReconfigurePath::FullBegin
|
||||
: Lr20x0ReconfigurePath::Incremental;
|
||||
}
|
||||
+138
-36
@@ -1,6 +1,7 @@
|
||||
#include "configuration.h"
|
||||
|
||||
#if (defined(USE_LR2021) || defined(ARCH_PORTDUINO)) && RADIOLIB_EXCLUDE_LR2021 != 1
|
||||
#include "LR20x0Band.h"
|
||||
#include "LR20x0Interface.h"
|
||||
#include "error.h"
|
||||
#include "mesh/NodeDB.h"
|
||||
@@ -42,6 +43,9 @@ static const Module::RfSwitchMode_t lr20x0_rfswitch_table[] = {
|
||||
#define LR2021_MAX_POWER_HF 12
|
||||
#endif
|
||||
|
||||
// Last programmed carrier; LF/HF hops use full begin() (live setOutputPower returns -706).
|
||||
static float lr20x0LastFreqMHz = 0;
|
||||
|
||||
template <typename T>
|
||||
LR20x0Interface<T>::LR20x0Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
|
||||
RADIOLIB_PIN_TYPE busy)
|
||||
@@ -98,14 +102,14 @@ template <typename T> bool LR20x0Interface<T>::init()
|
||||
|
||||
#ifdef LR2021_RF_SWITCH_SUBGHZ
|
||||
pinMode(LR2021_RF_SWITCH_SUBGHZ, OUTPUT);
|
||||
digitalWrite(LR2021_RF_SWITCH_SUBGHZ, getFreq() < 1e9 ? HIGH : LOW);
|
||||
LOG_DEBUG("Set RF0 switch to %s", getFreq() < 1e9 ? "SubGHz" : "2.4GHz");
|
||||
digitalWrite(LR2021_RF_SWITCH_SUBGHZ, isLr20x0HighBand(getFreq()) ? LOW : HIGH);
|
||||
LOG_DEBUG("Set RF0 switch to %s", isLr20x0HighBand(getFreq()) ? "2.4GHz" : "SubGHz");
|
||||
#endif
|
||||
|
||||
#ifdef LR2021_RF_SWITCH_2_4GHZ
|
||||
pinMode(LR2021_RF_SWITCH_2_4GHZ, OUTPUT);
|
||||
digitalWrite(LR2021_RF_SWITCH_2_4GHZ, getFreq() < 1e9 ? LOW : HIGH);
|
||||
LOG_DEBUG("Set RF1 switch to %s", getFreq() < 1e9 ? "SubGHz" : "2.4GHz");
|
||||
digitalWrite(LR2021_RF_SWITCH_2_4GHZ, isLr20x0HighBand(getFreq()) ? HIGH : LOW);
|
||||
LOG_DEBUG("Set RF1 switch to %s", isLr20x0HighBand(getFreq()) ? "2.4GHz" : "SubGHz");
|
||||
#endif
|
||||
|
||||
// Allow extra time for TCXO to stabilize after power-on
|
||||
@@ -169,56 +173,154 @@ template <typename T> bool LR20x0Interface<T>::init()
|
||||
if (res == RADIOLIB_ERR_NONE)
|
||||
startReceive(); // start receiving
|
||||
|
||||
lr20x0LastFreqMHz = getFreq();
|
||||
return res == RADIOLIB_ERR_NONE;
|
||||
}
|
||||
|
||||
template <typename T> bool LR20x0Interface<T>::reconfigure()
|
||||
{
|
||||
RadioLibInterface::reconfigure();
|
||||
bool success = RadioLibInterface::reconfigure();
|
||||
|
||||
// set mode to standby
|
||||
setStandby();
|
||||
|
||||
// configure publicly accessible settings
|
||||
int err = lora.setSpreadingFactor(sf);
|
||||
if (err != RADIOLIB_ERR_NONE)
|
||||
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
||||
|
||||
err = lora.setBandwidth(bw); // different form than LR11xx
|
||||
if (err != RADIOLIB_ERR_NONE)
|
||||
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
||||
|
||||
err = lora.setCodingRate(cr, cr != 7); // use long interleaving except if CR is 4/7 which doesn't support it
|
||||
if (err != RADIOLIB_ERR_NONE)
|
||||
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
||||
|
||||
err = lora.setSyncWord(syncWord);
|
||||
assert(err == RADIOLIB_ERR_NONE);
|
||||
|
||||
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24) { // clamp if wide freq range
|
||||
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24) {
|
||||
limitPower(LR2021_MAX_POWER_HF);
|
||||
} else {
|
||||
limitPower(LR2021_MAX_POWER); // default clamp for non-wide freq range
|
||||
limitPower(LR2021_MAX_POWER);
|
||||
}
|
||||
|
||||
const float freq = getFreq();
|
||||
const bool bandHop = lr20x0ReconfigurePath(lr20x0LastFreqMHz, freq) == Lr20x0ReconfigurePath::FullBegin;
|
||||
|
||||
if (bandHop) {
|
||||
LOG_INFO("LR20x0 LF/HF band hop %.1f -> %.1f MHz, full begin()", lr20x0LastFreqMHz, freq);
|
||||
setStandby();
|
||||
|
||||
// Match init(): external LF/HF front-end GPIOs (if board defines them).
|
||||
#ifdef LR2021_RF_SWITCH_SUBGHZ
|
||||
pinMode(LR2021_RF_SWITCH_SUBGHZ, OUTPUT);
|
||||
digitalWrite(LR2021_RF_SWITCH_SUBGHZ, isLr20x0HighBand(freq) ? LOW : HIGH);
|
||||
LOG_DEBUG("Set RF0 switch to %s", isLr20x0HighBand(freq) ? "2.4GHz" : "SubGHz");
|
||||
#endif
|
||||
#ifdef LR2021_RF_SWITCH_2_4GHZ
|
||||
pinMode(LR2021_RF_SWITCH_2_4GHZ, OUTPUT);
|
||||
digitalWrite(LR2021_RF_SWITCH_2_4GHZ, isLr20x0HighBand(freq) ? HIGH : LOW);
|
||||
LOG_DEBUG("Set RF1 switch to %s", isLr20x0HighBand(freq) ? "2.4GHz" : "SubGHz");
|
||||
#endif
|
||||
|
||||
#if ARCH_PORTDUINO
|
||||
float tcxoVoltage = (float)portduino_config.dio3_tcxo_voltage / 1000;
|
||||
#elif defined(LR2021_DIO3_TCXO_VOLTAGE)
|
||||
float tcxoVoltage = LR2021_DIO3_TCXO_VOLTAGE;
|
||||
#elif defined(TCXO_OPTIONAL)
|
||||
float tcxoVoltage = 1.6f;
|
||||
#else
|
||||
float tcxoVoltage = 0;
|
||||
#endif
|
||||
|
||||
delay(10); // same TCXO settle window as init()
|
||||
|
||||
int res = lora.begin(freq, bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage);
|
||||
if (res == RADIOLIB_ERR_SPI_CMD_FAILED) {
|
||||
LOG_WARN("LR20x0 band-hop begin SPI_CMD_FAILED, retrying...");
|
||||
delay(100);
|
||||
res = lora.begin(freq, bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage);
|
||||
}
|
||||
#if defined(TCXO_OPTIONAL)
|
||||
if (res != RADIOLIB_ERR_NONE && res != RADIOLIB_ERR_CHIP_NOT_FOUND && tcxoVoltage > 0) {
|
||||
LOG_WARN("LR20x0 band-hop begin TCXO failed (%s%d), retry without TCXO", radioLibErr, res);
|
||||
tcxoVoltage = 0;
|
||||
res = lora.begin(freq, bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage);
|
||||
}
|
||||
#endif
|
||||
if (res != RADIOLIB_ERR_NONE) {
|
||||
LOG_ERROR("LR20x0 band-hop begin %s%d", radioLibErr, res);
|
||||
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
||||
return false;
|
||||
}
|
||||
lr20x0LastFreqMHz = freq;
|
||||
|
||||
res = lora.setCRC(2);
|
||||
if (res != RADIOLIB_ERR_NONE) {
|
||||
LOG_ERROR("LR20x0 band-hop setCRC %s%d", radioLibErr, res);
|
||||
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef LR2021_DIO_AS_RF_SWITCH
|
||||
lora.setRfSwitchTable(lr20x0_rfswitch_dio_pins, lr20x0_rfswitch_table);
|
||||
#elif ARCH_PORTDUINO
|
||||
if (portduino_config.has_rfswitch_table)
|
||||
lora.setRfSwitchTable(lr20x0_rfswitch_dio_pins, lr20x0_rfswitch_table);
|
||||
#endif
|
||||
|
||||
res = lora.setRxBoostedGainMode(config.lora.sx126x_rx_boosted_gain);
|
||||
if (res != RADIOLIB_ERR_NONE) {
|
||||
LOG_ERROR("LR20x0 band-hop setRxBoostedGainMode %s%d", radioLibErr, res);
|
||||
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
||||
return false;
|
||||
}
|
||||
|
||||
startReceive();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Same-band reconfigure (previous incremental path)
|
||||
setStandby();
|
||||
|
||||
int err = lora.setFrequency(freq);
|
||||
if (err != RADIOLIB_ERR_NONE) {
|
||||
LOG_ERROR("LR20x0 setFrequency %.3f MHz %s%d", freq, radioLibErr, err);
|
||||
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
||||
success = false;
|
||||
}
|
||||
|
||||
err = lora.setSpreadingFactor(sf);
|
||||
if (err != RADIOLIB_ERR_NONE) {
|
||||
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
||||
success = false;
|
||||
}
|
||||
|
||||
err = lora.setBandwidth(bw);
|
||||
if (err != RADIOLIB_ERR_NONE) {
|
||||
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
||||
success = false;
|
||||
}
|
||||
|
||||
err = lora.setCodingRate(cr, cr != 7);
|
||||
if (err != RADIOLIB_ERR_NONE) {
|
||||
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
||||
success = false;
|
||||
}
|
||||
|
||||
err = lora.setSyncWord(syncWord);
|
||||
if (err != RADIOLIB_ERR_NONE) {
|
||||
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
||||
success = false;
|
||||
}
|
||||
|
||||
err = lora.setPreambleLength(preambleLength);
|
||||
assert(err == RADIOLIB_ERR_NONE);
|
||||
|
||||
err = lora.setFrequency(getFreq());
|
||||
if (err != RADIOLIB_ERR_NONE)
|
||||
if (err != RADIOLIB_ERR_NONE) {
|
||||
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
||||
success = false;
|
||||
}
|
||||
|
||||
err = lora.setOutputPower(power);
|
||||
assert(err == RADIOLIB_ERR_NONE);
|
||||
if (err != RADIOLIB_ERR_NONE) {
|
||||
LOG_ERROR("LR20x0 setOutputPower %d dBm @ %.3f MHz %s%d", power, freq, radioLibErr, err);
|
||||
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
||||
success = false;
|
||||
}
|
||||
|
||||
// Apply RX gain mode - valid in STDBY, matches resetAGC() pattern
|
||||
err = lora.setRxBoostedGainMode(config.lora.sx126x_rx_boosted_gain);
|
||||
if (err != RADIOLIB_ERR_NONE)
|
||||
if (err != RADIOLIB_ERR_NONE) {
|
||||
LOG_WARN("LR20x0 setRxBoostedGainMode %s%d", radioLibErr, err);
|
||||
success = false;
|
||||
}
|
||||
|
||||
startReceive(); // restart receiving
|
||||
|
||||
return true;
|
||||
if (success) {
|
||||
startReceive();
|
||||
lr20x0LastFreqMHz = freq;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
template <typename T> void LR20x0Interface<T>::disableInterrupt()
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "LR20x0Band.h"
|
||||
#include "MeshRadio.h"
|
||||
#include "MeshService.h"
|
||||
#include "RadioInterface.h"
|
||||
@@ -9,6 +10,47 @@
|
||||
|
||||
static MockMeshService *mockMeshService;
|
||||
|
||||
static void test_lr20x0BandClassification()
|
||||
{
|
||||
TEST_ASSERT_FALSE(isLr20x0HighBand(906.875f));
|
||||
TEST_ASSERT_FALSE(isLr20x0HighBand(1500.0f));
|
||||
TEST_ASSERT_TRUE(isLr20x0HighBand(2400.0f));
|
||||
TEST_ASSERT_TRUE(isLr20x0HighBand(2420.71875f));
|
||||
}
|
||||
|
||||
static void test_lr20x0BandHopDetection()
|
||||
{
|
||||
TEST_ASSERT_FALSE(isLr20x0BandHop(0.0f, 2420.71875f));
|
||||
TEST_ASSERT_FALSE(isLr20x0BandHop(906.875f, 915.0f));
|
||||
TEST_ASSERT_FALSE(isLr20x0BandHop(2400.0f, 2420.71875f));
|
||||
TEST_ASSERT_TRUE(isLr20x0BandHop(906.875f, 2420.71875f));
|
||||
TEST_ASSERT_TRUE(isLr20x0BandHop(2420.71875f, 906.875f));
|
||||
// Invalid requested frequency must not look like a band hop.
|
||||
TEST_ASSERT_FALSE(isLr20x0BandHop(2420.71875f, 0.0f));
|
||||
TEST_ASSERT_FALSE(isLr20x0BandHop(906.875f, 0.0f));
|
||||
TEST_ASSERT_FALSE(isLr20x0BandHop(2420.71875f, -1.0f));
|
||||
TEST_ASSERT_FALSE(isLr20x0BandHop(906.875f, -915.0f));
|
||||
}
|
||||
|
||||
static void test_lr20x0ReconfigurePathSelection()
|
||||
{
|
||||
// LF -> HF and HF -> LF take full begin(); same-band stays incremental.
|
||||
TEST_ASSERT_EQUAL(static_cast<int>(Lr20x0ReconfigurePath::FullBegin),
|
||||
static_cast<int>(lr20x0ReconfigurePath(906.875f, 2420.71875f)));
|
||||
TEST_ASSERT_EQUAL(static_cast<int>(Lr20x0ReconfigurePath::FullBegin),
|
||||
static_cast<int>(lr20x0ReconfigurePath(2420.71875f, 906.875f)));
|
||||
TEST_ASSERT_EQUAL(static_cast<int>(Lr20x0ReconfigurePath::Incremental),
|
||||
static_cast<int>(lr20x0ReconfigurePath(906.875f, 915.0f)));
|
||||
TEST_ASSERT_EQUAL(static_cast<int>(Lr20x0ReconfigurePath::Incremental),
|
||||
static_cast<int>(lr20x0ReconfigurePath(2400.0f, 2420.71875f)));
|
||||
TEST_ASSERT_EQUAL(static_cast<int>(Lr20x0ReconfigurePath::Incremental),
|
||||
static_cast<int>(lr20x0ReconfigurePath(0.0f, 2420.71875f)));
|
||||
TEST_ASSERT_EQUAL(static_cast<int>(Lr20x0ReconfigurePath::Incremental),
|
||||
static_cast<int>(lr20x0ReconfigurePath(2420.71875f, 0.0f)));
|
||||
TEST_ASSERT_EQUAL(static_cast<int>(Lr20x0ReconfigurePath::Incremental),
|
||||
static_cast<int>(lr20x0ReconfigurePath(906.875f, -1.0f)));
|
||||
}
|
||||
|
||||
// Test shim to expose protected radio parameters set by applyModemConfig()
|
||||
class TestableRadioInterface : public RadioInterface
|
||||
{
|
||||
@@ -359,6 +401,9 @@ void setup()
|
||||
initializeTestEnvironment();
|
||||
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_lr20x0BandClassification);
|
||||
RUN_TEST(test_lr20x0BandHopDetection);
|
||||
RUN_TEST(test_lr20x0ReconfigurePathSelection);
|
||||
RUN_TEST(test_bwCodeToKHz_specialMappings);
|
||||
RUN_TEST(test_bwCodeToKHz_passthrough);
|
||||
RUN_TEST(test_bwCodeToKHz_roundTrip);
|
||||
|
||||
Reference in New Issue
Block a user