Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define TX_GAIN_LORA 0
#endif

#ifndef HAS_LORA_FEM
#define HAS_LORA_FEM 0
#endif

// -----------------------------------------------------------------------------
// Feature toggles
// -----------------------------------------------------------------------------
Expand Down
86 changes: 85 additions & 1 deletion src/graphics/draw/MenuHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,24 @@ uint8_t test_count = 0;

void menuHandler::loraMenu()
{
#if HAS_LORA_FEM
static const char *optionsArray[] = {"Back", "Device Role", "Radio Preset", "Frequency Slot", "LoRa Region", "LoRa FEM LNA"};
Comment thread
Quency-D marked this conversation as resolved.
Outdated
enum optionsNumbers {
Back = 0,
DeviceRolePicker = 1,
RadioPresetPicker = 2,
FrequencySlot = 3,
LoraPicker = 4,
LoraFemLna = 5
};
#else
static const char *optionsArray[] = {"Back", "Device Role", "Radio Preset", "Frequency Slot", "LoRa Region"};
enum optionsNumbers { Back = 0, DeviceRolePicker = 1, RadioPresetPicker = 2, FrequencySlot = 3, LoraPicker = 4 };
#endif
BannerOverlayOptions bannerOptions;
bannerOptions.message = "LoRa Actions";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 5;
bannerOptions.optionsCount = sizeof(optionsArray) / sizeof((optionsArray)[0]);
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Back) {
// No action
Expand All @@ -83,6 +95,11 @@ void menuHandler::loraMenu()
} else if (selected == LoraPicker) {
menuHandler::menuQueue = menuHandler::LoraPicker;
}
#if HAS_LORA_FEM
else if (selected == LoraFemLna) {
menuHandler::menuQueue = menuHandler::LoraFemLnaToggleMenu;
}
#endif
};
screen->showOverlayBanner(bannerOptions);
}
Expand Down Expand Up @@ -2632,6 +2649,68 @@ void menuHandler::messageBubblesMenu()
screen->showOverlayBanner(bannerOptions);
}

#if HAS_LORA_FEM
void menuHandler::LoRaFEMLNAToggleMenu()
{
static const LoRaFEMLNAToggleOption femToggleOptions[] = {
{"Back", OptionsAction::Back},
{"Enabled", OptionsAction::Select, meshtastic_Config_LoRaConfig_FEM_LNA_Mode_ENABLED},
{"Disabled", OptionsAction::Select, meshtastic_Config_LoRaConfig_FEM_LNA_Mode_DISABLED},
};
constexpr size_t toggleCount = sizeof(femToggleOptions) / sizeof(femToggleOptions[0]);
static std::array<const char *, toggleCount> toggleLabels{};
BannerOverlayOptions bannerOptions;

if (loraFEMInterface.isLnaCanControl()) {
bannerOptions = createStaticBannerOptions("Toggle FEM LNA", femToggleOptions, toggleLabels,
[](const LoRaFEMLNAToggleOption &option, int) -> void {
if (option.action == OptionsAction::Back) {
menuQueue = LoraMenu;
screen->runNow();
return;
}

if (!option.hasValue) {
return;
}

if (config.lora.fem_lna_mode == option.value) {
return;
}

config.lora.fem_lna_mode = option.value;
service->reloadConfig(SEGMENT_CONFIG);
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
});

int initialSelection = 0;
for (size_t i = 0; i < toggleCount; ++i) {
if (femToggleOptions[i].hasValue && config.lora.fem_lna_mode == femToggleOptions[i].value) {
Comment thread
caveman99 marked this conversation as resolved.
Outdated
initialSelection = static_cast<int>(i);
break;
}
}
bannerOptions.InitialSelected = initialSelection;
} else {
static const char *optionsArray[] = {"Back"};
enum optionsNumbers { Back = 0 };

// BannerOverlayOptions bannerOptions;
Comment thread
Quency-D marked this conversation as resolved.
Outdated
bannerOptions.message = "LNA Control Unsupported";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 1;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Back) {
// No action
}
};
LOG_INFO("LNA cannot be disabled on this device");
}

screen->showOverlayBanner(bannerOptions);
}
#endif

void menuHandler::handleMenuSwitch(OLEDDisplay *display)
{
if (menuQueue != MenuNone)
Expand Down Expand Up @@ -2782,6 +2861,11 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case MessageBubblesMenu:
messageBubblesMenu();
break;
#if HAS_LORA_FEM
case LoraFemLnaToggleMenu:
LoRaFEMLNAToggleMenu();
break;
#endif
}
menuQueue = MenuNone;
}
Expand Down
10 changes: 9 additions & 1 deletion src/graphics/draw/MenuHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ class menuHandler
NodeNameLengthMenu,
FrameToggles,
DisplayUnits,
MessageBubblesMenu
MessageBubblesMenu,
LoraFemLnaToggleMenu
};
static screenMenus menuQueue;
static uint32_t pickedNodeNum; // node selected by NodePicker for ManageNodeMenu
Expand Down Expand Up @@ -111,6 +112,9 @@ class menuHandler
static void displayUnitsMenu();
static void messageBubblesMenu();
static void textMessageMenu();
#if HAS_LORA_FEM
static void LoRaFEMLNAToggleMenu();
#endif

private:
static void saveUIConfig();
Expand Down Expand Up @@ -160,5 +164,9 @@ using PositionMenuOption = MenuOption<int>;
using ManageNodeOption = MenuOption<int>;
using ClockFaceOption = MenuOption<bool>;

#if HAS_LORA_FEM
using LoRaFEMLNAToggleOption = MenuOption<meshtastic_Config_LoRaConfig_FEM_LNA_Mode>;
Comment thread
caveman99 marked this conversation as resolved.
Outdated
#endif

} // namespace graphics
#endif
192 changes: 192 additions & 0 deletions src/mesh/LoRaFEMInterface.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
#if HAS_LORA_FEM
#include "LoRaFEMInterface.h"

#if defined(ARCH_ESP32)
#include <driver/rtc_io.h>
#include <esp_sleep.h>
#endif

LoRaFEMInterface loraFEMInterface;
void LoRaFEMInterface::init(void)
{
setLnaCanControl(false); // Default is uncontrollable
#ifdef HELTEC_V4
pinMode(LORA_PA_POWER, OUTPUT);
digitalWrite(LORA_PA_POWER, HIGH);
rtc_gpio_hold_dis((gpio_num_t)LORA_PA_POWER);
delay(1);
rtc_gpio_hold_dis((gpio_num_t)LORA_KCT8103L_PA_CSD);
pinMode(LORA_KCT8103L_PA_CSD, INPUT); // detect which FEM is used
delay(1);
if (digitalRead(LORA_KCT8103L_PA_CSD) == HIGH) {
// FEM is KCT8103L
fem_type = KCT8103L_PA;
rtc_gpio_hold_dis((gpio_num_t)LORA_KCT8103L_PA_CTX);
pinMode(LORA_KCT8103L_PA_CSD, OUTPUT);
digitalWrite(LORA_KCT8103L_PA_CSD, HIGH);
pinMode(LORA_KCT8103L_PA_CTX, OUTPUT);
digitalWrite(LORA_KCT8103L_PA_CTX, HIGH);
setLnaCanControl(true);
} else if (digitalRead(LORA_KCT8103L_PA_CSD) == LOW) {
// FEM is GC1109
fem_type = GC1109_PA;
// LORA_GC1109_PA_EN and LORA_KCT8103L_PA_CSD are the same pin and do not need to be repeatedly turned off and held.
// rtc_gpio_hold_dis((gpio_num_t)LORA_GC1109_PA_EN);
pinMode(LORA_GC1109_PA_EN, OUTPUT);
digitalWrite(LORA_GC1109_PA_EN, HIGH);
pinMode(LORA_GC1109_PA_TX_EN, OUTPUT);
digitalWrite(LORA_GC1109_PA_TX_EN, LOW);
} else {
fem_type = OTHER_FEM_TYPES;
}
#elif defined(USE_GC1109_PA)
fem_type = GC1109_PA;
#if defined(ARCH_ESP32)
rtc_gpio_hold_dis((gpio_num_t)LORA_PA_POWER);
rtc_gpio_hold_dis((gpio_num_t)LORA_GC1109_PA_EN);
rtc_gpio_hold_dis((gpio_num_t)LORA_GC1109_PA_TX_EN);
#endif
pinMode(LORA_PA_POWER, OUTPUT);
digitalWrite(LORA_PA_POWER, HIGH);
delay(1);
pinMode(LORA_GC1109_PA_EN, OUTPUT);
digitalWrite(LORA_GC1109_PA_EN, HIGH);
pinMode(LORA_GC1109_PA_TX_EN, OUTPUT);
digitalWrite(LORA_GC1109_PA_TX_EN, LOW);
#endif
}

void LoRaFEMInterface::setSleepModeEnable(void)
{
#ifdef HELTEC_V4
if (fem_type == GC1109_PA) {
/*
* Do not switch the power on and off frequently.
* After turning off LORA_PA_EN, the power consumption has dropped to the uA level.
Comment thread
Quency-D marked this conversation as resolved.
Outdated
*/
digitalWrite(LORA_GC1109_PA_EN, LOW);
digitalWrite(LORA_GC1109_PA_TX_EN, LOW);
} else if (fem_type == KCT8103L_PA) {
// shutdown the PA
digitalWrite(LORA_KCT8103L_PA_CSD, LOW);
}
#elif defined(USE_GC1109_PA)
digitalWrite(LORA_GC1109_PA_EN, LOW);
digitalWrite(LORA_GC1109_PA_TX_EN, LOW);
#endif
}

void LoRaFEMInterface::setTxModeEnable(void)
{
#ifdef HELTEC_V4
if (fem_type == GC1109_PA) {
digitalWrite(LORA_GC1109_PA_EN, HIGH); // CSD=1: Chip enabled
digitalWrite(LORA_GC1109_PA_TX_EN, HIGH); // CPS: 1=full PA, 0=bypass (for RX, CPS is don't care)
} else if (fem_type == KCT8103L_PA) {
digitalWrite(LORA_KCT8103L_PA_CSD, HIGH);
digitalWrite(LORA_KCT8103L_PA_CTX, HIGH);
}
#elif defined(USE_GC1109_PA)
digitalWrite(LORA_GC1109_PA_EN, HIGH); // CSD=1: Chip enabled
digitalWrite(LORA_GC1109_PA_TX_EN, HIGH); // CPS: 1=full PA, 0=bypass (for RX, CPS is don't care)
#endif
}

void LoRaFEMInterface::setRxModeEnable(void)
{
#ifdef HELTEC_V4
if (fem_type == GC1109_PA) {
digitalWrite(LORA_GC1109_PA_EN, HIGH); // CSD=1: Chip enabled
digitalWrite(LORA_GC1109_PA_TX_EN, LOW);
} else if (fem_type == KCT8103L_PA) {
digitalWrite(LORA_KCT8103L_PA_CSD, HIGH);
if (lna_enabled) {
digitalWrite(LORA_KCT8103L_PA_CTX, LOW);
} else {
digitalWrite(LORA_KCT8103L_PA_CTX, HIGH);
}
}
#elif defined(USE_GC1109_PA)
digitalWrite(LORA_GC1109_PA_EN, HIGH); // CSD=1: Chip enabled
digitalWrite(LORA_GC1109_PA_TX_EN, LOW);
#endif
}

void LoRaFEMInterface::setRxModeEnableWhenMCUSleep(void)
{

#ifdef HELTEC_V4
// Keep GC1109 FEM powered during deep sleep so LNA remains active for RX wake.
// Set PA_POWER and PA_EN HIGH (overrides SX126xInterface::sleep() shutdown),
// then latch with RTC hold so the state survives deep sleep.
digitalWrite(LORA_PA_POWER, HIGH);
rtc_gpio_hold_en((gpio_num_t)LORA_PA_POWER);
if (fem_type == GC1109_PA) {
digitalWrite(LORA_GC1109_PA_EN, HIGH);
rtc_gpio_hold_en((gpio_num_t)LORA_GC1109_PA_EN);
gpio_pulldown_en((gpio_num_t)LORA_GC1109_PA_TX_EN);
} else if (fem_type == KCT8103L_PA) {
digitalWrite(LORA_KCT8103L_PA_CSD, HIGH);
rtc_gpio_hold_en((gpio_num_t)LORA_KCT8103L_PA_CSD);
if (lna_enabled) {
digitalWrite(LORA_KCT8103L_PA_CTX, LOW);
} else {
digitalWrite(LORA_KCT8103L_PA_CTX, HIGH);
}
rtc_gpio_hold_en((gpio_num_t)LORA_KCT8103L_PA_CTX);
}
#elif defined(USE_GC1109_PA)
digitalWrite(LORA_PA_POWER, HIGH);
digitalWrite(LORA_GC1109_PA_EN, HIGH);
#if defined(ARCH_ESP32)
rtc_gpio_hold_en((gpio_num_t)LORA_PA_POWER);
rtc_gpio_hold_en((gpio_num_t)LORA_GC1109_PA_EN);
gpio_pulldown_en((gpio_num_t)LORA_GC1109_PA_TX_EN);
#endif
#endif
}

void LoRaFEMInterface::setLNAEnable(bool enabled)
{
lna_enabled = enabled;
}

int8_t LoRaFEMInterface::powerConversion(int8_t loraOutputPower)
{
#ifdef HELTEC_V4
const uint16_t gc1109_tx_gain[] = {11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 9, 8, 7};
const uint16_t kct8103l_tx_gain[] = {13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 12, 12, 11, 11, 10, 9, 8, 7};
uint16_t *tx_gain, tx_gain_num;
if (fem_type == GC1109_PA) {
tx_gain = (uint16_t *)gc1109_tx_gain;
tx_gain_num = sizeof(gc1109_tx_gain) / sizeof(gc1109_tx_gain[0]);
} else if (fem_type == KCT8103L_PA) {
tx_gain = (uint16_t *)kct8103l_tx_gain;
Comment thread
Quency-D marked this conversation as resolved.
Outdated
tx_gain_num = sizeof(kct8103l_tx_gain) / sizeof(kct8103l_tx_gain[0]);
} else {
return loraOutputPower;
}
#else
#ifdef ARCH_PORTDUINO
size_t num_pa_points = portduino_config.num_pa_points;
const uint16_t *tx_gain = portduino_config.tx_gain_lora;
uint16_t tx_gain_num = NUM_PA_POINTS;
#else
size_t num_pa_points = NUM_PA_POINTS;
Comment thread
Quency-D marked this conversation as resolved.
const uint16_t tx_gain[NUM_PA_POINTS] = {TX_GAIN_LORA};
uint16_t tx_gain_num = NUM_PA_POINTS;
#endif
#endif
for (int radio_dbm = 0; radio_dbm < tx_gain_num; radio_dbm++) {
if (((radio_dbm + tx_gain[radio_dbm]) > loraOutputPower) ||
((radio_dbm == (tx_gain_num - 1)) && ((radio_dbm + tx_gain[radio_dbm]) <= loraOutputPower))) {
// we've exceeded the power limit, or hit the max we can do
LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", loraOutputPower, tx_gain[radio_dbm]);
loraOutputPower -= tx_gain[radio_dbm];
break;
}
}
return loraOutputPower;
}

#endif
31 changes: 31 additions & 0 deletions src/mesh/LoRaFEMInterface.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#if HAS_LORA_FEM
#pragma once
Comment thread
Quency-D marked this conversation as resolved.
Outdated
#include "NodeDB.h"
Comment thread
Quency-D marked this conversation as resolved.
Outdated
#include "configuration.h"
#include <stdint.h>

typedef enum { GC1109_PA, KCT8103L_PA, OTHER_FEM_TYPES } LoRaFEMType;

class LoRaFEMInterface
{
public:
LoRaFEMInterface() {}
virtual ~LoRaFEMInterface() {}
void init(void);
void setSleepModeEnable(void);
void setTxModeEnable(void);
void setRxModeEnable(void);
void setRxModeEnableWhenMCUSleep(void);
void setLNAEnable(bool enabled);
int8_t powerConversion(int8_t loraOutputPower);
bool isLnaCanControl(void) { return lna_can_control; }
void setLnaCanControl(bool can_control) { lna_can_control = can_control; }

private:
LoRaFEMType fem_type;
bool lna_enabled = false;
bool lna_can_control = false;
};
extern LoRaFEMInterface loraFEMInterface;

#endif
Loading
Loading