Add LoRa FEM RX-path LNA control via CLI; restore enabled default

Ported from meshcore-dev/MeshCore#2140 (Quency-D <hj_zzns@163.com>).

The upstream v1.16.0 merge changed the Heltec FEM LNA to disabled by
default (upstream commit 696aae6e).  This patch restores the enabled
default for repeater via the new "radio.fem.rxgain" preference
(defaulted to 1), while also making the setting user-configurable
and persisted across reboots.
This commit is contained in:
Valentin V. Bartenev
2026-06-07 15:36:19 +03:00
parent d58311ce3c
commit 1817248d97
14 changed files with 126 additions and 6 deletions
+14
View File
@@ -263,6 +263,20 @@ This document provides an overview of CLI commands that can be sent to MeshCore
--- ---
#### View or change the LoRa FEM receive-path gain state on supported boards
**Usage:**
- `get radio.fem.rxgain`
- `set radio.fem.rxgain <state>`
**Parameters:**
- `state`: `on`|`off`
**Notes:**
- This controls the external LoRa FEM receive-path LNA where the board supports it.
- This is separate from `radio.rxgain`, which controls the radio chip receive gain mode.
---
### System ### System
#### View or change this node's name #### View or change this node's name
+2
View File
@@ -1266,6 +1266,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
_prefs.rx_boosted_gain = 1; // enabled by default; _prefs.rx_boosted_gain = 1; // enabled by default;
#endif #endif
#endif #endif
_prefs.radio_fem_rxgain = 1;
pending_discover_tag = 0; pending_discover_tag = 0;
pending_discover_until = 0; pending_discover_until = 0;
@@ -1360,6 +1361,7 @@ void MyMesh::begin(FILESYSTEM *fs, ArchiveStorage* archive) {
radio_driver.setRxBoostedGainMode(_prefs.rx_boosted_gain); radio_driver.setRxBoostedGainMode(_prefs.rx_boosted_gain);
MESH_DEBUG_PRINTLN("RX Boosted Gain Mode: %s", MESH_DEBUG_PRINTLN("RX Boosted Gain Mode: %s",
radio_driver.getRxBoostedGainMode() ? "Enabled" : "Disabled"); radio_driver.getRxBoostedGainMode() ? "Enabled" : "Disabled");
board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain);
updateAdvertTimer(); updateAdvertTimer();
updateFloodAdvertTimer(); updateFloodAdvertTimer();
+3
View File
@@ -69,6 +69,9 @@ public:
virtual uint8_t getStartupReason() const = 0; virtual uint8_t getStartupReason() const = 0;
virtual bool getBootloaderVersion(char* version, size_t max_len) { return false; } virtual bool getBootloaderVersion(char* version, size_t max_len) { return false; }
virtual bool startOTAUpdate(const char* id, char reply[]) { return false; } // not supported virtual bool startOTAUpdate(const char* id, char reply[]) { return false; } // not supported
virtual bool setLoRaFemLnaEnabled(bool enable) { return false; }
virtual bool canControlLoRaFemLna() const { return false; }
virtual bool isLoRaFemLnaEnabled() const { return false; }
// Power management interface (boards with power management override these) // Power management interface (boards with power management override these)
virtual bool isExternalPowered() { return false; } virtual bool isExternalPowered() { return false; }
+36 -2
View File
@@ -106,7 +106,10 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
if (file.available() >= (int)sizeof(_prefs->flood_max_advert)) { if (file.available() >= (int)sizeof(_prefs->flood_max_advert)) {
file.read((uint8_t *)&_prefs->flood_max_advert, sizeof(_prefs->flood_max_advert)); // 296 file.read((uint8_t *)&_prefs->flood_max_advert, sizeof(_prefs->flood_max_advert)); // 296
} }
// next: 297 if (file.available() >= (int)sizeof(_prefs->radio_fem_rxgain)) {
file.read((uint8_t *)&_prefs->radio_fem_rxgain, sizeof(_prefs->radio_fem_rxgain)); // 297
}
// next: 298
// sanitise bad pref values // sanitise bad pref values
_prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f); _prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f);
@@ -136,6 +139,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
// sanitise settings // sanitise settings
_prefs->rx_boosted_gain = constrain(_prefs->rx_boosted_gain, 0, 1); // boolean _prefs->rx_boosted_gain = constrain(_prefs->rx_boosted_gain, 0, 1); // boolean
_prefs->radio_fem_rxgain = constrain(_prefs->radio_fem_rxgain, 0, 1); // boolean
_prefs->fan_mode = constrain(_prefs->fan_mode, 0, 2); _prefs->fan_mode = constrain(_prefs->fan_mode, 0, 2);
_prefs->fan_timeout_secs = constrain(_prefs->fan_timeout_secs, 0, 600); _prefs->fan_timeout_secs = constrain(_prefs->fan_timeout_secs, 0, 600);
@@ -204,7 +209,8 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) {
file.write((uint8_t *)&_prefs->fan_timeout_secs, sizeof(_prefs->fan_timeout_secs)); // 293 file.write((uint8_t *)&_prefs->fan_timeout_secs, sizeof(_prefs->fan_timeout_secs)); // 293
file.write((uint8_t *)&_prefs->flood_max_unscoped, sizeof(_prefs->flood_max_unscoped)); // 295 file.write((uint8_t *)&_prefs->flood_max_unscoped, sizeof(_prefs->flood_max_unscoped)); // 295
file.write((uint8_t *)&_prefs->flood_max_advert, sizeof(_prefs->flood_max_advert)); // 296 file.write((uint8_t *)&_prefs->flood_max_advert, sizeof(_prefs->flood_max_advert)); // 296
// next: 297 file.write((uint8_t *)&_prefs->radio_fem_rxgain, sizeof(_prefs->radio_fem_rxgain)); // 297
// next: 298
file.close(); file.close();
} }
@@ -597,6 +603,28 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
savePrefs(); savePrefs();
_callbacks->setRxBoostedGain(_prefs->rx_boosted_gain); _callbacks->setRxBoostedGain(_prefs->rx_boosted_gain);
#endif #endif
} else if (memcmp(config, "radio.fem.rxgain ", 17) == 0) {
if (!_board->canControlLoRaFemLna()) {
strcpy(reply, "Error: unsupported");
} else if (memcmp(&config[17], "on", 2) == 0) {
if (_board->setLoRaFemLnaEnabled(true)) {
_prefs->radio_fem_rxgain = 1;
savePrefs();
strcpy(reply, "OK - LoRa FEM RX gain on");
} else {
strcpy(reply, "Error: failed to apply LoRa FEM RX gain");
}
} else if (memcmp(&config[17], "off", 3) == 0) {
if (_board->setLoRaFemLnaEnabled(false)) {
_prefs->radio_fem_rxgain = 0;
savePrefs();
strcpy(reply, "OK - LoRa FEM RX gain off");
} else {
strcpy(reply, "Error: failed to apply LoRa FEM RX gain");
}
} else {
strcpy(reply, "Error: state must be on or off");
}
} else if (memcmp(config, "radio ", 6) == 0) { } else if (memcmp(config, "radio ", 6) == 0) {
strcpy(tmp, &config[6]); strcpy(tmp, &config[6]);
const char *parts[4]; const char *parts[4];
@@ -834,6 +862,12 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
} else if (memcmp(config, "radio.rxgain", 12) == 0) { } else if (memcmp(config, "radio.rxgain", 12) == 0) {
sprintf(reply, "> %s", _prefs->rx_boosted_gain ? "on" : "off"); sprintf(reply, "> %s", _prefs->rx_boosted_gain ? "on" : "off");
#endif #endif
} else if (memcmp(config, "radio.fem.rxgain", 16) == 0) {
if (!_board->canControlLoRaFemLna()) {
strcpy(reply, "Error: unsupported");
} else {
sprintf(reply, "> %s", _board->isLoRaFemLnaEnabled() ? "on" : "off");
}
} else if (memcmp(config, "radio", 5) == 0) { } else if (memcmp(config, "radio", 5) == 0) {
char freq[16], bw[16]; char freq[16], bw[16];
strcpy(freq, StrHelper::ftoa(_prefs->freq)); strcpy(freq, StrHelper::ftoa(_prefs->freq));
+1
View File
@@ -62,6 +62,7 @@ struct NodePrefs { // persisted to file
char owner_info[120]; char owner_info[120];
uint8_t reserved_290; // reserved to preserve on-disk prefs layout uint8_t reserved_290; // reserved to preserve on-disk prefs layout
uint8_t rx_boosted_gain; // power settings uint8_t rx_boosted_gain; // power settings
uint8_t radio_fem_rxgain; // LoRa FEM RX gain setting
uint8_t path_hash_mode; // which path mode to use when sending uint8_t path_hash_mode; // which path mode to use when sending
uint8_t loop_detect; uint8_t loop_detect;
uint8_t fan_mode; uint8_t fan_mode;
+2 -1
View File
@@ -12,8 +12,9 @@ class LoRaFEMControl
void setRxModeEnable(void); void setRxModeEnable(void);
void setRxModeEnableWhenMCUSleep(void); void setRxModeEnableWhenMCUSleep(void);
void setLNAEnable(bool enabled); void setLNAEnable(bool enabled);
bool isLnaCanControl(void) { return lna_can_control; } bool isLnaCanControl(void) const { return lna_can_control; }
void setLnaCanControl(bool can_control) { lna_can_control = can_control; } void setLnaCanControl(bool can_control) { lna_can_control = can_control; }
bool isLNAEnabled(void) const { return lna_enabled; }
private: private:
bool lna_enabled = false; bool lna_enabled = false;
+19 -1
View File
@@ -123,4 +123,22 @@ void T096Board::powerOff() {
const char* T096Board::getManufacturerName() const { const char* T096Board::getManufacturerName() const {
return "Heltec T096"; return "Heltec T096";
} }
bool T096Board::setLoRaFemLnaEnabled(bool enable) {
if (!loRaFEMControl.isLnaCanControl()) {
return false;
}
loRaFEMControl.setLNAEnable(enable);
loRaFEMControl.setRxModeEnable();
return true;
}
bool T096Board::canControlLoRaFemLna() const {
return loRaFEMControl.isLnaCanControl();
}
bool T096Board::isLoRaFemLnaEnabled() const {
return loRaFEMControl.isLNAEnabled();
}
+3
View File
@@ -25,4 +25,7 @@ public:
uint16_t getBattMilliVolts() override; uint16_t getBattMilliVolts() override;
const char* getManufacturerName() const override ; const char* getManufacturerName() const override ;
void powerOff() override; void powerOff() override;
bool setLoRaFemLnaEnabled(bool enable) override;
bool canControlLoRaFemLna() const override;
bool isLoRaFemLnaEnabled() const override;
}; };
@@ -82,3 +82,21 @@ void HeltecTrackerV2Board::begin() {
const char* HeltecTrackerV2Board::getManufacturerName() const { const char* HeltecTrackerV2Board::getManufacturerName() const {
return "Heltec Tracker V2"; return "Heltec Tracker V2";
} }
bool HeltecTrackerV2Board::setLoRaFemLnaEnabled(bool enable) {
if (!loRaFEMControl.isLnaCanControl()) {
return false;
}
loRaFEMControl.setLNAEnable(enable);
loRaFEMControl.setRxModeEnable();
return true;
}
bool HeltecTrackerV2Board::canControlLoRaFemLna() const {
return loRaFEMControl.isLnaCanControl();
}
bool HeltecTrackerV2Board::isLoRaFemLnaEnabled() const {
return loRaFEMControl.isLNAEnabled();
}
@@ -21,5 +21,8 @@ public:
void powerOff() override; void powerOff() override;
uint16_t getBattMilliVolts() override; uint16_t getBattMilliVolts() override;
const char* getManufacturerName() const override ; const char* getManufacturerName() const override ;
bool setLoRaFemLnaEnabled(bool enable) override;
bool canControlLoRaFemLna() const override;
bool isLoRaFemLnaEnabled() const override;
}; };
+2 -1
View File
@@ -12,8 +12,9 @@ class LoRaFEMControl
void setRxModeEnable(void); void setRxModeEnable(void);
void setRxModeEnableWhenMCUSleep(void); void setRxModeEnableWhenMCUSleep(void);
void setLNAEnable(bool enabled); void setLNAEnable(bool enabled);
bool isLnaCanControl(void) { return lna_can_control; } bool isLnaCanControl(void) const { return lna_can_control; }
void setLnaCanControl(bool can_control) { lna_can_control = can_control; } void setLnaCanControl(bool can_control) { lna_can_control = can_control; }
bool isLNAEnabled(void) const { return lna_enabled; }
private: private:
bool lna_enabled = false; bool lna_enabled = false;
+18
View File
@@ -83,3 +83,21 @@ void HeltecV4Board::begin() {
return loRaFEMControl.getFEMType() == KCT8103L_PA ? "Heltec V4.3 OLED" : "Heltec V4 OLED"; return loRaFEMControl.getFEMType() == KCT8103L_PA ? "Heltec V4.3 OLED" : "Heltec V4 OLED";
#endif #endif
} }
bool HeltecV4Board::setLoRaFemLnaEnabled(bool enable) {
if (!loRaFEMControl.isLnaCanControl()) {
return false;
}
loRaFEMControl.setLNAEnable(enable);
loRaFEMControl.setRxModeEnable();
return true;
}
bool HeltecV4Board::canControlLoRaFemLna() const {
return loRaFEMControl.isLnaCanControl();
}
bool HeltecV4Board::isLoRaFemLnaEnabled() const {
return loRaFEMControl.isLNAEnabled();
}
+3
View File
@@ -25,6 +25,9 @@ public:
void onAfterTransmit(void) override; void onAfterTransmit(void) override;
void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1); void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1);
void powerOff() override; void powerOff() override;
bool setLoRaFemLnaEnabled(bool enable) override;
bool canControlLoRaFemLna() const override;
bool isLoRaFemLnaEnabled() const override;
uint16_t getBattMilliVolts() override; uint16_t getBattMilliVolts() override;
bool setAdcMultiplier(float multiplier) override { bool setAdcMultiplier(float multiplier) override {
if (multiplier == 0.0f) { if (multiplier == 0.0f) {
+2 -1
View File
@@ -18,8 +18,9 @@ class LoRaFEMControl
void setRxModeEnable(void); void setRxModeEnable(void);
void setRxModeEnableWhenMCUSleep(void); void setRxModeEnableWhenMCUSleep(void);
void setLNAEnable(bool enabled); void setLNAEnable(bool enabled);
bool isLnaCanControl(void) { return lna_can_control; } bool isLnaCanControl(void) const { return lna_can_control; }
void setLnaCanControl(bool can_control) { lna_can_control = can_control; } void setLnaCanControl(bool can_control) { lna_can_control = can_control; }
bool isLNAEnabled(void) const { return lna_enabled; }
LoRaFEMType getFEMType(void) const { return fem_type; } LoRaFEMType getFEMType(void) const { return fem_type; }
private: private:
LoRaFEMType fem_type=OTHER_FEM_TYPES; LoRaFEMType fem_type=OTHER_FEM_TYPES;