mirror of
https://github.com/meshtastic/firmware.git
synced 2026-06-07 10:58:53 +00:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ce23bcf91f | |||
| d3d606c1d5 | |||
| b5940fc9f0 | |||
| 9b11352d42 | |||
| 1daeb2edce | |||
| f6956160bb | |||
| 63d0781e29 | |||
| bfc536f96c | |||
| 7d7bfbcf24 | |||
| 8fcaf5ff30 | |||
| 3773683df6 | |||
| 2c782223a5 | |||
| dcabb85000 | |||
| 9c3ceaf6e9 | |||
| b2c07708bd | |||
| b5737d089b | |||
| 3ccec98b58 | |||
| 30a3f39ed0 | |||
| 55da30ab58 | |||
| 078cd68191 | |||
| e932c9786f | |||
| 8d1659a993 | |||
| d04509bbf0 | |||
| 8d1674c3e5 | |||
| af87c55530 | |||
| 1898aec281 | |||
| dd2a4a3121 | |||
| a77836279a | |||
| ebc41e571c |
@@ -383,8 +383,10 @@ void InputBroker::Init()
|
||||
rotaryEncoderInterruptImpl1 = nullptr;
|
||||
}
|
||||
#endif
|
||||
#if !MESHTASTIC_EXCLUDE_I2C
|
||||
cardKbI2cImpl = new CardKbI2cImpl();
|
||||
cardKbI2cImpl->init();
|
||||
#endif
|
||||
#if defined(M5STACK_UNITC6L)
|
||||
i2cButton = new i2cButtonThread("i2cButtonThread");
|
||||
#endif
|
||||
|
||||
+27
-5
@@ -221,6 +221,8 @@ bool pauseBluetoothLogging = false;
|
||||
|
||||
bool pmu_found;
|
||||
|
||||
uint8_t pa_fan_percentage = 50;
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_I2C
|
||||
// Array map of sensor types with i2c address and wire as we'll find in the i2c scan
|
||||
std::pair<uint8_t, TwoWire *> nodeTelemetrySensorsMap[_meshtastic_TelemetrySensorType_MAX + 1] = {};
|
||||
@@ -994,16 +996,36 @@ void setup()
|
||||
mqttInit();
|
||||
#endif
|
||||
|
||||
#ifdef RF95_FAN_EN
|
||||
// Ability to disable FAN if PIN has been set with RF95_FAN_EN.
|
||||
#ifdef RADIO_FAN_EN
|
||||
// Ability to disable FAN if PIN has been set with RADIO_FAN_EN.
|
||||
// Make sure LoRa has been started before disabling FAN.
|
||||
if (config.lora.pa_fan_disabled)
|
||||
digitalWrite(RF95_FAN_EN, LOW ^ 0);
|
||||
#ifdef RADIO_FAN_PWM
|
||||
#if defined(ARCH_ESP32)
|
||||
// Set up PWM at Channel 1 at 25KHz, using 8-bit resolution
|
||||
// Turn ON/OFF fan to the specified value if enabled by config.
|
||||
// code by https://github.com/gjelsoe/
|
||||
if (ledcSetup(1, 25000, 8)) {
|
||||
ledcAttachPin(RADIO_FAN_EN, 1);
|
||||
LOG_INFO("PWM init C1 P%d\n", RADIO_FAN_EN);
|
||||
// Set PWM duty cycle based on fan disabled state
|
||||
ledcWrite(1, config.lora.pa_fan_disabled ? 0 : (pa_fan_percentage * 2.55));
|
||||
} else {
|
||||
LOG_WARN("PWM init fail P%d\n", RADIO_FAN_EN);
|
||||
}
|
||||
#elif defined(ARCH_NRF52)
|
||||
pinMode(RADIO_FAN_EN, OUTPUT);
|
||||
analogWrite(RADIO_FAN_EN, config.lora.pa_fan_disabled ? 0 : (pa_fan_percentage * 2.55));
|
||||
#endif
|
||||
#else
|
||||
// Set up as ON/OFF switch of fan; default on unless disabled by config.
|
||||
pinMode(RADIO_FAN_EN, OUTPUT);
|
||||
digitalWrite(RADIO_FAN_EN, config.lora.pa_fan_disabled ? (LOW ^ 0) : (HIGH ^ 0));
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef ARCH_PORTDUINO
|
||||
|
||||
// Initialize Wifi
|
||||
// Initialize Wifi
|
||||
#if HAS_WIFI
|
||||
initWifi();
|
||||
#endif
|
||||
|
||||
@@ -90,6 +90,8 @@ extern bool suppressRebootBanner;
|
||||
|
||||
extern uint32_t serialSinceMsec;
|
||||
|
||||
extern uint8_t pa_fan_percentage;
|
||||
|
||||
// If a thread does something that might need for it to be rescheduled ASAP it can set this flag
|
||||
// This will suppress the current delay and instead try to run ASAP.
|
||||
extern bool runASAP;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#if RADIOLIB_EXCLUDE_LR11X0 != 1
|
||||
#include "LR11x0Interface.h"
|
||||
#include "RadioExternalPa.h"
|
||||
#include "Throttle.h"
|
||||
#include "configuration.h"
|
||||
#include "error.h"
|
||||
#include "main.h"
|
||||
#include "mesh/NodeDB.h"
|
||||
#ifdef LR11X0_DIO_AS_RF_SWITCH
|
||||
#include "rfswitch.h"
|
||||
@@ -54,6 +56,8 @@ template <typename T> bool LR11x0Interface<T>::init()
|
||||
digitalWrite(LR11X0_POWER_EN, HIGH);
|
||||
#endif
|
||||
|
||||
enableFan();
|
||||
|
||||
#if ARCH_PORTDUINO
|
||||
float tcxoVoltage = (float)portduino_config.dio3_tcxo_voltage / 1000;
|
||||
// FIXME: correct logic to default to not using TCXO if no voltage is specified for LR11x0_DIO3_TCXO_VOLTAGE
|
||||
@@ -184,8 +188,10 @@ template <typename T> bool LR11x0Interface<T>::reconfigure()
|
||||
if (err != RADIOLIB_ERR_NONE)
|
||||
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
||||
|
||||
if (power > LR1110_MAX_POWER) // This chip has lower power limits than some
|
||||
power = LR1110_MAX_POWER;
|
||||
// Re-apply regulatory limits and any external-PA power mapping. applyModemConfig()
|
||||
// (run by the base reconfigure()) reset `power` to the requested total, so without
|
||||
// this a runtime tx_power change would skip region clamping and external-PA mapping.
|
||||
limitPower(LR1110_MAX_POWER);
|
||||
if ((power > LR1120_MAX_POWER) && (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) // 2.4G power limit
|
||||
power = LR1120_MAX_POWER;
|
||||
|
||||
@@ -236,6 +242,7 @@ template <typename T> void LR11x0Interface<T>::addReceiveMetadata(meshtastic_Mes
|
||||
*/
|
||||
template <typename T> void LR11x0Interface<T>::configHardwareForSend()
|
||||
{
|
||||
radioExternalPaTxEnable(); // bias an external PA (if any) before we transmit
|
||||
RadioLibInterface::configHardwareForSend();
|
||||
}
|
||||
|
||||
@@ -250,6 +257,8 @@ template <typename T> void LR11x0Interface<T>::startReceive()
|
||||
|
||||
setStandby();
|
||||
|
||||
radioExternalPaRxIdle(); // drop external PA bias while receiving/idle
|
||||
|
||||
lora.setPreambleLength(preambleLength); // Solve RX ack fail after direct message sent. Not sure why this is needed.
|
||||
|
||||
// We use a 16 bit preamble so this should save some power by letting radio sit in standby mostly.
|
||||
@@ -337,6 +346,8 @@ template <typename T> bool LR11x0Interface<T>::sleep()
|
||||
LOG_DEBUG("LR11x0 entering sleep mode");
|
||||
setStandby(); // Stop any pending operations
|
||||
|
||||
radioExternalPaSleep(); // power down an external PA (if any)
|
||||
|
||||
// turn off TCXO if it was powered
|
||||
lora.setTCXO(0);
|
||||
|
||||
|
||||
@@ -154,10 +154,7 @@ bool RF95Interface::init()
|
||||
digitalWrite(RF95_TXEN, 0);
|
||||
#endif
|
||||
|
||||
#ifdef RF95_FAN_EN
|
||||
pinMode(RF95_FAN_EN, OUTPUT);
|
||||
digitalWrite(RF95_FAN_EN, 1);
|
||||
#endif
|
||||
enableFan();
|
||||
|
||||
#ifdef RF95_RXEN
|
||||
pinMode(RF95_RXEN, OUTPUT);
|
||||
@@ -335,10 +332,7 @@ bool RF95Interface::sleep()
|
||||
// put chipset into sleep mode
|
||||
setStandby(); // First cancel any active receiving/sending
|
||||
lora->sleep();
|
||||
|
||||
#ifdef RF95_FAN_EN
|
||||
digitalWrite(RF95_FAN_EN, 0);
|
||||
#endif
|
||||
disableFan();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
#include "RadioExternalPa.h"
|
||||
|
||||
// Weak default implementations: no external PA. A board with an external PA
|
||||
// provides strong overrides (see e.g.
|
||||
// src/platform/extra_variants/radiomaster_nomad_gemini/variant.cpp).
|
||||
|
||||
int8_t __attribute__((weak)) radioExternalPaMapPower(int8_t requestedTotalDbm, float freqHz)
|
||||
{
|
||||
(void)freqHz;
|
||||
(void)requestedTotalDbm;
|
||||
return RADIO_EXTERNAL_PA_NO_MAP;
|
||||
}
|
||||
|
||||
void __attribute__((weak)) radioExternalPaTxEnable() {}
|
||||
|
||||
void __attribute__((weak)) radioExternalPaRxIdle() {}
|
||||
|
||||
void __attribute__((weak)) radioExternalPaSleep() {}
|
||||
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* Generic hooks for boards with an external power amplifier whose gain/bias is
|
||||
* controlled outside the LoRa transceiver (e.g. an analog PA biased through a DAC
|
||||
* pin, as on the RadioMaster Nomad Gemini).
|
||||
*
|
||||
* All four functions have weak no-op / pass-through defaults (see RadioExternalPa.cpp),
|
||||
* so boards without an external PA are completely unaffected. A board provides an
|
||||
* external PA by giving STRONG overrides (typically in its
|
||||
* src/platform/extra_variants/<board>/variant.cpp).
|
||||
*
|
||||
* Relationship to the existing TX_GAIN_LORA / LoRaFEMInterface mechanisms:
|
||||
* - TX_GAIN_LORA assumes the chip output index is a non-negative dBm value.
|
||||
* - LoRaFEMInterface models *digital* front-end modules (discrete enable pins).
|
||||
* An analog PA that needs the transceiver driven at negative dBm fits neither, so it
|
||||
* is handled here instead.
|
||||
*/
|
||||
|
||||
/// Sentinel returned by radioExternalPaMapPower() when the board has no external PA.
|
||||
#define RADIO_EXTERNAL_PA_NO_MAP INT8_MIN
|
||||
|
||||
/**
|
||||
* Map a desired *total* radiated output power to the transceiver chip output power.
|
||||
*
|
||||
* Called from RadioInterface::limitPower() AFTER regional/regulatory clamping, so
|
||||
* @p requestedTotalDbm is already the legal total output we want out of the antenna.
|
||||
* The override returns the chip output power (dBm, may be negative) that, combined
|
||||
* with the external PA gain, yields that total, and configures/stashes the PA bias.
|
||||
*
|
||||
* @param requestedTotalDbm desired total output power in dBm (already region-limited)
|
||||
* @param freqHz operating frequency in Hz (for band-dependent PAs)
|
||||
* @return chip output power in dBm, or RADIO_EXTERNAL_PA_NO_MAP if no external PA
|
||||
*/
|
||||
int8_t radioExternalPaMapPower(int8_t requestedTotalDbm, float freqHz);
|
||||
|
||||
/// Engage the external PA bias for transmit (called just before a transmission).
|
||||
void radioExternalPaTxEnable();
|
||||
|
||||
/// Drop the external PA bias for receive/idle (called when entering RX/standby).
|
||||
void radioExternalPaRxIdle();
|
||||
|
||||
/// Power the external PA fully down (called when the radio goes to sleep).
|
||||
void radioExternalPaSleep();
|
||||
+35
-22
@@ -9,6 +9,7 @@
|
||||
#include "MeshService.h"
|
||||
#include "NodeDB.h"
|
||||
#include "RF95Interface.h"
|
||||
#include "RadioExternalPa.h"
|
||||
#include "Router.h"
|
||||
#include "SX1262Interface.h"
|
||||
#include "SX1268Interface.h"
|
||||
@@ -917,38 +918,50 @@ void RadioInterface::limitPower(int8_t loraMaxPower)
|
||||
power = maxPower;
|
||||
}
|
||||
|
||||
// Boards with an external PA controlled outside the transceiver (e.g. an analog PA
|
||||
// biased through a DAC pin) map the desired total output to a possibly-negative chip
|
||||
// output power here. This is applied even for licensed operators, because it reflects
|
||||
// a hardware gain stage, not a regulatory limit (the regulatory clamp above already
|
||||
// honors the license). Pass-through by default; see RadioExternalPa.h.
|
||||
int8_t externalPaChip = radioExternalPaMapPower(power, getFreq());
|
||||
if (externalPaChip != RADIO_EXTERNAL_PA_NO_MAP) {
|
||||
LOG_INFO("External PA: %d dBm total -> chip %d dBm", power, externalPaChip);
|
||||
power = externalPaChip;
|
||||
} else {
|
||||
#if HAS_LORA_FEM
|
||||
if (!devicestate.owner.is_licensed) {
|
||||
power = loraFEMInterface.powerConversion(power);
|
||||
}
|
||||
if (!devicestate.owner.is_licensed) {
|
||||
power = loraFEMInterface.powerConversion(power);
|
||||
}
|
||||
#else
|
||||
// todo:All entries containing "lora fem" are grouped together above.
|
||||
#ifdef ARCH_PORTDUINO
|
||||
size_t num_pa_points = portduino_config.num_pa_points;
|
||||
const uint16_t *tx_gain = portduino_config.tx_gain_lora;
|
||||
size_t num_pa_points = portduino_config.num_pa_points;
|
||||
const uint16_t *tx_gain = portduino_config.tx_gain_lora;
|
||||
#else
|
||||
size_t num_pa_points = NUM_PA_POINTS;
|
||||
const uint16_t tx_gain[NUM_PA_POINTS] = {TX_GAIN_LORA};
|
||||
size_t num_pa_points = NUM_PA_POINTS;
|
||||
const uint16_t tx_gain[NUM_PA_POINTS] = {TX_GAIN_LORA};
|
||||
#endif
|
||||
|
||||
if (num_pa_points == 1) {
|
||||
if (tx_gain[0] > 0 && !devicestate.owner.is_licensed) {
|
||||
LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", power, tx_gain[0]);
|
||||
power -= tx_gain[0];
|
||||
}
|
||||
} else if (!devicestate.owner.is_licensed) {
|
||||
// we have an array of PA gain values. Find the highest power setting that works.
|
||||
for (int radio_dbm = 0; radio_dbm < (int)num_pa_points; radio_dbm++) {
|
||||
if (((radio_dbm + tx_gain[radio_dbm]) > power) ||
|
||||
((radio_dbm == (int)(num_pa_points - 1)) && ((radio_dbm + tx_gain[radio_dbm]) <= power))) {
|
||||
// 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", power, tx_gain[radio_dbm]);
|
||||
power -= tx_gain[radio_dbm];
|
||||
break;
|
||||
if (num_pa_points == 1) {
|
||||
if (tx_gain[0] > 0 && !devicestate.owner.is_licensed) {
|
||||
LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", power, tx_gain[0]);
|
||||
power -= tx_gain[0];
|
||||
}
|
||||
} else if (!devicestate.owner.is_licensed) {
|
||||
// we have an array of PA gain values. Find the highest power setting that works.
|
||||
for (int radio_dbm = 0; radio_dbm < (int)num_pa_points; radio_dbm++) {
|
||||
if (((radio_dbm + tx_gain[radio_dbm]) > power) ||
|
||||
((radio_dbm == (int)(num_pa_points - 1)) && ((radio_dbm + tx_gain[radio_dbm]) <= power))) {
|
||||
// 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", power, tx_gain[radio_dbm]);
|
||||
power -= tx_gain[radio_dbm];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (power > loraMaxPower) // Clamp power to maximum defined level
|
||||
power = loraMaxPower;
|
||||
|
||||
|
||||
@@ -691,3 +691,36 @@ bool RadioLibInterface::startSend(meshtastic_MeshPacket *txp)
|
||||
return res == RADIOLIB_ERR_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
void RadioLibInterface::enableFan()
|
||||
{
|
||||
#ifdef RADIO_FAN_EN
|
||||
|
||||
#ifdef RADIO_FAN_PWM
|
||||
#if defined(ARCH_ESP32)
|
||||
ledcWrite(1, config.lora.pa_fan_disabled ? 0 : (pa_fan_percentage * 2.55));
|
||||
#elif defined(ARCH_NRF52)
|
||||
analogWrite(RADIO_FAN_EN, config.lora.pa_fan_disabled ? 0 : (pa_fan_percentage * 2.55));
|
||||
#endif
|
||||
#else
|
||||
pinMode(RADIO_FAN_EN, OUTPUT);
|
||||
digitalWrite(RADIO_FAN_EN, config.lora.pa_fan_disabled ? 0 : 1);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
void RadioLibInterface::disableFan()
|
||||
{
|
||||
#ifdef RADIO_FAN_EN
|
||||
#ifdef RADIO_FAN_PWM
|
||||
#if defined(ARCH_ESP32)
|
||||
ledcWrite(1, 0);
|
||||
#elif defined(ARCH_NRF52)
|
||||
analogWrite(RADIO_FAN_EN, 0);
|
||||
#endif
|
||||
#else
|
||||
pinMode(RADIO_FAN_EN, OUTPUT);
|
||||
digitalWrite(RADIO_FAN_EN, 0);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -203,6 +203,9 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
|
||||
/** Attempt to find a packet in the TxQueue. Returns true if the packet was found. */
|
||||
virtual bool findInTxQueue(NodeNum from, PacketId id) override;
|
||||
|
||||
void enableFan();
|
||||
void disableFan();
|
||||
|
||||
/**
|
||||
* Update the noise floor measurement by sampling RSSI from a slow path.
|
||||
* This should not be called from radio interrupt or TX/RX critical paths.
|
||||
|
||||
@@ -64,10 +64,7 @@ template <typename T> bool SX126xInterface<T>::init()
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef RF95_FAN_EN
|
||||
pinMode(RF95_FAN_EN, OUTPUT);
|
||||
digitalWrite(RF95_FAN_EN, HIGH);
|
||||
#endif
|
||||
enableFan();
|
||||
|
||||
#if ARCH_PORTDUINO
|
||||
tcxoVoltage = (float)portduino_config.dio3_tcxo_voltage / 1000;
|
||||
|
||||
@@ -35,10 +35,7 @@ template <typename T> bool SX128xInterface<T>::init()
|
||||
digitalWrite(SX128X_POWER_EN, HIGH);
|
||||
#endif
|
||||
|
||||
#ifdef RF95_FAN_EN
|
||||
pinMode(RF95_FAN_EN, OUTPUT);
|
||||
digitalWrite(RF95_FAN_EN, 1);
|
||||
#endif
|
||||
enableFan();
|
||||
|
||||
#if ARCH_PORTDUINO
|
||||
if (portduino_config.lora_rxen_pin.pin != RADIOLIB_NC) {
|
||||
|
||||
@@ -793,13 +793,21 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef RF95_FAN_EN
|
||||
#ifdef RADIO_FAN_EN
|
||||
#ifdef RADIO_FAN_PWM
|
||||
#if defined(ARCH_ESP32)
|
||||
ledcWrite(1, c.payload_variant.lora.pa_fan_disabled ? 0 : (pa_fan_percentage * 2.55));
|
||||
#elif defined(ARCH_NRF52)
|
||||
analogWrite(RADIO_FAN_EN, c.payload_variant.lora.pa_fan_disabled ? 0 : (pa_fan_percentage * 2.55));
|
||||
#endif
|
||||
#else
|
||||
// Turn PA off if disabled by config
|
||||
if (c.payload_variant.lora.pa_fan_disabled) {
|
||||
digitalWrite(RF95_FAN_EN, LOW ^ 0);
|
||||
digitalWrite(RADIO_FAN_EN, LOW ^ 0);
|
||||
} else {
|
||||
digitalWrite(RF95_FAN_EN, HIGH ^ 0);
|
||||
digitalWrite(RADIO_FAN_EN, HIGH ^ 0);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
config.lora = validatedLora;
|
||||
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
#include "configuration.h"
|
||||
|
||||
#ifdef RADIOMASTER_NOMAD_GEMINI
|
||||
|
||||
#include "RadioExternalPa.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
/**
|
||||
* RadioMaster Nomad Gemini external analog PA driver.
|
||||
*
|
||||
* The Nomad's PA is biased by an analog control voltage on the APC2 pin
|
||||
* (RADIO_PA_APC2_PIN = GPIO26 = ESP32 DAC channel 2), driven with dacWrite()
|
||||
* (0-255 -> 0-3.3V). The DAC is essentially a fixed bias; the actual output power
|
||||
* is set by driving the LR1121 chip at a (negative) output power and letting the PA
|
||||
* add ~25-31 dB of gain.
|
||||
*
|
||||
* The calibration below is derived from the ExpressLRS hardware target
|
||||
* "TX/Radiomaster Nomad.json" (power_values = APC2 DAC codes, power_values2 = chip
|
||||
* output dBm). These values reproduce ELRS behaviour but have NOT been validated on
|
||||
* a bench against a power meter for Meshtastic — verify before trusting at 1 W, both
|
||||
* for PA safety and for regulatory compliance.
|
||||
*
|
||||
* TODO: dual-band (Gemini) operation uses ELRS power_values_dual and a second radio;
|
||||
* once JANUS_RADIO is implemented, branch on freqHz / active radio here.
|
||||
*/
|
||||
|
||||
// total dBm corresponds to the ELRS power presets 10/25/50/100/250/500/1000 mW.
|
||||
static const struct {
|
||||
int8_t totalDbm; // desired total radiated power
|
||||
int8_t chipDbm; // LR1121 output power register (ELRS power_values2)
|
||||
uint8_t apc2; // APC2 DAC code (ELRS power_values)
|
||||
} NOMAD_PA_CAL[] = {
|
||||
{10, -17, 120}, // 10 mW
|
||||
{14, -16, 120}, // 25 mW
|
||||
{17, -14, 120}, // 50 mW
|
||||
{20, -11, 120}, // 100 mW
|
||||
{24, -7, 120}, // 250 mW
|
||||
{27, -3, 120}, // 500 mW
|
||||
{30, 5, 95}, // 1000 mW (1 W)
|
||||
};
|
||||
static const size_t NOMAD_PA_CAL_N = sizeof(NOMAD_PA_CAL) / sizeof(NOMAD_PA_CAL[0]);
|
||||
|
||||
// APC2 bias chosen for the currently-configured power level; applied on TX, dropped
|
||||
// to 0 for RX/idle/sleep so the PA only draws current while transmitting.
|
||||
static uint8_t nomadApc2Level = 0;
|
||||
|
||||
// Runs at the start of setup(), before the LoRa radio is initialized.
|
||||
void earlyInitVariant()
|
||||
{
|
||||
dacWrite(RADIO_PA_APC2_PIN, 0); // bias the PA off until we actually transmit
|
||||
nomadApc2Level = 0;
|
||||
}
|
||||
|
||||
int8_t radioExternalPaMapPower(int8_t requestedTotalDbm, float freqHz)
|
||||
{
|
||||
(void)freqHz; // single-band for now (see dual-band TODO above)
|
||||
|
||||
if (requestedTotalDbm <= NOMAD_PA_CAL[0].totalDbm) {
|
||||
nomadApc2Level = NOMAD_PA_CAL[0].apc2;
|
||||
return NOMAD_PA_CAL[0].chipDbm;
|
||||
}
|
||||
const size_t last = NOMAD_PA_CAL_N - 1;
|
||||
if (requestedTotalDbm >= NOMAD_PA_CAL[last].totalDbm) {
|
||||
nomadApc2Level = NOMAD_PA_CAL[last].apc2;
|
||||
return NOMAD_PA_CAL[last].chipDbm;
|
||||
}
|
||||
|
||||
// Piecewise-linear interpolation of chip dBm between the bracketing points.
|
||||
for (size_t i = 1; i < NOMAD_PA_CAL_N; i++) {
|
||||
if (requestedTotalDbm <= NOMAD_PA_CAL[i].totalDbm) {
|
||||
const int t0 = NOMAD_PA_CAL[i - 1].totalDbm, t1 = NOMAD_PA_CAL[i].totalDbm;
|
||||
const int c0 = NOMAD_PA_CAL[i - 1].chipDbm, c1 = NOMAD_PA_CAL[i].chipDbm;
|
||||
const int chip = c0 + ((c1 - c0) * (requestedTotalDbm - t0) + (t1 - t0) / 2) / (t1 - t0); // round-to-nearest
|
||||
nomadApc2Level = NOMAD_PA_CAL[i - 1].apc2; // only the top point lowers the bias
|
||||
return (int8_t)chip;
|
||||
}
|
||||
}
|
||||
nomadApc2Level = NOMAD_PA_CAL[last].apc2; // unreachable
|
||||
return NOMAD_PA_CAL[last].chipDbm;
|
||||
}
|
||||
|
||||
void radioExternalPaTxEnable()
|
||||
{
|
||||
dacWrite(RADIO_PA_APC2_PIN, nomadApc2Level);
|
||||
}
|
||||
|
||||
void radioExternalPaRxIdle()
|
||||
{
|
||||
dacWrite(RADIO_PA_APC2_PIN, 0);
|
||||
}
|
||||
|
||||
void radioExternalPaSleep()
|
||||
{
|
||||
dacWrite(RADIO_PA_APC2_PIN, 0);
|
||||
}
|
||||
|
||||
#endif // RADIOMASTER_NOMAD_GEMINI
|
||||
@@ -12,7 +12,7 @@
|
||||
#define LORA_MISO 19
|
||||
#define LORA_MOSI 23
|
||||
#define LORA_CS 5
|
||||
#define RF95_FAN_EN 17
|
||||
#define RADIO_FAN_EN 17
|
||||
|
||||
// This is a LED_WS2812 not a standard LED
|
||||
#define HAS_NEOPIXEL // Enable the use of neopixels
|
||||
|
||||
@@ -46,7 +46,8 @@
|
||||
FAN is active at 250mW on it's ExpressLRS Firmware.
|
||||
This FAN has TACHO signal on Pin 27 for use with PWM.
|
||||
*/
|
||||
#define RF95_FAN_EN 2
|
||||
#define RADIO_FAN_EN 2
|
||||
#define RADIO_FAN_PWM
|
||||
|
||||
/*
|
||||
LED PIN setup and it has a NeoPixel LED.
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
This unit has a FAN built-in.
|
||||
FAN is active at 250mW on it's ExpressLRS Firmware.
|
||||
*/
|
||||
#define RF95_FAN_EN 2
|
||||
#define RADIO_FAN_EN 2
|
||||
|
||||
/*
|
||||
LED PIN setup.
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
[env:radiomaster_nomad_gemini]
|
||||
extends = esp32_base
|
||||
board = esp32doit-devkit-v1
|
||||
build_flags =
|
||||
${esp32_base.build_flags}
|
||||
-DRADIOMASTER_NOMAD_GEMINI
|
||||
-DPRIVATE_HW
|
||||
-DVTABLES_IN_FLASH=1
|
||||
-DCONFIG_DISABLE_HAL_LOCKS=1
|
||||
-O2
|
||||
-Ivariants/esp32/radiomaster_nomad_gemini
|
||||
-DMESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR=1
|
||||
-DMESHTASTIC_EXCLUDE_I2C=1
|
||||
-DRADIOLIB_EXCLUDE_SX128X=1
|
||||
-DRADIOLIB_EXCLUDE_SX127X=1
|
||||
-DRADIOLIB_EXCLUDE_SX126X=1
|
||||
board_build.f_cpu = 240000000L
|
||||
upload_protocol = esptool
|
||||
lib_deps =
|
||||
${esp32_base.lib_deps}
|
||||
@@ -0,0 +1,56 @@
|
||||
#include "RadioLib.h"
|
||||
|
||||
static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_LR11X0_DIO7,
|
||||
RADIOLIB_LR11X0_DIO8, RADIOLIB_NC};
|
||||
|
||||
static const Module::RfSwitchMode_t rfswitch_table[] = {
|
||||
// mode DIO5 DIO6 DIO7 DIO8
|
||||
{LR11x0::MODE_STBY, {LOW, LOW, LOW, LOW}}, {LR11x0::MODE_RX, {LOW, LOW, HIGH, LOW}},
|
||||
{LR11x0::MODE_TX, {LOW, LOW, LOW, HIGH}}, {LR11x0::MODE_TX_HP, {LOW, LOW, LOW, HIGH}},
|
||||
{LR11x0::MODE_TX_HF, {LOW, HIGH, LOW, LOW}}, {LR11x0::MODE_GNSS, {LOW, LOW, LOW, LOW}},
|
||||
{LR11x0::MODE_WIFI, {HIGH, LOW, LOW, LOW}}, END_OF_MODE_TABLE,
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
DIO5: RXEN 2.4GHz
|
||||
DIO6: TXEN 2.4GHz
|
||||
DIO7: RXEN 900MHz
|
||||
DIO8: TXEN 900MHz
|
||||
|
||||
|
||||
"radio_dcdc": true,
|
||||
"radio_rfo_hf": true,
|
||||
|
||||
"power_apc2": 26,
|
||||
"power_min": 0,
|
||||
"power_high": 6,
|
||||
"power_max": 6,
|
||||
"power_default": 3,
|
||||
"power_control": 3, POWER_OUTPUT_DACWRITE // use internal dacWrite function to set value on GPIO_PIN_RFamp_APC2
|
||||
[0, 1, 2, 3, 4, 5, 6 ] // 0-6
|
||||
"power_values": [120, 120, 120, 120, 120, 120, 95] // DAC Value
|
||||
"power_values2": [-17, -16, -14, -11, -7, -3, 5 ] // 900M
|
||||
"power_values_dual": [-18, -14, -8, -6, -2, 3, 5 ] // 2.4G
|
||||
|
||||
// default value 0 means direct!
|
||||
#define POWER_OUTPUT_DACWRITE (hardware_int(HARDWARE_power_control)==3)
|
||||
#define POWER_OUTPUT_VALUES hardware_i16_array(HARDWARE_power_values)
|
||||
#define POWER_OUTPUT_VALUES_COUNT hardware_int(HARDWARE_power_values_count)
|
||||
#define POWER_OUTPUT_VALUES2 hardware_i16_array(HARDWARE_power_values2)
|
||||
#define POWER_OUTPUT_VALUES_DUAL hardware_i16_array(HARDWARE_power_values_dual)
|
||||
#define POWER_OUTPUT_VALUES_DUAL_COUNT hardware_int(HARDWARE_power_values_dual_count)
|
||||
|
||||
#define GPIO_PIN_FAN_EN hardware_pin(HARDWARE_misc_fan_en)
|
||||
|
||||
case PWR_10mW: return 10;
|
||||
case PWR_25mW: return 14;
|
||||
case PWR_50mW: return 17;
|
||||
case PWR_100mW: return 20;
|
||||
case PWR_250mW: return 24;
|
||||
case PWR_500mW: return 27;
|
||||
case PWR_1000mW: return 30;
|
||||
|
||||
95 -> +25dBm
|
||||
120 -> +24dBm
|
||||
*/
|
||||
@@ -0,0 +1,68 @@
|
||||
#define HAS_SCREEN 0
|
||||
#define HAS_WIRE 0
|
||||
#define HAS_GPS 0
|
||||
#undef GPS_RX_PIN
|
||||
#undef GPS_TX_PIN
|
||||
|
||||
#define PIN_SPI_MISO 33
|
||||
#define PIN_SPI_MOSI 32
|
||||
#define PIN_SPI_SCK 25
|
||||
#define PIN_SPI_NSS 27
|
||||
|
||||
#define LORA_RESET 15
|
||||
#define LORA_DIO1 37
|
||||
#define LORA_DIO2 36
|
||||
#define LORA_SCK PIN_SPI_SCK
|
||||
#define LORA_MISO PIN_SPI_MISO
|
||||
#define LORA_MOSI PIN_SPI_MOSI
|
||||
#define LORA_CS PIN_SPI_NSS
|
||||
|
||||
// supported modules list
|
||||
#define USE_LR1121
|
||||
|
||||
#define LR1121_IRQ_PIN LORA_DIO1
|
||||
#define LR1121_NRESET_PIN LORA_RESET
|
||||
#define LR1121_BUSY_PIN LORA_DIO2
|
||||
#define LR1121_SPI_NSS_PIN LORA_CS
|
||||
#define LR1121_SPI_SCK_PIN LORA_SCK
|
||||
#define LR1121_SPI_MOSI_PIN LORA_MOSI
|
||||
#define LR1121_SPI_MISO_PIN LORA_MISO
|
||||
|
||||
// Caps for the LR1121 *chip* output. The external analog PA (driven via the APC2
|
||||
// DAC pin, see RADIO_PA_APC2_PIN below) provides the rest of the gain, so the chip
|
||||
// itself is driven low. See src/platform/extra_variants/radiomaster_nomad_gemini.
|
||||
#define LR1110_MAX_POWER 5
|
||||
// 2.4G Part
|
||||
#define LR1120_MAX_POWER 5
|
||||
|
||||
// not yet implemented
|
||||
#define JANUS_RADIO
|
||||
#define LR1121_IRQ2_PIN 34
|
||||
#define LR1121_NRESET2_PIN 21
|
||||
#define LR1121_BUSY2_PIN 39
|
||||
#define LR1121_SPI_NSS2_PIN 13
|
||||
|
||||
// TODO: check if this is correct
|
||||
// #define LR11X0_DIO3_TCXO_VOLTAGE 1.6
|
||||
#define LR11X0_DIO_AS_RF_SWITCH
|
||||
|
||||
#define HAS_NEOPIXEL // Enable the use of neopixels
|
||||
#define NEOPIXEL_COUNT 2 // How many neopixels are connected
|
||||
#define NEOPIXEL_DATA 22 // GPIO pin used to send data to the neopixels
|
||||
#define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) // Type of neopixels in use
|
||||
#define ENABLE_AMBIENTLIGHTING // Turn on Ambient Lighting
|
||||
|
||||
// Primary button is GPIO14 (ELRS "button"); GPIO12 is the second button ("button2").
|
||||
// NOTE: GPIO34 is the second radio's DIO1 (LR1121_IRQ2_PIN) and is input-only with no
|
||||
// internal pull resistor, so it must NOT be reused as a button.
|
||||
#define BUTTON_PIN 14
|
||||
#define BUTTON_NEED_PULLUP
|
||||
|
||||
#undef EXT_NOTIFY_OUT
|
||||
|
||||
#define RADIO_FAN_EN 2
|
||||
|
||||
// Analog PA bias control (ELRS APC2). GPIO26 = ESP32 DAC channel 2, driven with
|
||||
// dacWrite() (0-255 -> 0-3.3V). Defining this enables the external-PA driver in
|
||||
// src/platform/extra_variants/radiomaster_nomad_gemini/variant.cpp.
|
||||
#define RADIO_PA_APC2_PIN 26
|
||||
@@ -84,7 +84,7 @@
|
||||
// Fan control
|
||||
#define FAN_CTRL_PIN 41
|
||||
// Meshtastic standard fan control pin macro
|
||||
#define RF95_FAN_EN FAN_CTRL_PIN
|
||||
#define RADIO_FAN_EN FAN_CTRL_PIN
|
||||
|
||||
// PA Ramp Time - T-Beam 1W requires >800us stabilization (default is 200us)
|
||||
// Value 0x05 = RADIOLIB_SX126X_PA_RAMP_800U
|
||||
|
||||
Reference in New Issue
Block a user