* Fix SX126x daemon abort when tx power exceeds chip limit
reconfigure() asserted on setOutputPower() failure. `power` is derived from
operator-settable config (tx_power, SX126X_MAX_POWER); a value above the SX126x
RadioLib limit (+22 dBm) returns RADIOLIB_ERR_INVALID_OUTPUT_POWER. The assert
aborts meshtasticd mid-reconfigure -- before reloadConfig() persists the change --
so the offending config silently reverts and the daemon crash-loops. Record a
critical error and continue instead, matching the adjacent setFrequency() handling.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Address Copilot review: per-chip power limit, log requested power
- Comment no longer states a universal "+22 dBm for SX126x"; notes the
limit is the driver's max (e.g. +22 on SX1262/SX1268, lower on SX1261).
- Log the rejected `power` value and that the previous Tx power is retained,
so the invalid setting and the resulting config/radio drift are visible.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Address CodeRabbit nit: trim inline comment to repo guideline
Collapse the setOutputPower failure rationale from six lines to two,
per the repo's "one or two lines at most" comment guideline. The full
incident history lives in the PR and issue #10710.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Convert pre-existing em-dashes to ASCII (trunk ascii-dash)
Two em-dashes in this file (from bbcc35e) fail the repo's ascii-dash
formatter once the file is in a PR's modified set. `trunk fmt` rewrites
them to ASCII hyphens; applied here so the Trunk Check passes. No
functional change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
500 lines
19 KiB
C++
500 lines
19 KiB
C++
#if RADIOLIB_EXCLUDE_SX126X != 1
|
|
#include "SX126xInterface.h"
|
|
#include "configuration.h"
|
|
#include "error.h"
|
|
#include "mesh/NodeDB.h"
|
|
#ifdef ARCH_PORTDUINO
|
|
#include "PortduinoGlue.h"
|
|
#endif
|
|
#if defined(ARCH_ESP32)
|
|
#include <driver/rtc_io.h>
|
|
#include <esp_sleep.h>
|
|
#endif
|
|
|
|
#include "Throttle.h"
|
|
|
|
// 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 SX126x power config forgotten)
|
|
#if ARCH_PORTDUINO
|
|
#define SX126X_MAX_POWER portduino_config.sx126x_max_power
|
|
#endif
|
|
#ifndef SX126X_MAX_POWER
|
|
#define SX126X_MAX_POWER 22
|
|
#endif
|
|
|
|
template <typename T>
|
|
SX126xInterface<T>::SX126xInterface(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_DEBUG("SX126xInterface(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 SX126xInterface<T>::init()
|
|
{
|
|
|
|
// Typically, the RF switch on SX126x boards is controlled by two signals, which are negations of each other (switched RFIO
|
|
// paths). The negation is usually performed in hardware, or (suboptimal design) TXEN and RXEN are the two inputs to this style of
|
|
// RF switch. On some boards, there is no hardware negation between CTRL and ¬CTRL, but CTRL is internally connected to DIO2, and
|
|
// DIO2's switching is done by the SX126X itself, so the MCU can't control ¬CTRL at exactly the same time. One solution would be
|
|
// to set ¬CTRL as SX126X_TXEN or SX126X_RXEN, but they may already be used for another purpose, such as controlling another
|
|
// PA/LNA. Keeping ¬CTRL high seems to work, as long CTRL=1, ¬CTRL=1 has the opposite and stable RF path effect as CTRL=0 and
|
|
// ¬CTRL=1, this depends on the RF switch, but it seems this usually works. Better hardware design, which is done most the time,
|
|
// means this workaround is not necessary.
|
|
#ifdef SX126X_ANT_SW // Perhaps add RADIOLIB_NC check, and beforehand define as such if it is undefined, but it is not commonly
|
|
// used and not part of the 'default' set of pin definitions.
|
|
digitalWrite(SX126X_ANT_SW, HIGH);
|
|
pinMode(SX126X_ANT_SW, OUTPUT);
|
|
#endif
|
|
|
|
#ifdef SX126X_POWER_EN // Perhaps add RADIOLIB_NC check, and beforehand define as such if it is undefined, but it is not commonly
|
|
// used and not part of the 'default' set of pin definitions.
|
|
pinMode(SX126X_POWER_EN, OUTPUT);
|
|
digitalWrite(SX126X_POWER_EN, HIGH);
|
|
#endif
|
|
|
|
#if HAS_LORA_FEM
|
|
loraFEMInterface.init();
|
|
// Apply saved FEM LNA mode from config
|
|
if (loraFEMInterface.isLnaCanControl()) {
|
|
loraFEMInterface.setLNAEnable(config.lora.fem_lna_mode != meshtastic_Config_LoRaConfig_FEM_LNA_Mode_DISABLED);
|
|
}
|
|
#endif
|
|
|
|
#ifdef RF95_FAN_EN
|
|
pinMode(RF95_FAN_EN, OUTPUT);
|
|
digitalWrite(RF95_FAN_EN, HIGH);
|
|
#endif
|
|
|
|
#if ARCH_PORTDUINO
|
|
tcxoVoltage = (float)portduino_config.dio3_tcxo_voltage / 1000;
|
|
if (portduino_config.lora_sx126x_ant_sw_pin.pin != RADIOLIB_NC) {
|
|
digitalWrite(portduino_config.lora_sx126x_ant_sw_pin.pin, HIGH);
|
|
pinMode(portduino_config.lora_sx126x_ant_sw_pin.pin, OUTPUT);
|
|
}
|
|
#endif
|
|
if (tcxoVoltage == 0.0)
|
|
LOG_DEBUG("SX126X_DIO3_TCXO_VOLTAGE not defined, not using DIO3 as TCXO reference voltage");
|
|
else
|
|
LOG_DEBUG("SX126X_DIO3_TCXO_VOLTAGE defined, using DIO3 as TCXO reference voltage at %f V", tcxoVoltage);
|
|
setTransmitEnable(false);
|
|
// FIXME: May want to set depending on a definition, currently all SX126x variant files use the DC-DC regulator option
|
|
bool useRegulatorLDO = false; // Seems to depend on the connection to pin 9/DCC_SW - if an inductor DCDC?
|
|
|
|
RadioLibInterface::init();
|
|
|
|
limitPower(SX126X_MAX_POWER);
|
|
// Make sure we reach the minimum power supported to turn the chip on (-9dBm)
|
|
if (power < -9)
|
|
power = -9;
|
|
|
|
int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage, useRegulatorLDO);
|
|
|
|
#ifdef SX126X_PA_RAMP_US
|
|
// Set custom PA ramp time for boards requiring longer stabilization (e.g., T-Beam 1W needs >800us)
|
|
if (res == RADIOLIB_ERR_NONE) {
|
|
lora.setPaRampTime(SX126X_PA_RAMP_US);
|
|
}
|
|
#endif
|
|
// \todo Display actual typename of the adapter, not just `SX126x`
|
|
LOG_INFO("SX126x init result %d", res);
|
|
if (res == RADIOLIB_ERR_CHIP_NOT_FOUND || res == RADIOLIB_ERR_SPI_CMD_FAILED)
|
|
return false;
|
|
|
|
LOG_INFO("Frequency set to %f", getFreq());
|
|
LOG_INFO("Bandwidth set to %f", bw);
|
|
LOG_INFO("Power output set to %d", power);
|
|
|
|
// Overriding current limit
|
|
// (https://github.com/jgromes/RadioLib/blob/690a050ebb46e6097c5d00c371e961c1caa3b52e/src/modules/SX126x/SX126x.cpp#L85) using
|
|
// value in SX126xInterface.h (currently 140 mA) It may or may not be necessary, depending on how RadioLib functions, from
|
|
// SX1261/2 datasheet: OCP after setting DeviceSel with SetPaConfig(): SX1261 - 60 mA, SX1262 - 140 mA For the SX1268 the IC
|
|
// defaults to 140mA no matter the set power level, but RadioLib set it lower, this would need further checking Default values
|
|
// are: SX1262, SX1268: 0x38 (140 mA), SX1261: 0x18 (60 mA)
|
|
// FIXME: Not ideal to increase SX1261 current limit above 60mA as it can only transmit max 15dBm, should probably only do it
|
|
// if using SX1262 or SX1268
|
|
res = lora.setCurrentLimit(currentLimit);
|
|
LOG_DEBUG("Current limit set to %f", currentLimit);
|
|
LOG_DEBUG("Current limit set result %d", res);
|
|
|
|
if (res == RADIOLIB_ERR_NONE) {
|
|
#ifdef SX126X_DIO2_AS_RF_SWITCH
|
|
bool dio2AsRfSwitch = true;
|
|
#elif defined(ARCH_PORTDUINO)
|
|
bool dio2AsRfSwitch = false;
|
|
if (portduino_config.dio2_as_rf_switch) {
|
|
dio2AsRfSwitch = true;
|
|
}
|
|
#else
|
|
bool dio2AsRfSwitch = false;
|
|
#endif
|
|
res = lora.setDio2AsRfSwitch(dio2AsRfSwitch);
|
|
LOG_DEBUG("Set DIO2 as %sRF switch, result: %d", dio2AsRfSwitch ? "" : "not ", res);
|
|
}
|
|
|
|
// If a pin isn't defined, we set it to RADIOLIB_NC, it is safe to always do external RF switching with RADIOLIB_NC as it has
|
|
// no effect
|
|
#if ARCH_PORTDUINO
|
|
if (res == RADIOLIB_ERR_NONE) {
|
|
LOG_DEBUG("Use MCU pin %i as RXEN and pin %i as TXEN to control RF switching", portduino_config.lora_rxen_pin.pin,
|
|
portduino_config.lora_txen_pin.pin);
|
|
lora.setRfSwitchPins(portduino_config.lora_rxen_pin.pin, portduino_config.lora_txen_pin.pin);
|
|
}
|
|
#else
|
|
#ifndef SX126X_RXEN
|
|
#define SX126X_RXEN RADIOLIB_NC
|
|
LOG_DEBUG("SX126X_RXEN not defined, defaulting to RADIOLIB_NC");
|
|
#endif
|
|
#ifndef SX126X_TXEN
|
|
#define SX126X_TXEN RADIOLIB_NC
|
|
LOG_DEBUG("SX126X_TXEN not defined, defaulting to RADIOLIB_NC");
|
|
#endif
|
|
if (res == RADIOLIB_ERR_NONE) {
|
|
LOG_DEBUG("Use MCU pin %i as RXEN and pin %i as TXEN to control RF switching", SX126X_RXEN, SX126X_TXEN);
|
|
lora.setRfSwitchPins(SX126X_RXEN, SX126X_TXEN);
|
|
}
|
|
#endif
|
|
if (config.lora.sx126x_rx_boosted_gain) {
|
|
uint16_t result = lora.setRxBoostedGainMode(true);
|
|
LOG_INFO("Set RX gain to boosted mode; result: %d", result);
|
|
} else {
|
|
uint16_t result = lora.setRxBoostedGainMode(false);
|
|
LOG_INFO("Set RX gain to power saving mode (boosted mode off); result: %d", result);
|
|
}
|
|
|
|
// Undocumented SX1262 register patch recommended by Heltec/Semtech for improved RX sensitivity.
|
|
// Sets bit 0 of register 0x8B5.
|
|
if (module.SPIsetRegValue(0x8B5, 0x01, 0, 0) == RADIOLIB_ERR_NONE) {
|
|
LOG_INFO("Applied SX1262 register 0x8B5 patch for RX improvement");
|
|
} else {
|
|
LOG_WARN("Failed to apply SX1262 register 0x8B5 patch for RX improvement");
|
|
}
|
|
|
|
#if 0
|
|
// Read/write a register we are not using (only used for FSK mode) to test SPI comms
|
|
uint8_t crcLSB = 0;
|
|
int err = lora.readRegister(SX126X_REG_CRC_POLYNOMIAL_LSB, &crcLSB, 1);
|
|
if(err != RADIOLIB_ERR_NONE)
|
|
RECORD_CRITICALERROR(CriticalErrorCode_SX1262Failure);
|
|
|
|
//if(crcLSB != 0x0f)
|
|
// RECORD_CRITICALERROR(CriticalErrorCode_SX1262Failure);
|
|
|
|
crcLSB = 0x5a;
|
|
err = lora.writeRegister(SX126X_REG_CRC_POLYNOMIAL_LSB, &crcLSB, 1);
|
|
if(err != RADIOLIB_ERR_NONE)
|
|
RECORD_CRITICALERROR(CriticalErrorCode_SX1262Failure);
|
|
|
|
err = lora.readRegister(SX126X_REG_CRC_POLYNOMIAL_LSB, &crcLSB, 1);
|
|
if(err != RADIOLIB_ERR_NONE)
|
|
RECORD_CRITICALERROR(CriticalErrorCode_SX1262Failure);
|
|
|
|
if(crcLSB != 0x5a)
|
|
RECORD_CRITICALERROR(CriticalErrorCode_SX1262Failure);
|
|
// If we got this far register accesses (and therefore SPI comms) are good
|
|
#endif
|
|
|
|
if (res == RADIOLIB_ERR_NONE)
|
|
res = lora.setCRC(RADIOLIB_SX126X_LORA_CRC_ON);
|
|
|
|
if (res == RADIOLIB_ERR_NONE)
|
|
startReceive(); // start receiving
|
|
|
|
return res == RADIOLIB_ERR_NONE;
|
|
}
|
|
|
|
template <typename T> bool SX126xInterface<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);
|
|
if (err != RADIOLIB_ERR_NONE)
|
|
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
|
|
|
err = lora.setCodingRate(cr);
|
|
if (err != RADIOLIB_ERR_NONE)
|
|
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
|
|
|
err = lora.setSyncWord(syncWord);
|
|
if (err != RADIOLIB_ERR_NONE)
|
|
LOG_ERROR("SX126X setSyncWord %s%d", radioLibErr, err);
|
|
assert(err == RADIOLIB_ERR_NONE);
|
|
|
|
err = lora.setCurrentLimit(currentLimit);
|
|
if (err != RADIOLIB_ERR_NONE)
|
|
LOG_ERROR("SX126X setCurrentLimit %s%d", radioLibErr, err);
|
|
assert(err == RADIOLIB_ERR_NONE);
|
|
|
|
err = lora.setPreambleLength(preambleLength);
|
|
if (err != RADIOLIB_ERR_NONE)
|
|
LOG_ERROR("SX126X setPreambleLength %s%d", radioLibErr, err);
|
|
assert(err == RADIOLIB_ERR_NONE);
|
|
|
|
err = lora.setFrequency(getFreq());
|
|
if (err != RADIOLIB_ERR_NONE)
|
|
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
|
|
|
limitPower(SX126X_MAX_POWER);
|
|
// Make sure we reach the minimum power supported to turn the chip on (-9dBm)
|
|
if (power < -9)
|
|
power = -9;
|
|
|
|
err = lora.setOutputPower(power);
|
|
if (err != RADIOLIB_ERR_NONE) {
|
|
// Don't abort: this power is operator config (tx_power/SX126X_MAX_POWER); a value above the
|
|
// driver's max would crash the daemon before reloadConfig() persists. Flag it and keep prior power.
|
|
LOG_ERROR("SX126X setOutputPower %d dBm rejected (%s%d); keeping previous Tx power", power, radioLibErr, err);
|
|
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
|
}
|
|
|
|
// Apply RX gain mode - valid in STDBY (datasheet §9.6), matches resetAGC() pattern
|
|
err = lora.setRxBoostedGainMode(config.lora.sx126x_rx_boosted_gain);
|
|
if (err != RADIOLIB_ERR_NONE)
|
|
LOG_WARN("SX126X setRxBoostedGainMode %s%d", radioLibErr, err);
|
|
|
|
startReceive(); // restart receiving
|
|
|
|
return true;
|
|
}
|
|
|
|
template <typename T> int16_t SX126xInterface<T>::getCurrentRSSI()
|
|
{
|
|
float rssi = lora.getRSSI(false);
|
|
return (int16_t)round(rssi);
|
|
}
|
|
|
|
template <typename T> void SX126xInterface<T>::disableInterrupt()
|
|
{
|
|
lora.clearDio1Action();
|
|
}
|
|
|
|
template <typename T> void SX126xInterface<T>::setStandby()
|
|
{
|
|
checkNotification(); // handle any pending interrupts before we force standby
|
|
|
|
int err = lora.standby();
|
|
|
|
if (err != RADIOLIB_ERR_NONE)
|
|
LOG_DEBUG("SX126x standby %s%d", radioLibErr, err);
|
|
#ifdef ARCH_PORTDUINO
|
|
if (err != RADIOLIB_ERR_NONE)
|
|
portduino_status.LoRa_in_error = true;
|
|
#else
|
|
assert(err == RADIOLIB_ERR_NONE);
|
|
#endif
|
|
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 SX126xInterface<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 SX126xInterface<T>::configHardwareForSend()
|
|
{
|
|
setTransmitEnable(true);
|
|
RadioLibInterface::configHardwareForSend();
|
|
}
|
|
|
|
// For power draw measurements, helpful to force radio to stay sleeping
|
|
// #define SLEEP_ONLY
|
|
|
|
template <typename T> void SX126xInterface<T>::startReceive()
|
|
{
|
|
#ifdef SLEEP_ONLY
|
|
sleep();
|
|
#else
|
|
|
|
setTransmitEnable(false);
|
|
setStandby();
|
|
|
|
#ifdef ARCH_PORTDUINO_WASM
|
|
// Continuous RX in the browser: duty-cycle sleep parks BUSY high between RX
|
|
// windows and stalls the slow WebUSB SPI link. No battery to save here.
|
|
int err = lora.startReceive(RADIOLIB_SX126X_RX_TIMEOUT_INF, MESHTASTIC_RADIOLIB_IRQ_RX_FLAGS);
|
|
const char *rxMethod = "startReceive";
|
|
#else
|
|
// We use a 16 bit preamble so this should save some power by letting radio sit in standby mostly.
|
|
int err = lora.startReceiveDutyCycleAuto(preambleLength, 8, MESHTASTIC_RADIOLIB_IRQ_RX_FLAGS);
|
|
const char *rxMethod = "startReceiveDutyCycleAuto";
|
|
#endif
|
|
if (err != RADIOLIB_ERR_NONE)
|
|
LOG_ERROR("SX126X %s %s%d", rxMethod, radioLibErr, err);
|
|
#ifdef ARCH_PORTDUINO
|
|
if (err != RADIOLIB_ERR_NONE)
|
|
portduino_status.LoRa_in_error = true;
|
|
#else
|
|
assert(err == RADIOLIB_ERR_NONE);
|
|
#endif
|
|
|
|
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 SX126xInterface<T>::isChannelActive()
|
|
{
|
|
// check if we can detect a LoRa preamble on the current channel
|
|
ChannelScanConfig_t cfg = {.cad = {.symNum = NUM_SYM_CAD,
|
|
.detPeak = RADIOLIB_SX126X_CAD_PARAM_DEFAULT,
|
|
.detMin = RADIOLIB_SX126X_CAD_PARAM_DEFAULT,
|
|
.exitMode = RADIOLIB_SX126X_CAD_PARAM_DEFAULT,
|
|
.timeout = 0,
|
|
.irqFlags = RADIOLIB_IRQ_CAD_DEFAULT_FLAGS,
|
|
.irqMask = RADIOLIB_IRQ_CAD_DEFAULT_MASK}};
|
|
int16_t result;
|
|
setTransmitEnable(false);
|
|
setStandby();
|
|
result = lora.scanChannel(cfg);
|
|
if (result == RADIOLIB_LORA_DETECTED)
|
|
return true;
|
|
if (result != RADIOLIB_CHANNEL_FREE)
|
|
LOG_ERROR("SX126X scanChannel %s%d", radioLibErr, result);
|
|
#ifdef ARCH_PORTDUINO
|
|
if (result == RADIOLIB_ERR_WRONG_MODEM)
|
|
portduino_status.LoRa_in_error = true;
|
|
#else
|
|
assert(result != RADIOLIB_ERR_WRONG_MODEM);
|
|
#endif
|
|
|
|
return false;
|
|
}
|
|
|
|
/** Could we send right now (i.e. either not actively receiving or transmitting)? */
|
|
template <typename T> bool SX126xInterface<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.getIrqFlags(), RADIOLIB_SX126X_IRQ_HEADER_VALID, RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED);
|
|
}
|
|
|
|
template <typename T> bool SX126xInterface<T>::sleep()
|
|
{
|
|
// Not keeping config is busted - next time nrf52 board boots lora sending fails tcxo related? - see datasheet
|
|
// \todo Display actual typename of the adapter, not just `SX126x`
|
|
LOG_DEBUG("SX126x entering sleep mode"); // (FIXME, don't keep config)
|
|
setStandby(); // Stop any pending operations
|
|
|
|
// turn off TCXO if it was powered
|
|
// FIXME - this isn't correct
|
|
// lora.setTCXO(0);
|
|
|
|
// put chipset into sleep mode (we've already disabled interrupts by now)
|
|
bool keepConfig = true;
|
|
lora.sleep(keepConfig); // Note: we do not keep the config, full reinit will be needed
|
|
|
|
#ifdef SX126X_POWER_EN
|
|
digitalWrite(SX126X_POWER_EN, LOW);
|
|
#endif
|
|
|
|
#if HAS_LORA_FEM
|
|
loraFEMInterface.setSleepModeEnable();
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
template <typename T> void SX126xInterface<T>::resetAGC()
|
|
{
|
|
// Safety: don't reset mid-packet
|
|
if (sendingPacket != NULL || (isReceiving && isActivelyReceiving()))
|
|
return;
|
|
|
|
LOG_DEBUG("SX126x AGC reset: warm sleep + Calibrate(0x7F)");
|
|
|
|
// 1. Warm sleep - powers down the entire analog frontend, resetting AGC state.
|
|
// A plain standby→startReceive cycle does NOT reset the AGC.
|
|
lora.sleep(true);
|
|
|
|
// 2. Wake to RC standby for stable calibration
|
|
lora.standby(RADIOLIB_SX126X_STANDBY_RC, true);
|
|
|
|
// 3. Calibrate all blocks (ADC, PLL, image, RC oscillators)
|
|
uint8_t calData = RADIOLIB_SX126X_CALIBRATE_ALL;
|
|
module.SPIwriteStream(RADIOLIB_SX126X_CMD_CALIBRATE, &calData, 1, true, false);
|
|
|
|
// 4. Wait for calibration to complete (BUSY pin goes low)
|
|
module.hal->delay(5);
|
|
uint32_t start = millis();
|
|
while (module.hal->digitalRead(module.getGpio())) {
|
|
if (millis() - start > 50)
|
|
break;
|
|
module.hal->yield();
|
|
}
|
|
|
|
if (module.hal->digitalRead(module.getGpio())) {
|
|
LOG_WARN("SX126x AGC reset: calibration did not complete within 50ms");
|
|
startReceive();
|
|
return;
|
|
}
|
|
|
|
// 5. Re-calibrate image rejection for actual operating frequency
|
|
// Calibrate(0x7F) defaults to 902-928 MHz which is wrong for other regions.
|
|
lora.calibrateImage(getFreq());
|
|
|
|
// Re-apply settings that calibration may have reset
|
|
|
|
// DIO2 as RF switch
|
|
#ifdef SX126X_DIO2_AS_RF_SWITCH
|
|
lora.setDio2AsRfSwitch(true);
|
|
#elif defined(ARCH_PORTDUINO)
|
|
if (portduino_config.dio2_as_rf_switch)
|
|
lora.setDio2AsRfSwitch(true);
|
|
#endif
|
|
|
|
// RX boosted gain mode
|
|
lora.setRxBoostedGainMode(config.lora.sx126x_rx_boosted_gain);
|
|
|
|
// Re-apply the undocumented 0x8B5 RX sensitivity patch that was set in init().
|
|
// The CALIBRATE_ALL (0x7F) command above clears bit 0 of register 0x8B5, which
|
|
// silently removes the RX sensitivity improvement introduced in #9571 / #9777.
|
|
// Without this re-apply, every SX1262 node loses its RX boost ~60s after boot
|
|
// and never recovers until reboot. See empirical evidence in the PR description.
|
|
if (module.SPIsetRegValue(0x8B5, 0x01, 0, 0) != RADIOLIB_ERR_NONE) {
|
|
LOG_WARN("SX126x resetAGC: failed to re-apply 0x8B5 RX sensitivity patch");
|
|
}
|
|
|
|
// 6. Resume receiving
|
|
startReceive();
|
|
}
|
|
|
|
/** Control PA mode for GC1109 FEM - CPS pin selects full PA (txon=true) or bypass mode (txon=false) */
|
|
template <typename T> void SX126xInterface<T>::setTransmitEnable(bool txon)
|
|
{
|
|
#if HAS_LORA_FEM
|
|
if (txon) {
|
|
loraFEMInterface.setTxModeEnable();
|
|
} else {
|
|
loraFEMInterface.setRxModeEnable();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#endif |