mirror of
https://github.com/alexhopeoconnor/firmware.git
synced 2026-10-13 15:11:20 +10:00
* fix: apply all LoRa config changes live without rebooting All LoRa radio settings (SF, BW, CR, frequency, power, preset, sx126x_rx_boosted_gain) now apply immediately via reconfigure() without requiring a node reboot. - AdminModule: requiresReboot = false for all LoRa config changes; LoRa changes were already handled by the configChanged observer calling reconfigure() but the reboot flag was set unnecessarily - AdminModule: validate LORA_24 region against radio hardware at config time; reject with BAD_REQUEST if hardware lacks 2.4 GHz capability (wideLora() returns false or no radio instance) - SX126xInterface/LR11x0Interface: apply sx126x_rx_boosted_gain in reconfigure(); register 0x08AC is writable in STDBY mode (SX1261/2 datasheet §9.6); retention registers written so setting survives warm-sleep cycles; log warning on setter failure - DebugRenderer: show BW/SF/CR on debug screen when custom modem is active instead of the preset name - DisplayFormatters: clarify comment on getModemPresetDisplayName * fix: remove redundant reboot after LoRa config changes in on-device menus Region, frequency slot, and radio preset pickers in MenuHandler all called reloadConfig() then immediately set rebootAtMsec. reloadConfig() already fires the configChanged observer which calls reconfigure(), so the forced reboot was unnecessary — same rationale as the parent commit. * fix: guard LORA_24 region selection against hardware capability in on-device menu Without a reboot, reconfigure() now applies region changes directly. Previously getRadio() caught the LORA_24-on-sub-GHz mismatch post-reboot and reverted to UNSET — that safety net is gone. Add an explicit wideLora() check in LoraRegionPicker so sub-GHz-only hardware silently ignores LORA_24 selection instead of attempting a live reconfigure with an invalid frequency. --------- Co-authored-by: elwimen <elwimen@users.noreply.github.com> Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
359 lines
13 KiB
C++
359 lines
13 KiB
C++
#if RADIOLIB_EXCLUDE_LR11X0 != 1
|
|
#include "LR11x0Interface.h"
|
|
#include "Throttle.h"
|
|
#include "configuration.h"
|
|
#include "error.h"
|
|
#include "mesh/NodeDB.h"
|
|
#ifdef LR11X0_DIO_AS_RF_SWITCH
|
|
#include "rfswitch.h"
|
|
#elif ARCH_PORTDUINO
|
|
#include "PortduinoGlue.h"
|
|
#define rfswitch_dio_pins portduino_config.rfswitch_dio_pins
|
|
#define rfswitch_table portduino_config.rfswitch_table
|
|
#else
|
|
static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC};
|
|
static const Module::RfSwitchMode_t rfswitch_table[] = {
|
|
{LR11x0::MODE_STBY, {}}, {LR11x0::MODE_RX, {}}, {LR11x0::MODE_TX, {}}, {LR11x0::MODE_TX_HP, {}},
|
|
{LR11x0::MODE_TX_HF, {}}, {LR11x0::MODE_GNSS, {}}, {LR11x0::MODE_WIFI, {}}, END_OF_MODE_TABLE,
|
|
};
|
|
#endif
|
|
|
|
// Particular boards might define a different max power based on what their hardware can do, default to max power output if not
|
|
// specified (may be dangerous if using external PA and LR11x0 power config forgotten)
|
|
#if ARCH_PORTDUINO
|
|
#define LR1110_MAX_POWER portduino_config.lr1110_max_power
|
|
#endif
|
|
#ifndef LR1110_MAX_POWER
|
|
#define LR1110_MAX_POWER 22
|
|
#endif
|
|
|
|
// the 2.4G part maxes at 13dBm
|
|
|
|
#if ARCH_PORTDUINO
|
|
#define LR1120_MAX_POWER portduino_config.lr1120_max_power
|
|
#endif
|
|
#ifndef LR1120_MAX_POWER
|
|
#define LR1120_MAX_POWER 13
|
|
#endif
|
|
|
|
template <typename T>
|
|
LR11x0Interface<T>::LR11x0Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
|
|
RADIOLIB_PIN_TYPE busy)
|
|
: RadioLibInterface(hal, cs, irq, rst, busy, &lora), lora(&module)
|
|
{
|
|
LOG_WARN("LR11x0Interface(cs=%d, irq=%d, rst=%d, busy=%d)", cs, irq, rst, busy);
|
|
}
|
|
|
|
/// Initialise the Driver transport hardware and software.
|
|
/// Make sure the Driver is properly configured before calling init().
|
|
/// \return true if initialisation succeeded.
|
|
template <typename T> bool LR11x0Interface<T>::init()
|
|
{
|
|
#ifdef LR11X0_POWER_EN
|
|
pinMode(LR11X0_POWER_EN, OUTPUT);
|
|
digitalWrite(LR11X0_POWER_EN, HIGH);
|
|
#endif
|
|
|
|
#if ARCH_PORTDUINO
|
|
float tcxoVoltage = (float)portduino_config.dio3_tcxo_voltage / 1000;
|
|
// FIXME: correct logic to default to not using TCXO if no voltage is specified for LR11x0_DIO3_TCXO_VOLTAGE
|
|
#elif !defined(LR11X0_DIO3_TCXO_VOLTAGE)
|
|
float tcxoVoltage =
|
|
0; // "TCXO reference voltage to be set on DIO3. Defaults to 1.6 V, set to 0 to skip." per
|
|
// https://github.com/jgromes/RadioLib/blob/690a050ebb46e6097c5d00c371e961c1caa3b52e/src/modules/LR11x0/LR11x0.h#L471C26-L471C104
|
|
// (DIO3 is free to be used as an IRQ)
|
|
LOG_DEBUG("LR11X0_DIO3_TCXO_VOLTAGE not defined, not using DIO3 as TCXO reference voltage");
|
|
#else
|
|
float tcxoVoltage = LR11X0_DIO3_TCXO_VOLTAGE;
|
|
LOG_DEBUG("LR11X0_DIO3_TCXO_VOLTAGE defined, using DIO3 as TCXO reference voltage at %f V", LR11X0_DIO3_TCXO_VOLTAGE);
|
|
// (DIO3 is not free to be used as an IRQ)
|
|
#endif
|
|
|
|
RadioLibInterface::init();
|
|
|
|
limitPower(LR1110_MAX_POWER);
|
|
|
|
if ((power > LR1120_MAX_POWER) &&
|
|
(config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { // clamp again if wide freq range
|
|
power = LR1120_MAX_POWER;
|
|
preambleLength = 12; // 12 is the default for operation above 2GHz
|
|
}
|
|
|
|
#ifdef LR11X0_RF_SWITCH_SUBGHZ
|
|
pinMode(LR11X0_RF_SWITCH_SUBGHZ, OUTPUT);
|
|
digitalWrite(LR11X0_RF_SWITCH_SUBGHZ, getFreq() < 1e9 ? HIGH : LOW);
|
|
LOG_DEBUG("Set RF0 switch to %s", getFreq() < 1e9 ? "SubGHz" : "2.4GHz");
|
|
#endif
|
|
|
|
#ifdef LR11X0_RF_SWITCH_2_4GHZ
|
|
pinMode(LR11X0_RF_SWITCH_2_4GHZ, OUTPUT);
|
|
digitalWrite(LR11X0_RF_SWITCH_2_4GHZ, getFreq() < 1e9 ? LOW : HIGH);
|
|
LOG_DEBUG("Set RF1 switch to %s", getFreq() < 1e9 ? "SubGHz" : "2.4GHz");
|
|
#endif
|
|
|
|
// Allow extra time for TCXO to stabilize after power-on
|
|
delay(10);
|
|
|
|
int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage);
|
|
|
|
// Retry if we get SPI command failed - some units need extra TCXO stabilization time
|
|
if (res == RADIOLIB_ERR_SPI_CMD_FAILED) {
|
|
LOG_WARN("LR11x0 init failed with %d (SPI_CMD_FAILED), retrying after delay...", res);
|
|
delay(100);
|
|
res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage);
|
|
}
|
|
|
|
// \todo Display actual typename of the adapter, not just `LR11x0`
|
|
LOG_INFO("LR11x0 init result %d", res);
|
|
if (res == RADIOLIB_ERR_CHIP_NOT_FOUND || res == RADIOLIB_ERR_SPI_CMD_FAILED)
|
|
return false;
|
|
|
|
LR11x0VersionInfo_t version;
|
|
res = lora.getVersionInfo(&version);
|
|
if (res == RADIOLIB_ERR_NONE)
|
|
LOG_DEBUG("LR11x0 Device %d, HW %d, FW %d.%d, WiFi %d.%d, GNSS %d.%d", version.device, version.hardware, version.fwMajor,
|
|
version.fwMinor, version.fwMajorWiFi, version.fwMinorWiFi, version.fwGNSS, version.almanacGNSS);
|
|
|
|
LOG_INFO("Frequency set to %f", getFreq());
|
|
LOG_INFO("Bandwidth set to %f", bw);
|
|
LOG_INFO("Power output set to %d", power);
|
|
|
|
if (res == RADIOLIB_ERR_NONE)
|
|
res = lora.setCRC(2);
|
|
|
|
// FIXME: May want to set depending on a definition, currently all LR1110 variant files use the DC-DC regulator option
|
|
if (res == RADIOLIB_ERR_NONE)
|
|
res = lora.setRegulatorDCDC();
|
|
|
|
#ifdef LR11X0_DIO_AS_RF_SWITCH
|
|
bool dioAsRfSwitch = true;
|
|
#elif defined(ARCH_PORTDUINO)
|
|
bool dioAsRfSwitch = portduino_config.has_rfswitch_table;
|
|
#else
|
|
bool dioAsRfSwitch = false;
|
|
#endif
|
|
|
|
if (dioAsRfSwitch) {
|
|
lora.setRfSwitchTable(rfswitch_dio_pins, rfswitch_table);
|
|
LOG_DEBUG("Set DIO RF switch");
|
|
}
|
|
|
|
if (res == RADIOLIB_ERR_NONE) {
|
|
if (config.lora.sx126x_rx_boosted_gain) { // the name is unfortunate but historically accurate
|
|
res = lora.setRxBoostedGainMode(true);
|
|
LOG_INFO("Set RX gain to boosted mode; result: %d", res);
|
|
} else {
|
|
res = lora.setRxBoostedGainMode(false);
|
|
LOG_INFO("Set RX gain to power saving mode (boosted mode off); result: %d", res);
|
|
}
|
|
}
|
|
|
|
if (res == RADIOLIB_ERR_NONE)
|
|
startReceive(); // start receiving
|
|
|
|
return res == RADIOLIB_ERR_NONE;
|
|
}
|
|
|
|
template <typename T> bool LR11x0Interface<T>::reconfigure()
|
|
{
|
|
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, wideLora() && (getFreq() > 1000.0f));
|
|
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);
|
|
|
|
err = lora.setPreambleLength(preambleLength);
|
|
assert(err == RADIOLIB_ERR_NONE);
|
|
|
|
err = lora.setFrequency(getFreq());
|
|
if (err != RADIOLIB_ERR_NONE)
|
|
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
|
|
|
if (power > LR1110_MAX_POWER) // This chip has lower power limits than some
|
|
power = LR1110_MAX_POWER;
|
|
if ((power > LR1120_MAX_POWER) && (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) // 2.4G power limit
|
|
power = LR1120_MAX_POWER;
|
|
|
|
err = lora.setOutputPower(power);
|
|
assert(err == RADIOLIB_ERR_NONE);
|
|
|
|
// Apply RX gain mode — valid in STDBY, matches resetAGC() pattern
|
|
err = lora.setRxBoostedGainMode(config.lora.sx126x_rx_boosted_gain);
|
|
if (err != RADIOLIB_ERR_NONE)
|
|
LOG_WARN("LR11x0 setRxBoostedGainMode %s%d", radioLibErr, err);
|
|
|
|
startReceive(); // restart receiving
|
|
|
|
return RADIOLIB_ERR_NONE;
|
|
}
|
|
|
|
template <typename T> void LR11x0Interface<T>::disableInterrupt()
|
|
{
|
|
lora.clearIrqAction();
|
|
}
|
|
|
|
template <typename T> void LR11x0Interface<T>::setStandby()
|
|
{
|
|
checkNotification(); // handle any pending interrupts before we force standby
|
|
|
|
int err = lora.standby();
|
|
|
|
if (err != RADIOLIB_ERR_NONE) {
|
|
LOG_DEBUG("LR11x0 standby failed with error %d", err);
|
|
}
|
|
|
|
assert(err == RADIOLIB_ERR_NONE);
|
|
|
|
isReceiving = false; // If we were receiving, not any more
|
|
activeReceiveStart = 0;
|
|
disableInterrupt();
|
|
completeSending(); // If we were sending, not anymore
|
|
RadioLibInterface::setStandby();
|
|
}
|
|
|
|
/**
|
|
* Add SNR data to received messages
|
|
*/
|
|
template <typename T> void LR11x0Interface<T>::addReceiveMetadata(meshtastic_MeshPacket *mp)
|
|
{
|
|
// LOG_DEBUG("PacketStatus %x", lora.getPacketStatus());
|
|
mp->rx_snr = lora.getSNR();
|
|
mp->rx_rssi = lround(lora.getRSSI());
|
|
LOG_DEBUG("Corrected frequency offset: %f", lora.getFrequencyError());
|
|
}
|
|
|
|
/** We override to turn on transmitter power as needed.
|
|
*/
|
|
template <typename T> void LR11x0Interface<T>::configHardwareForSend()
|
|
{
|
|
RadioLibInterface::configHardwareForSend();
|
|
}
|
|
|
|
// For power draw measurements, helpful to force radio to stay sleeping
|
|
// #define SLEEP_ONLY
|
|
|
|
template <typename T> void LR11x0Interface<T>::startReceive()
|
|
{
|
|
#ifdef SLEEP_ONLY
|
|
sleep();
|
|
#else
|
|
|
|
setStandby();
|
|
|
|
lora.setPreambleLength(preambleLength); // Solve RX ack fail after direct message sent. Not sure why this is needed.
|
|
|
|
// We use a 16 bit preamble so this should save some power by letting radio sit in standby mostly.
|
|
int err =
|
|
lora.startReceive(RADIOLIB_LR11X0_RX_TIMEOUT_INF, MESHTASTIC_RADIOLIB_IRQ_RX_FLAGS, RADIOLIB_IRQ_RX_DEFAULT_MASK, 0);
|
|
if (err)
|
|
LOG_ERROR("StartReceive error: %d", err);
|
|
assert(err == RADIOLIB_ERR_NONE);
|
|
|
|
RadioLibInterface::startReceive();
|
|
|
|
// Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits
|
|
enableInterrupt(isrRxLevel0);
|
|
checkRxDoneIrqFlag();
|
|
#endif
|
|
}
|
|
|
|
/** Is the channel currently active? */
|
|
template <typename T> bool LR11x0Interface<T>::isChannelActive()
|
|
{
|
|
// check if we can detect a LoRa preamble on the current channel
|
|
ChannelScanConfig_t cfg = {.cad = {.symNum = NUM_SYM_CAD,
|
|
.detPeak = RADIOLIB_LR11X0_CAD_PARAM_DEFAULT,
|
|
.detMin = RADIOLIB_LR11X0_CAD_PARAM_DEFAULT,
|
|
.exitMode = RADIOLIB_LR11X0_CAD_PARAM_DEFAULT,
|
|
.timeout = 0,
|
|
.irqFlags = RADIOLIB_IRQ_CAD_DEFAULT_FLAGS,
|
|
.irqMask = RADIOLIB_IRQ_CAD_DEFAULT_MASK}};
|
|
int16_t result;
|
|
|
|
setStandby();
|
|
result = lora.scanChannel(cfg);
|
|
if (result == RADIOLIB_LORA_DETECTED)
|
|
return true;
|
|
|
|
assert(result != RADIOLIB_ERR_WRONG_MODEM);
|
|
|
|
return false;
|
|
}
|
|
|
|
/** Could we send right now (i.e. either not actively receiving or transmitting)? */
|
|
template <typename T> bool LR11x0Interface<T>::isActivelyReceiving()
|
|
{
|
|
// The IRQ status will be cleared when we start our read operation. Check if we've started a header, but haven't yet
|
|
// received and handled the interrupt for reading the packet/handling errors.
|
|
return receiveDetected(lora.getIrqStatus(), RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID,
|
|
RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED);
|
|
}
|
|
|
|
#ifdef LR11X0_AGC_RESET
|
|
template <typename T> void LR11x0Interface<T>::resetAGC()
|
|
{
|
|
// Safety: don't reset mid-packet
|
|
if (sendingPacket != NULL || (isReceiving && isActivelyReceiving()))
|
|
return;
|
|
|
|
LOG_DEBUG("LR11x0 AGC reset: warm sleep + Calibrate(0x3F)");
|
|
|
|
// 1. Warm sleep — powers down the analog frontend, resetting AGC state
|
|
lora.sleep(true, 0);
|
|
|
|
// 2. Wake to RC standby for stable calibration
|
|
lora.standby(RADIOLIB_LR11X0_STANDBY_RC, true);
|
|
|
|
// 3. Calibrate all blocks (PLL, ADC, image, RC oscillators)
|
|
// calibrate() is protected on LR11x0, so use raw SPI (same as internal implementation)
|
|
uint8_t calData = RADIOLIB_LR11X0_CALIBRATE_ALL;
|
|
module.SPIwriteStream(RADIOLIB_LR11X0_CMD_CALIBRATE, &calData, 1, true, true);
|
|
|
|
// 4. Re-calibrate image rejection for actual operating frequency
|
|
// Calibrate(0x3F) defaults to 902-928 MHz which is wrong for other regions.
|
|
lora.calibrateImageRejection(getFreq() - 4.0f, getFreq() + 4.0f);
|
|
|
|
// 5. Re-apply RX boosted gain mode
|
|
lora.setRxBoostedGainMode(config.lora.sx126x_rx_boosted_gain);
|
|
|
|
// 6. Resume receiving
|
|
startReceive();
|
|
}
|
|
#endif
|
|
|
|
template <typename T> bool LR11x0Interface<T>::sleep()
|
|
{
|
|
// \todo Display actual typename of the adapter, not just `LR11x0`
|
|
LOG_DEBUG("LR11x0 entering sleep mode");
|
|
setStandby(); // Stop any pending operations
|
|
|
|
// turn off TCXO if it was powered
|
|
lora.setTCXO(0);
|
|
|
|
// put chipset into sleep mode (we've already disabled interrupts by now)
|
|
bool keepConfig = false;
|
|
lora.sleep(keepConfig, 0); // Note: we do not keep the config, full reinit will be needed
|
|
|
|
#ifdef LR11X0_POWER_EN
|
|
digitalWrite(LR11X0_POWER_EN, LOW);
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
#endif
|