mirror of
https://github.com/meshcore-dev/MeshCore.git
synced 2026-08-14 07:19:46 +00:00
Merge pull request #3137 from agessaman/feat/station-g3-fem-prefs
Station G3: Expose, persist, and apply FEM gain preferences
This commit is contained in:
@@ -291,6 +291,22 @@ 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.
|
||||
- The setting is saved immediately, but on Station G3 the level is applied to the hardware at the start of the next transmit, so that the PA supply rail is never re-targeted while the PA is being driven. `get` reports the configured state, which may lead the hardware until the node next transmits.
|
||||
|
||||
---
|
||||
|
||||
### System
|
||||
|
||||
#### View or change this node's name
|
||||
|
||||
@@ -885,6 +885,8 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
|
||||
_prefs.tx_power_dbm = LORA_TX_POWER;
|
||||
_prefs.gps_enabled = 0; // GPS disabled by default
|
||||
_prefs.gps_interval = 0; // No automatic GPS updates by default
|
||||
_prefs.radio_fem_rxgain = 1;
|
||||
_prefs.radio_fem_txgain = 0;
|
||||
//_prefs.rx_delay_base = 10.0f; enable once new algo fixed
|
||||
_prefs.setRepeatEn(false);
|
||||
#if defined(USE_SX1262) || defined(USE_SX1268)
|
||||
@@ -974,6 +976,8 @@ void MyMesh::begin(bool has_display) {
|
||||
radio_driver.setParams(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||
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);
|
||||
MESH_DEBUG_PRINTLN("RX Boosted Gain Mode: %s",
|
||||
radio_driver.getRxBoostedGainMode() ? "Enabled" : "Disabled");
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ public:
|
||||
uint32_t gps_interval = 0; // GPS read interval in seconds
|
||||
uint8_t autoadd_config = 0; // bitmask for auto-add contacts config
|
||||
uint8_t rx_boosted_gain = 0; // SX126x RX boosted gain mode (0=power saving, 1=boosted)
|
||||
uint8_t radio_fem_rxgain = 0; // external LoRa FEM RX gain (LNA)
|
||||
uint8_t radio_fem_txgain = 0; // external LoRa FEM TX gain (low by default)
|
||||
uint8_t _client_repeat = 0; // DEPRECATED -> use repeat.disable_fwd
|
||||
uint8_t path_hash_mode = 0; // which path mode to use when sending
|
||||
uint8_t autoadd_max_hops = 0; // 0 = no limit, 1 = direct (0 hops), N = up to N-1 hops (max 64)
|
||||
@@ -51,7 +53,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);
|
||||
@@ -135,4 +138,4 @@ public:
|
||||
// new accessor methods
|
||||
bool isRepeatEn() const { return repeat.disable_fwd == 0; }
|
||||
void setRepeatEn(bool en) { repeat.disable_fwd = en ? 0 : 1; }
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -133,6 +133,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 +563,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 +850,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));
|
||||
|
||||
@@ -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)
|
||||
@@ -83,7 +84,8 @@ private:
|
||||
def(radio_cad_key, _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,80 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include "../../examples/companion_radio/NodePrefs.h"
|
||||
|
||||
class ReplayStream : public Stream {
|
||||
const char* _text;
|
||||
int _pos = 0;
|
||||
int _len;
|
||||
|
||||
public:
|
||||
explicit ReplayStream(const char* text) : _text(text), _len(strlen(text)) { }
|
||||
|
||||
int available() override { return _len - _pos; }
|
||||
int read() override { return _pos < _len ? _text[_pos++] : -1; }
|
||||
int peek() override { return _pos < _len ? _text[_pos] : -1; }
|
||||
};
|
||||
|
||||
class CaptureStream : public Stream {
|
||||
std::string _text;
|
||||
|
||||
size_t emit(long long value) {
|
||||
char text[24];
|
||||
int length = snprintf(text, sizeof(text), "%lld", value);
|
||||
return write(reinterpret_cast<const uint8_t*>(text), length);
|
||||
}
|
||||
|
||||
public:
|
||||
size_t write(uint8_t value) override {
|
||||
_text.push_back(static_cast<char>(value));
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t write(const uint8_t* buffer, size_t size) override {
|
||||
_text.append(reinterpret_cast<const char*>(buffer), size);
|
||||
return size;
|
||||
}
|
||||
|
||||
size_t print(unsigned char value, int = DEC) override { return emit(value); }
|
||||
size_t print(int value, int = DEC) override { return emit(value); }
|
||||
size_t print(unsigned int value, int = DEC) override { return emit(value); }
|
||||
size_t print(long value, int = DEC) override { return emit(value); }
|
||||
size_t print(unsigned long value, int = DEC) override { return emit(value); }
|
||||
size_t print(long long value, int = DEC) override { return emit(value); }
|
||||
size_t print(unsigned long long value, int = DEC) override { return emit(value); }
|
||||
|
||||
const std::string& text() const { return _text; }
|
||||
};
|
||||
|
||||
TEST(CompanionNodePrefs, RxGainSettingsRoundTripIndependently) {
|
||||
NodePrefs saved;
|
||||
saved.rx_boosted_gain = 0;
|
||||
saved.radio_fem_rxgain = 1;
|
||||
saved.radio_fem_txgain = 0;
|
||||
|
||||
CaptureStream output;
|
||||
ASSERT_TRUE(saved.saveSerial(output));
|
||||
EXPECT_NE(std::string::npos, output.text().find("rxgain:0"));
|
||||
EXPECT_NE(std::string::npos, output.text().find("fem_rxgain:1"));
|
||||
EXPECT_NE(std::string::npos, output.text().find("fem_txgain:0"));
|
||||
|
||||
ReplayStream input("{radio:{rxgain:1,fem_rxgain:0,fem_txgain:1}}");
|
||||
NodePrefs loaded;
|
||||
loaded.rx_boosted_gain = 0;
|
||||
loaded.radio_fem_rxgain = 1;
|
||||
loaded.radio_fem_txgain = 0;
|
||||
|
||||
ASSERT_TRUE(loaded.loadSerial(input));
|
||||
EXPECT_EQ(1, loaded.rx_boosted_gain);
|
||||
EXPECT_EQ(0, loaded.radio_fem_rxgain);
|
||||
EXPECT_EQ(1, loaded.radio_fem_txgain);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -1,6 +1,14 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "helpers/ConfigSerializer.h"
|
||||
|
||||
class NativeFileSystem {
|
||||
public:
|
||||
void mkdir(const char*) { }
|
||||
};
|
||||
#define FILESYSTEM NativeFileSystem
|
||||
#include "helpers/CommonCLI.h"
|
||||
#undef FILESYSTEM
|
||||
|
||||
#define TEST_INT_S "56"
|
||||
#define TEST_INT 56
|
||||
#define TEST_FLOAT_S "-6.123"
|
||||
@@ -21,6 +29,19 @@ public:
|
||||
class MockPrintStream : public Stream {
|
||||
int len = 0;
|
||||
uint8_t _buf[1024];
|
||||
|
||||
size_t printSigned(long long value) {
|
||||
char text[24];
|
||||
snprintf(text, sizeof(text), "%lld", value);
|
||||
return Print::print(text);
|
||||
}
|
||||
|
||||
size_t printUnsigned(unsigned long long value) {
|
||||
char text[24];
|
||||
snprintf(text, sizeof(text), "%llu", value);
|
||||
return Print::print(text);
|
||||
}
|
||||
|
||||
public:
|
||||
size_t write(uint8_t b) override {
|
||||
if (len < sizeof(_buf)) {
|
||||
@@ -30,17 +51,17 @@ public:
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t print(unsigned char b, int r) override { if (b == TEST_INT) return Print::print(TEST_INT_S); return 0; }
|
||||
size_t print(int v, int r) override { if (v == TEST_INT) return Print::print(TEST_INT_S); return 0; }
|
||||
size_t print(unsigned int v, int r) override { if (v == TEST_INT) return Print::print(TEST_INT_S); return 0; }
|
||||
size_t print(long v, int r) override { if (v == TEST_INT) return Print::print(TEST_INT_S); return 0; }
|
||||
size_t print(unsigned long v, int r) override { if (v == TEST_INT) return Print::print(TEST_INT_S); return 0; }
|
||||
size_t print(long long v, int r) override { if (v == TEST_INT) return Print::print(TEST_INT_S); return 0; }
|
||||
size_t print(unsigned long long v, int r) override { if (v == TEST_INT) return Print::print(TEST_INT_S); return 0; }
|
||||
size_t print(double v, int p = 2) override {
|
||||
if (p == 6) return Print::print(TEST_DOUBLE_S);
|
||||
if (p == 4) return Print::print(TEST_FLOAT_S);
|
||||
return 0;
|
||||
size_t print(unsigned char v, int r) override { return printUnsigned(v); }
|
||||
size_t print(int v, int r) override { return printSigned(v); }
|
||||
size_t print(unsigned int v, int r) override { return printUnsigned(v); }
|
||||
size_t print(long v, int r) override { return printSigned(v); }
|
||||
size_t print(unsigned long v, int r) override { return printUnsigned(v); }
|
||||
size_t print(long long v, int r) override { return printSigned(v); }
|
||||
size_t print(unsigned long long v, int r) override { return printUnsigned(v); }
|
||||
size_t print(double v, int p = 2) override {
|
||||
char text[32];
|
||||
snprintf(text, sizeof(text), "%.*f", p, v);
|
||||
return Print::print(text);
|
||||
}
|
||||
|
||||
int getLength() const { return len; }
|
||||
@@ -171,6 +192,28 @@ TEST(ConfigSerializer, LoadSerial_IgnoreUnknowns) {
|
||||
EXPECT_TRUE(match);
|
||||
}
|
||||
|
||||
TEST(NodePrefs, FemGainSettingsRoundTrip) {
|
||||
NodePrefs saved;
|
||||
saved.radio_fem_rxgain = 0;
|
||||
saved.radio_fem_txgain = 1;
|
||||
|
||||
MockPrintStream output;
|
||||
ASSERT_TRUE(saved.saveSerial(output));
|
||||
|
||||
std::string serialised(reinterpret_cast<const char*>(output.getBytes()), output.getLength());
|
||||
EXPECT_NE(std::string::npos, serialised.find("fem_rxgain:0"));
|
||||
EXPECT_NE(std::string::npos, serialised.find("fem_txgain:1"));
|
||||
|
||||
MockInputStream input(serialised.c_str());
|
||||
NodePrefs loaded;
|
||||
loaded.radio_fem_rxgain = 1;
|
||||
loaded.radio_fem_txgain = 0;
|
||||
|
||||
ASSERT_TRUE(loaded.loadSerial(input));
|
||||
EXPECT_EQ(0, loaded.radio_fem_rxgain);
|
||||
EXPECT_EQ(1, loaded.radio_fem_txgain);
|
||||
}
|
||||
|
||||
|
||||
// ── main ───────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
#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);
|
||||
applyPAGain();
|
||||
#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() {
|
||||
// Latch the requested PA level here, before the SX1262 starts driving the PA. PA PL1
|
||||
// retargets the PA's DC-DC rail, so moving it mid-transmit collapses the supply while
|
||||
// the PA is still driven at full input power.
|
||||
applyPAGain();
|
||||
#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) {
|
||||
// Recorded only -- the pin is driven from setTxModeEnable(). The PA level only matters
|
||||
// while transmitting, so deferring costs nothing and keeps the rail change out of an
|
||||
// in-flight transmit (the CLI runs on every main-loop pass, including mid-TX).
|
||||
pa_gain_enabled = enabled;
|
||||
}
|
||||
|
||||
void LoRaFEMControl::applyPAGain() {
|
||||
#ifdef P_PA1_EN
|
||||
digitalWrite(P_PA1_EN, pa_gain_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,22 @@
|
||||
#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:
|
||||
void applyPAGain();
|
||||
|
||||
bool lna_enabled = true;
|
||||
bool pa_gain_enabled = false;
|
||||
};
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user