diff --git a/docs/cli_commands.md b/docs/cli_commands.md index b618ae2bfe..8772b929fe 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -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 ` + +**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 diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index b03334c1c9..2c33406632 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -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"); } diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index 39a5386a9f..5e67daaa91 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -32,6 +32,8 @@ class NodePrefs : public ConfigSerializer { // persisted to file 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) @@ -50,7 +52,8 @@ class NodePrefs : public ConfigSerializer { // persisted to file //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); @@ -133,4 +136,4 @@ class NodePrefs : public ConfigSerializer { // persisted to file // new accessor methods bool isRepeatEn() const { return repeat.disable_fwd == 0; } void setRepeatEn(bool en) { repeat.disable_fwd = en ? 0 : 1; } -}; \ No newline at end of file +}; diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index c93ba1a4cd..d3c2e1604d 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -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(); diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 0aff39cc1a..f7ec148035 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -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(); diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 17f8e323e5..9bfa5ec6a0 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -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(); diff --git a/src/MeshCore.h b/src/MeshCore.h index 89e60b1f7e..e67371ef17 100644 --- a/src/MeshCore.h +++ b/src/MeshCore.h @@ -67,6 +67,10 @@ class MainBoard { 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; } diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 07181e16ad..b318bb58e8 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -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)); diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 2a9ec43bcb..237c758e9f 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -65,6 +65,7 @@ class NodePrefs : public ConfigSerializer { 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 @@ class NodePrefs : public ConfigSerializer { 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); diff --git a/test/test_companion_node_prefs/test_companion_node_prefs.cpp b/test/test_companion_node_prefs/test_companion_node_prefs.cpp new file mode 100644 index 0000000000..6d3cebdfe9 --- /dev/null +++ b/test/test_companion_node_prefs/test_companion_node_prefs.cpp @@ -0,0 +1,80 @@ +#include + +#include +#include +#include + +#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(text), length); + } + +public: + size_t write(uint8_t value) override { + _text.push_back(static_cast(value)); + return 1; + } + + size_t write(const uint8_t* buffer, size_t size) override { + _text.append(reinterpret_cast(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(); +} diff --git a/test/test_config_serializer/test_config_serializer.cpp b/test/test_config_serializer/test_config_serializer.cpp index 7a13f487e2..27c3c8119e 100644 --- a/test/test_config_serializer/test_config_serializer.cpp +++ b/test/test_config_serializer/test_config_serializer.cpp @@ -1,6 +1,14 @@ #include #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 @@ class MockInputStream : public Stream { 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 @@ class MockPrintStream : public Stream { 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(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 ─────────────────────────────────────────────────────── diff --git a/variants/station_g3_esp32/LoRaFEMControl.cpp b/variants/station_g3_esp32/LoRaFEMControl.cpp new file mode 100644 index 0000000000..8d4f90f6fa --- /dev/null +++ b/variants/station_g3_esp32/LoRaFEMControl.cpp @@ -0,0 +1,79 @@ +#include "LoRaFEMControl.h" + +#include +#include + +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 +} diff --git a/variants/station_g3_esp32/LoRaFEMControl.h b/variants/station_g3_esp32/LoRaFEMControl.h new file mode 100644 index 0000000000..f622de91e4 --- /dev/null +++ b/variants/station_g3_esp32/LoRaFEMControl.h @@ -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; +}; diff --git a/variants/station_g3_esp32/StationG3Board.cpp b/variants/station_g3_esp32/StationG3Board.cpp index 4a49831107..dd863aca61 100644 --- a/variants/station_g3_esp32/StationG3Board.cpp +++ b/variants/station_g3_esp32/StationG3Board.cpp @@ -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(); +} diff --git a/variants/station_g3_esp32/StationG3Board.h b/variants/station_g3_esp32/StationG3Board.h index 4b1fb81c10..52628eb6cc 100644 --- a/variants/station_g3_esp32/StationG3Board.h +++ b/variants/station_g3_esp32/StationG3Board.h @@ -3,45 +3,15 @@ #include #include #include - -#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 @@ class StationG3Board : public ESP32Board { } 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 { diff --git a/variants/station_g3_esp32/platformio.ini b/variants/station_g3_esp32/platformio.ini index e4a66a18ee..074d6a2ed4 100644 --- a/variants/station_g3_esp32/platformio.ini +++ b/variants/station_g3_esp32/platformio.ini @@ -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