Implement 'agc' reset for SX126x & LR11x0 chip families (#9705)
* Implement 'agc' reset for SX126x chip family There's no actual agc on SX126x chips but you can reset the analog registers by doing a warm sleep & running calibration. * Address PR comments & implement for LR11x0 too * calibrate for configured frequency band * Gate LR11X0_AGC_RESET
This commit is contained in:
@@ -1125,6 +1125,13 @@ void loop()
|
||||
lastRadioMissedIrqPoll = millis();
|
||||
RadioLibInterface::instance->pollMissedIrqs();
|
||||
}
|
||||
|
||||
// Periodic AGC reset — warm sleep + recalibrate to prevent stuck AGC gain
|
||||
static uint32_t lastAgcReset;
|
||||
if (!Throttle::isWithinTimespanMs(lastAgcReset, AGC_RESET_INTERVAL_MS)) {
|
||||
lastAgcReset = millis();
|
||||
RadioLibInterface::instance->resetAGC();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG_STACK
|
||||
|
||||
@@ -299,6 +299,38 @@ template <typename T> bool LR11x0Interface<T>::isActivelyReceiving()
|
||||
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`
|
||||
|
||||
@@ -27,6 +27,10 @@ template <class T> class LR11x0Interface : public RadioLibInterface
|
||||
|
||||
bool isIRQPending() override { return lora.getIrqFlags() != 0; }
|
||||
|
||||
#ifdef LR11X0_AGC_RESET
|
||||
void resetAGC() override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Specific module instance
|
||||
|
||||
@@ -529,6 +529,11 @@ void RadioLibInterface::pollMissedIrqs()
|
||||
}
|
||||
}
|
||||
|
||||
void RadioLibInterface::resetAGC()
|
||||
{
|
||||
// Base implementation: no-op. Override in chip-specific subclasses.
|
||||
}
|
||||
|
||||
void RadioLibInterface::checkRxDoneIrqFlag()
|
||||
{
|
||||
if (iface->checkIrq(RADIOLIB_IRQ_RX_DONE)) {
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
// In addition to the default Rx flags, we need the PREAMBLE_DETECTED flag to detect whether we are actively receiving
|
||||
#define MESHTASTIC_RADIOLIB_IRQ_RX_FLAGS (RADIOLIB_IRQ_RX_DEFAULT_FLAGS | (1 << RADIOLIB_IRQ_PREAMBLE_DETECTED))
|
||||
|
||||
#define AGC_RESET_INTERVAL_MS (60 * 1000) // 60 seconds
|
||||
|
||||
/**
|
||||
* We need to override the RadioLib ArduinoHal class to add mutex protection for SPI bus access
|
||||
*/
|
||||
@@ -117,6 +119,13 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
|
||||
*/
|
||||
void pollMissedIrqs();
|
||||
|
||||
/**
|
||||
* Reset AGC by power-cycling the analog frontend.
|
||||
* Subclasses override with chip-specific calibration sequences.
|
||||
* Safe to call periodically — skips if currently sending or receiving.
|
||||
*/
|
||||
virtual void resetAGC();
|
||||
|
||||
/**
|
||||
* Debugging counts
|
||||
*/
|
||||
|
||||
@@ -434,6 +434,61 @@ template <typename T> bool SX126xInterface<T>::sleep()
|
||||
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);
|
||||
|
||||
// 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)
|
||||
{
|
||||
|
||||
@@ -28,6 +28,8 @@ template <class T> class SX126xInterface : public RadioLibInterface
|
||||
|
||||
bool isIRQPending() override { return lora.getIrqFlags() != 0; }
|
||||
|
||||
void resetAGC() override;
|
||||
|
||||
void setTCXOVoltage(float voltage) { tcxoVoltage = voltage; }
|
||||
|
||||
protected:
|
||||
|
||||
Reference in New Issue
Block a user