Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions src/mesh/LR20x0Band.h
Original file line number Diff line number Diff line change
@@ -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;
}
160 changes: 131 additions & 29 deletions src/mesh/LR20x0Interface.cpp
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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();

if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24) {
limitPower(LR2021_MAX_POWER_HF);
} else {
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;
}

// set mode to standby
startReceive();
return true;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// Same-band reconfigure (previous incremental path)
setStandby();

// configure publicly accessible settings
int err = lora.setSpreadingFactor(sf);
if (err != RADIOLIB_ERR_NONE)
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.setBandwidth(bw); // different form than LR11xx
if (err != RADIOLIB_ERR_NONE)
err = lora.setSpreadingFactor(sf);
if (err != RADIOLIB_ERR_NONE) {
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
success = false;
}

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)
err = lora.setBandwidth(bw);
if (err != RADIOLIB_ERR_NONE) {
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
success = false;
}

err = lora.setSyncWord(syncWord);
assert(err == RADIOLIB_ERR_NONE);
err = lora.setCodingRate(cr, cr != 7);
if (err != RADIOLIB_ERR_NONE) {
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
success = false;
}

if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24) { // clamp if wide freq range
limitPower(LR2021_MAX_POWER_HF);
} else {
limitPower(LR2021_MAX_POWER); // default clamp for non-wide freq range
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()
Expand Down
45 changes: 45 additions & 0 deletions test/test_radio/test_main.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include "LR20x0Band.h"
#include "MeshRadio.h"
#include "MeshService.h"
#include "RadioInterface.h"
Expand All @@ -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)));
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// Test shim to expose protected radio parameters set by applyModemConfig()
class TestableRadioInterface : public RadioInterface
{
Expand Down Expand Up @@ -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);
Expand Down
Loading