Restore SX1276 CW support and calibrated power sweeps

Retain the original carrier feature from PR #11 with zero-deviation FSK, board PA calibration, and LoRa restoration. Re-key after power changes without extending the timeout, and restore prior power after a failed sweep. Cover native and SX1276 transitions, failure recovery, external PA and RFO mapping; verify Linkflow repeater and Heltec V2 USB Companion builds.
This commit is contained in:
mikecarper committed 2026-09-14 19:39:33 -07:00
1 parent 9de1373bd9
commit 27e4a0d0db
6 files changed
+226 -14

No files matched your search

+28 -1
View File
@@ -10,8 +10,12 @@
class CustomSX1276Wrapper : public RadioLibWrapper {
public:
CustomSX1276Wrapper(CustomSX1276& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { }
bool supportsCarrierWave() const override { return supportsRadioDeepInit(); }
void powerOff() { _radio->standby(); _radio->sleep(); }
void powerOff() {
if (isCarrierWaveActive()) setCarrierWave(0, 0);
_radio->standby(); _radio->sleep();
}
protected:
bool applyParams(float freq, float bw, uint8_t sf, uint8_t cr) override {
@@ -24,6 +28,7 @@ protected:
public:
bool setCodingRate(uint8_t cr) override {
if (isCarrierWaveActive()) return false;
return ((CustomSX1276 *)_radio)->setCodingRate(cr) == RADIOLIB_ERR_NONE;
}
@@ -40,6 +45,28 @@ public:
uint8_t getSpreadingFactor() const override { return ((CustomSX1276 *)_radio)->spreadingFactor; }
protected:
int16_t enterCarrierWave() override {
auto* radio = static_cast<CustomSX1276*>(_radio);
// Direct TX needs FSK. Zero deviation prevents a floating DIO2 data input
// from moving the carrier. Start at the PA_BOOST floor, while still idle;
// user-facing power may be an external PA level, not chip drive power.
int16_t status = radio->beginFSK(_cur_freq, 4.8, 0.0, 125.0, 2, 16, false);
if (status != RADIOLIB_ERR_NONE) return status;
#ifdef SX127X_CURRENT_LIMIT
status = radio->setCurrentLimit(SX127X_CURRENT_LIMIT);
if (status != RADIOLIB_ERR_NONE) return status;
#endif
status = applyCachedTxPower(_dbm_valid ? _cur_dbm : LORA_TX_POWER);
if (status != RADIOLIB_ERR_NONE) return status;
return radio->transmitDirect();
}
bool restoreCarrierWaveModem() override {
// Restores LoRa, runtime profile/preamble, IRQ mapping and PA calibration.
// Shared cleanup retains the stop/retry guard if any restoration fails.
return restoreAfterDeepInit();
}
int16_t performChannelScan() override {
return ((CustomSX1276 *)_radio)->tryScanChannel(cadScanTimeoutMillis(), *_board);
}
+23 -4
View File
@@ -89,6 +89,23 @@ uint32_t RadioLibWrapper::getRngSeed() {
}
bool RadioLibWrapper::setTxPower(int8_t dbm) {
if (_cw_active) {
if (_cw_stopping) return false;
// Permit meter sweeps through the board's normal power/PA calibration.
// SX127x's power setter enters standby, so re-key without resetting the
// modem or extending the original CW deadline.
if (applyCachedTxPower(dbm) != RADIOLIB_ERR_NONE) {
stopCarrierWave();
return false;
}
if (_radio->transmitDirect() != RADIOLIB_ERR_NONE) {
stopCarrierWave();
return false;
}
_cur_dbm = dbm;
_dbm_valid = true;
return true;
}
const uint8_t resume_rx = beginReconfigure();
if (resume_rx > 1) return false;
@@ -707,7 +724,7 @@ mesh::RadioParamApplyResult RadioLibWrapper::setCarrierWave(uint8_t profile, uin
_board->setRadioTestActive(true); // service the deadline even without USB
_board->onBeforeTransmit();
_cw_board_tx = true;
if (_radio->transmitDirect() != RADIOLIB_ERR_NONE) {
if (enterCarrierWave() != RADIOLIB_ERR_NONE) {
stopCarrierWave(); // a failed SPI result does not prove TX stayed off
return Result::FAILED;
}
@@ -717,11 +734,13 @@ mesh::RadioParamApplyResult RadioLibWrapper::setCarrierWave(uint8_t profile, uin
bool RadioLibWrapper::stopCarrierWave() {
_cw_stopping = true;
// finishTransmit clears radio IRQs and enters standby on both supported
// families. Deassert external TX even if SPI fails, then try a radio reset.
// Deassert external TX even if SPI fails. SX127x must also rebuild its LoRa
// modem after FSK direct mode before normal packet work can resume.
const int16_t status = _radio->finishTransmit();
if (_cw_board_tx) { _board->onAfterTransmit(); _cw_board_tx = false; }
if (status != RADIOLIB_ERR_NONE && !restoreAfterDeepInit()) {
const bool restored = status == RADIOLIB_ERR_NONE
? restoreCarrierWaveModem() : restoreAfterDeepInit();
if (!restored) {
_cw_retry_at = static_cast<uint32_t>(millis()) + 100;
return false; // hold normal work until stopping the carrier is confirmed
}
+6
View File
@@ -157,6 +157,12 @@ protected:
return _radio->setOutputPower(dbm);
}
virtual bool applyRxBoostedGainMode(bool) { return false; }
virtual int16_t enterCarrierWave() { return _radio->transmitDirect(); }
// Native-CW radios keep their modem. Reapply cached power in case a meter
// sweep failed halfway through; SX127x also rebuilds its LoRa configuration.
virtual bool restoreCarrierWaveModem() {
return !_dbm_valid || applyCachedTxPower(_cur_dbm) == RADIOLIB_ERR_NONE;
}
// Dual-profile scanning already suspends RXPS. Supported radios can also
// retain their oscillator across the intervening standby/reconfigure steps.
// Called only at the safe scan-mode entry/exit boundary, not during a packet.