fix(station-g3): expose FEM gain preferences

This commit is contained in:
agessaman
2026-08-06 15:29:14 -07:00
parent 623422694b
commit 23066573e5
12 changed files with 198 additions and 44 deletions
+15
View File
@@ -291,6 +291,21 @@ This document provides an overview of CLI commands that can be sent to MeshCore
---
#### View or change the LoRa FEM transmit-path gain state on supported boards
**Usage:**
- `get radio.fem.txgain`
- `set radio.fem.txgain <state>`
**Parameters:**
- `state`: `on`|`off`
**Notes:**
- This controls a software-selectable external LoRa FEM transmit gain where the board supports it.
- On Station G3, remove the PA PL1 jumper to allow software control. `on` selects PA PL1 high/short and `off` selects PA PL1 low/open. The PA PL2 hardware jumper determines whether this switches between power levels 1/3 or 2/4.
- Select an operating level and SX1262 transmit power that comply with local RF limits and the Station G3 power-supply requirements.
---
### System
#### View or change this node's name
+2
View File
@@ -913,6 +913,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
#endif
#endif
_prefs.radio_fem_rxgain = 1;
_prefs.radio_fem_txgain = 0;
pending_discover_tag = 0;
pending_discover_until = 0;
@@ -962,6 +963,7 @@ void MyMesh::begin(FILESYSTEM *fs) {
MESH_DEBUG_PRINTLN("RX Boosted Gain Mode: %s",
radio_driver.getRxBoostedGainMode() ? "Enabled" : "Disabled");
board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain);
board.setLoRaFemPaGainEnabled(_prefs.radio_fem_txgain);
updateAdvertTimer();
updateFloodAdvertTimer();
+2
View File
@@ -683,6 +683,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
#endif
#endif
_prefs.radio_fem_rxgain = 1;
_prefs.radio_fem_txgain = 0;
next_post_idx = 0;
next_client_idx = 0;
@@ -726,6 +727,7 @@ void MyMesh::begin(FILESYSTEM *fs) {
radio_driver.setTxPower(_prefs.tx_power_dbm);
radio_driver.setRxBoostedGainMode(_prefs.rx_boosted_gain);
board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain);
board.setLoRaFemPaGainEnabled(_prefs.radio_fem_txgain);
updateAdvertTimer();
updateFloodAdvertTimer();
+2
View File
@@ -735,6 +735,7 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise
_prefs.gps_interval = 0;
_prefs.advert_loc_policy = ADVERT_LOC_PREFS;
_prefs.radio_fem_rxgain = 1;
_prefs.radio_fem_txgain = 0;
memset(default_scope.key, 0, sizeof(default_scope.key));
}
@@ -771,6 +772,7 @@ void SensorMesh::begin(FILESYSTEM* fs) {
radio_driver.setParams(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
radio_driver.setTxPower(_prefs.tx_power_dbm);
board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain);
board.setLoRaFemPaGainEnabled(_prefs.radio_fem_txgain);
updateAdvertTimer();
updateFloodAdvertTimer();
+4
View File
@@ -67,6 +67,10 @@ public:
virtual bool setLoRaFemLnaEnabled(bool enable) { return false; }
virtual bool canControlLoRaFemLna() const { return false; }
virtual bool isLoRaFemLnaEnabled() const { return false; }
// Software-selectable external FEM transmit gain. This is not a PA power switch.
virtual bool setLoRaFemPaGainEnabled(bool enable) { return false; }
virtual bool canControlLoRaFemPaGain() const { return false; }
virtual bool isLoRaFemPaGainEnabled() const { return false; }
// Power management interface (boards with power management override these)
virtual bool isExternalPowered() { return false; }
+31 -1
View File
@@ -102,7 +102,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { // Legacy
file.read((uint8_t *)&_prefs->flood_max_advert, sizeof(_prefs->flood_max_advert)); // 292
file.read((uint8_t *)&_prefs->radio_fem_rxgain, sizeof(_prefs->radio_fem_rxgain)); // 293
file.read((uint8_t *)&_prefs->cad_enabled, sizeof(_prefs->cad_enabled)); // 294
// next: 295
file.read((uint8_t *)&_prefs->radio_fem_txgain, sizeof(_prefs->radio_fem_txgain)); // 295
// next: 296
// sanitise bad pref values
_prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f);
@@ -133,6 +134,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { // Legacy
// sanitise settings
_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->radio_fem_txgain = constrain(_prefs->radio_fem_txgain, 0, 1); // boolean
_prefs->cad_enabled = constrain(_prefs->cad_enabled, 0, 1); // boolean
file.close();
@@ -562,6 +564,28 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
} else {
strcpy(reply, "Error: state must be on or off");
}
} else if (memcmp(config, "radio.fem.txgain ", 17) == 0) {
if (!_board->canControlLoRaFemPaGain()) {
strcpy(reply, "Error: unsupported");
} else if (memcmp(&config[17], "on", 2) == 0) {
if (_board->setLoRaFemPaGainEnabled(true)) {
_prefs->radio_fem_txgain = 1;
savePrefs();
strcpy(reply, "OK - LoRa FEM TX gain on");
} else {
strcpy(reply, "Error: failed to apply LoRa FEM TX gain");
}
} else if (memcmp(&config[17], "off", 3) == 0) {
if (_board->setLoRaFemPaGainEnabled(false)) {
_prefs->radio_fem_txgain = 0;
savePrefs();
strcpy(reply, "OK - LoRa FEM TX gain off");
} else {
strcpy(reply, "Error: failed to apply LoRa FEM TX gain");
}
} else {
strcpy(reply, "Error: state must be on or off");
}
} else if (memcmp(config, "radio ", 6) == 0) {
strcpy(tmp, &config[6]);
const char *parts[4];
@@ -827,6 +851,12 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
} else {
sprintf(reply, "> %s", _board->isLoRaFemLnaEnabled() ? "on" : "off");
}
} else if (memcmp(config, "radio.fem.txgain", 16) == 0) {
if (!_board->canControlLoRaFemPaGain()) {
strcpy(reply, "Error: unsupported");
} else {
sprintf(reply, "> %s", _board->isLoRaFemPaGainEnabled() ? "on" : "off");
}
} else if (memcmp(config, "radio", 5) == 0) {
char freq[16], bw[16];
strcpy(freq, StrHelper::ftoa(_prefs->freq));
+3 -1
View File
@@ -65,6 +65,7 @@ public:
char owner_info[120];
uint8_t rx_boosted_gain = 0; // power settings
uint8_t radio_fem_rxgain = 0; // LoRa FEM RX gain setting
uint8_t radio_fem_txgain = 0; // LoRa FEM TX gain setting
uint8_t path_hash_mode = 0; // which path mode to use when sending
uint8_t loop_detect = 0;
uint8_t cad_enabled = 0; // hardware Channel Activity Detection before TX (boolean)
@@ -82,7 +83,8 @@ private:
def("cad", _parent->cad_enabled);
def("int_thr", _parent->interference_threshold);
def("rxgain", _parent->rx_boosted_gain);
def("fem_rxgain", _parent->rx_boosted_gain);
def("fem_rxgain", _parent->radio_fem_rxgain);
def("fem_txgain", _parent->radio_fem_txgain);
def("tx", _parent->tx_power_dbm);
def("af", _parent->airtime_factor);
def("rxdelay", _parent->rx_delay_base);
@@ -0,0 +1,69 @@
#include "LoRaFEMControl.h"
#include <Arduino.h>
#include <driver/rtc_io.h>
void LoRaFEMControl::init() {
#ifdef P_PA1_EN
rtc_gpio_hold_dis((gpio_num_t)P_PA1_EN);
pinMode(P_PA1_EN, OUTPUT);
setPAGainEnable(pa_gain_enabled);
#endif
#ifdef P_PRIMARY_LNA_EN
rtc_gpio_hold_dis((gpio_num_t)P_PRIMARY_LNA_EN);
pinMode(P_PRIMARY_LNA_EN, OUTPUT);
setRxModeEnable();
#endif
}
void LoRaFEMControl::setSleepModeEnable() {
#ifdef P_PA1_EN
// PA PL1 low/open selects the lower of the two hardware-jumper-selected levels.
digitalWrite(P_PA1_EN, !P_PA1_EN_ACTIVE);
#endif
#ifdef P_PRIMARY_LNA_EN
// Preserve the existing Station G3 power-off state.
digitalWrite(P_PRIMARY_LNA_EN, P_PRIMARY_LNA_EN_ACTIVE);
#endif
}
void LoRaFEMControl::setTxModeEnable() {
#ifdef P_PRIMARY_LNA_EN
digitalWrite(P_PRIMARY_LNA_EN, !P_PRIMARY_LNA_EN_ACTIVE);
#endif
}
void LoRaFEMControl::setRxModeEnable() {
#ifdef P_PRIMARY_LNA_EN
digitalWrite(P_PRIMARY_LNA_EN, lna_enabled ? P_PRIMARY_LNA_EN_ACTIVE : !P_PRIMARY_LNA_EN_ACTIVE);
#endif
}
void LoRaFEMControl::setLNAEnable(bool enabled) {
lna_enabled = enabled;
setRxModeEnable();
}
void LoRaFEMControl::setPAGainEnable(bool enabled) {
pa_gain_enabled = enabled;
#ifdef P_PA1_EN
digitalWrite(P_PA1_EN, enabled ? P_PA1_EN_ACTIVE : !P_PA1_EN_ACTIVE);
#endif
}
bool LoRaFEMControl::canControlLNA() const {
#ifdef P_PRIMARY_LNA_EN
return true;
#else
return false;
#endif
}
bool LoRaFEMControl::canControlPAGain() const {
#ifdef P_PA1_EN
return true;
#else
return false;
#endif
}
@@ -0,0 +1,20 @@
#pragma once
class LoRaFEMControl {
public:
void init();
void setSleepModeEnable();
void setTxModeEnable();
void setRxModeEnable();
void setLNAEnable(bool enabled);
void setPAGainEnable(bool enabled);
bool canControlLNA() const;
bool canControlPAGain() const;
bool isLNAEnabled() const { return lna_enabled; }
bool isPAGainEnabled() const { return pa_gain_enabled; }
private:
bool lna_enabled = true;
bool pa_gain_enabled = false;
};
+33 -2
View File
@@ -1,15 +1,46 @@
#include "StationG3Board.h"
void StationG3Board::powerOff() {
loRaFEMControl.setSleepModeEnable();
#ifdef P_PA1_EN
setPAModeHigh(false);
rtc_gpio_hold_en((gpio_num_t)P_PA1_EN);
#endif
#ifdef P_PRIMARY_LNA_EN
setPrimaryLNAControl(true);
rtc_gpio_hold_en((gpio_num_t)P_PRIMARY_LNA_EN);
#endif
ESP32Board::powerOff();
}
bool StationG3Board::setLoRaFemLnaEnabled(bool enable) {
if (!loRaFEMControl.canControlLNA()) {
return false;
}
loRaFEMControl.setLNAEnable(enable);
return true;
}
bool StationG3Board::canControlLoRaFemLna() const {
return loRaFEMControl.canControlLNA();
}
bool StationG3Board::isLoRaFemLnaEnabled() const {
return loRaFEMControl.isLNAEnabled();
}
bool StationG3Board::setLoRaFemPaGainEnabled(bool enable) {
if (!loRaFEMControl.canControlPAGain()) {
return false;
}
loRaFEMControl.setPAGainEnable(enable);
return true;
}
bool StationG3Board::canControlLoRaFemPaGain() const {
return loRaFEMControl.canControlPAGain();
}
bool StationG3Board::isLoRaFemPaGainEnabled() const {
return loRaFEMControl.isPAGainEnabled();
}
+15 -38
View File
@@ -3,45 +3,15 @@
#include <Arduino.h>
#include <helpers/ESP32Board.h>
#include <driver/rtc_io.h>
#ifndef P_PRIMARY_LNA_EN_ACTIVE
#define P_PRIMARY_LNA_EN_ACTIVE LOW
#endif
#ifndef P_PA1_EN_ACTIVE
#define P_PA1_EN_ACTIVE HIGH
#endif
#include "LoRaFEMControl.h"
class StationG3Board : public ESP32Board {
void setPAModeHigh(bool enabled) {
#ifdef P_PA1_EN
// Station G3 PA PL1 mode: LOW/open is PA low, HIGH/short is PA high.
digitalWrite(P_PA1_EN, enabled ? P_PA1_EN_ACTIVE : !P_PA1_EN_ACTIVE);
#endif
}
void setPrimaryLNAControl(bool enabled) {
#ifdef P_PRIMARY_LNA_EN
// Station G3 primary LNA mode is active-low: LOW/open is LNA on, HIGH/short is LNA off.
digitalWrite(P_PRIMARY_LNA_EN, enabled ? P_PRIMARY_LNA_EN_ACTIVE : !P_PRIMARY_LNA_EN_ACTIVE);
#endif
}
public:
LoRaFEMControl loRaFEMControl;
void begin() {
ESP32Board::begin();
#ifdef P_PA1_EN
rtc_gpio_hold_dis((gpio_num_t)P_PA1_EN);
pinMode(P_PA1_EN, OUTPUT);
setPAModeHigh(false);
#endif
#ifdef P_PRIMARY_LNA_EN
rtc_gpio_hold_dis((gpio_num_t)P_PRIMARY_LNA_EN);
pinMode(P_PRIMARY_LNA_EN, OUTPUT);
setPrimaryLNAControl(true);
#endif
loRaFEMControl.init();
esp_reset_reason_t reason = esp_reset_reason();
if (reason == ESP_RST_DEEPSLEEP) {
@@ -56,23 +26,30 @@ public:
}
void setPrimaryLNAEnable(bool enabled) {
setPrimaryLNAControl(enabled);
loRaFEMControl.setLNAEnable(enabled);
}
void setPrimaryPAHighPower(bool enabled) {
setPAModeHigh(enabled);
loRaFEMControl.setPAGainEnable(enabled);
}
void onBeforeTransmit() override {
ESP32Board::onBeforeTransmit();
setPrimaryLNAControl(false);
loRaFEMControl.setTxModeEnable();
}
void onAfterTransmit() override {
ESP32Board::onAfterTransmit();
setPrimaryLNAControl(true);
loRaFEMControl.setRxModeEnable();
}
bool setLoRaFemLnaEnabled(bool enable) override;
bool canControlLoRaFemLna() const override;
bool isLoRaFemLnaEnabled() const override;
bool setLoRaFemPaGainEnabled(bool enable) override;
bool canControlLoRaFemPaGain() const override;
bool isLoRaFemPaGainEnabled() const override;
void powerOff() override;
uint16_t getBattMilliVolts() override {
+2 -2
View File
@@ -17,11 +17,11 @@ build_flags =
-D P_LORA_SCLK=12
-D P_LORA_MISO=14
-D P_LORA_MOSI=13
-D P_PA1_EN=9 ; PA PL1 Mode: LOW/open is PA low, HIGH/short is PA high.
-D P_PA1_EN=9 ; PA PL1 Mode: LOW/open selects low level, HIGH/short selects high level.
-D P_PA1_EN_ACTIVE=HIGH
-D P_PRIMARY_LNA_EN=10 ; Primary Slot LNA Mode: LOW/open is LNA on, HIGH/short is LNA off.
-D P_PRIMARY_LNA_EN_ACTIVE=LOW
-D LORA_TX_POWER=7 ; configured as 7dbm, because the final output will be ~27dbm (~0.5w) if the PA is enabled.
-D LORA_TX_POWER=7 ; SX1262 input power to the Station G3 PA; final output depends on PA PL1/PL2 level.
-D MAX_LORA_TX_POWER=22
; -D P_LORA_TX_LED=35
-D PIN_BOARD_SDA=5