Compare commits

..

19 Commits

Author SHA1 Message Date
af8647d239 Added MeshPoE-S3 board 2026-04-26 10:30:50 +03:00
91df82b6cf Added LilyGo T-Beam Supreme V3 2026-04-26 10:30:50 +03:00
d7594dfa03 Increase OCP limit 2026-04-26 10:30:47 +03:00
d925afc2f8 Added Heltec V4.3 support/FEM autodetection 2026-04-26 10:29:28 +03:00
e1029912e6 Added TXEN pin handling for sx126x 2026-04-26 10:11:11 +03:00
d31c6f8168 fallback tx_power increased from 17 to 20 2026-04-26 10:11:11 +03:00
7ae7aa659d Updated README.md 2026-04-26 10:11:00 +03:00
94c33be6b4 Updated README.md 2026-04-26 10:06:51 +03:00
c080295b49 PMU edits 2026-04-26 10:05:26 +03:00
46c04649ca Added MeshAdventurer-S3 and DIY-V1 variants 2026-04-26 10:05:26 +03:00
7f5b20d028 Initial commit 2026-04-26 10:05:21 +03:00
Mark Qvist
d39339f8ec Updated readme 2026-04-24 12:26:35 +02:00
Mark Qvist
9b39b6ce59 Prepare release 2026-04-24 10:42:13 +02:00
Mark Qvist
3167a3e679 Cleanup 2026-04-21 00:33:53 +02:00
Mark Qvist
fe594b2048 Turn off display on T114 2026-04-21 00:33:18 +02:00
Mark Qvist
9fd0ae33d2 Added support for Heltec v4.3 PA and LNA 2026-04-20 17:16:39 +02:00
Mark Qvist
0c07c1b856 Updated version 2026-04-19 15:10:06 +02:00
markqvist
ae0434726b Merge pull request #132 from GlassOnTin/sx1262-errata-fixes
Fix SX1262 errata 15.4 (IQ polarity) and 15.1 (modulation quality)
2026-04-19 14:50:16 +02:00
GlassOnTin
5d2f0b93e3 Fix SX1262 errata 15.4 (IQ polarity) and 15.1 (modulation quality)
Errata 15.4: SetPacketParams resets register 0x0736 to an incorrect
default for IQ polarity. For standard IQ (no inversion), bit 2 must
be SET after every SetPacketParams call. Without this, LoRa RX
demodulation can fail silently while TX continues to work — the
symptom mimics a hardware failure.

Errata 15.1: Register 0x0889 bit 2 controls modulation quality at
different bandwidths. It must be cleared for 500 kHz BW and set for
all others. The previous implementation was a no-op stub.

Both fixes are from the SX1262 datasheet errata (DS_SX1261-2_V2.1)
and apply to all SX1262-based boards.
2026-03-29 17:53:28 +01:00
9 changed files with 184 additions and 73 deletions

View File

@@ -158,6 +158,10 @@
#endif #endif
#endif #endif
#define LORA_PA_UNKNOWN 0x00
#define LORA_PA_GC1109 0x01
#define LORA_PA_KCT8103L 0x02
#define HAS_DISPLAY false #define HAS_DISPLAY false
#define HAS_BLUETOOTH false #define HAS_BLUETOOTH false
#define HAS_BLE false #define HAS_BLE false
@@ -453,7 +457,7 @@
#define HAS_TCXO true #define HAS_TCXO true
#define HAS_BUSY true #define HAS_BUSY true
#define DIO2_AS_RF_SWITCH true #define DIO2_AS_RF_SWITCH true
#define OCP_TUNED 0x18 #define OCP_TUNED 0x28
const int pin_busy = 32; const int pin_busy = 32;
const int pin_dio = 33; const int pin_dio = 33;
const int pin_tcxo_enable = -1; const int pin_tcxo_enable = -1;
@@ -559,7 +563,7 @@
#define HAS_SLEEP true #define HAS_SLEEP true
#define PIN_WAKEUP GPIO_NUM_0 #define PIN_WAKEUP GPIO_NUM_0
#define WAKEUP_LEVEL 0 #define WAKEUP_LEVEL 0
#define OCP_TUNED 0x18 #define OCP_TUNED 0x28
const int pin_btn_usr1 = 0; const int pin_btn_usr1 = 0;
@@ -601,8 +605,9 @@
#define HAS_LORA_LNA true #define HAS_LORA_LNA true
#define PIN_WAKEUP GPIO_NUM_0 #define PIN_WAKEUP GPIO_NUM_0
#define WAKEUP_LEVEL 0 #define WAKEUP_LEVEL 0
#define OCP_TUNED 0x38 #define OCP_TUNED 0x28
#define Vext GPIO_NUM_36 #define Vext GPIO_NUM_36
#define LORA_PA_MODEL LORA_PA_UNKNOWN;
const int pin_btn_usr1 = 0; const int pin_btn_usr1 = 0;
@@ -624,19 +629,17 @@
#define LORA_LNA_GAIN 17 #define LORA_LNA_GAIN 17
#define LORA_LNA_GVT 12 #define LORA_LNA_GVT 12
#define LORA_PA_GC1109 true
#define LORA_PA_AUTO_DETECT true
#define LORA_PA_PWR_EN 7 #define LORA_PA_PWR_EN 7
#define LORA_PA_CSD 2 // Same pin on GC1109
#define LORA_PA_CPS 46 // Same pin on GC1109
#define LORA_PA_CTX 5 // Only used on KCT8103
#define LORA_PA_CSD 2 // GC1109: PA_EN | KCT8103L: CSD (same pin, different pull resistor) #define PA_MAX_OUTPUT 28
#define LORA_PA_CTX 5 // KCT8103L: TX/LNA select (CTX=LOW=LNA, CTX=HIGH=PA) #define PA_GAIN_POINTS 22
#define LORA_PA_CPS 46 // GC1109: TX_EN
#define PA_MAX_OUTPUT 28 #define LORA_LNA_KCT8103L_GAIN 21
#define PA_GAIN_POINTS 22 const int PA_GC1109_VALUES[PA_GAIN_POINTS] = {11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 9, 8, 7};
#define PA_GC1109_GAIN_VALUES 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 9, 8, 7 const int PA_KCT8103L_VALUES[PA_GAIN_POINTS] = {13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 12, 12, 11, 11, 10, 9, 8, 7};
#define PA_KCT8103L_GAIN_VALUES 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 12, 12, 11, 11, 10, 9, 8, 7
#define PA_GAIN_VALUES PA_KCT8103L_GAIN_VALUES // compile-time fallback; runtime detection selects correct table
const int pin_cs = 8; const int pin_cs = 8;
const int pin_busy = 13; const int pin_busy = 13;
@@ -825,7 +828,7 @@
#define DIO2_AS_RF_SWITCH true #define DIO2_AS_RF_SWITCH true
#define HAS_BUSY true #define HAS_BUSY true
#define HAS_TCXO true #define HAS_TCXO true
#define OCP_TUNED 0x18 #define OCP_TUNED 0x28
#define HAS_DISPLAY true #define HAS_DISPLAY true
#define HAS_CONSOLE true #define HAS_CONSOLE true
@@ -1237,7 +1240,7 @@
// Default OCP value if not specified // Default OCP value if not specified
// in board configuration // in board configuration
#ifndef OCP_TUNED #ifndef OCP_TUNED
#define OCP_TUNED 0x38 #define OCP_TUNED 0x28
#endif #endif
#ifndef NP_M #ifndef NP_M

View File

@@ -20,7 +20,7 @@
#define CONFIG_H #define CONFIG_H
#define MAJ_VERS 0x01 #define MAJ_VERS 0x01
#define MIN_VERS 0x55 #define MIN_VERS 0x56
#define MODE_HOST 0x11 #define MODE_HOST 0x11
#define MODE_TNC 0x12 #define MODE_TNC 0x12

View File

@@ -4,10 +4,10 @@ import sys
import shutil import shutil
packages = { packages = {
"rns": "rns-1.0.3-py3-none-any.whl", "rns": "rns-1.1.9-py3-none-any.whl",
"nomadnet": "nomadnet-0.9.1-py3-none-any.whl", "nomadnet": "nomadnet-0.9.11-py3-none-any.whl",
"lxmf": "lxmf-0.9.3-py3-none-any.whl", "lxmf": "lxmf-0.9.6-py3-none-any.whl",
"rnsh": "rnsh-0.1.5-py3-none-any.whl", "rnsh": "rnsh-0.1.9-py3-none-any.whl",
} }
DEFAULT_TITLE = "RNode Bootstrap Console" DEFAULT_TITLE = "RNode Bootstrap Console"
@@ -194,8 +194,29 @@ def optimise_manual(path):
("_images/meshchat_1.webp", pm), ("_images/meshchat_1.webp", pm),
("_images/radio_is5ac.png", pm), ("_images/radio_is5ac.png", pm),
("_images/radio_rblhg5.png", pm), ("_images/radio_rblhg5.png", pm),
("_images/rbrowser.webp", pm),
("_images/rnphone.webp", pm),
("_images/retibbs.webp", pm),
("_images/meshchatx.webp", pm),
("_images/lxst_phone.webp", pm),
("_images/columba.webp", pm),
("_static/rns_logo_512.png", 256), ("_static/rns_logo_512.png", 256),
("../images/bg_h_1.webp", pm), ("../images/bg_h_1.webp", pm),
("../../images/3_conv.webp", pm/2),
("../../images/an1.webp", pm/2),
("../../images/bg1ds1.webp", pm/2),
("../../images/bg1ds2.webp", pm/2),
("../../images/bg_h_1.webp", pm/2),
("../../images/bg_h_2.webp", pm/2),
("../../images/g1p.webp", pm/2),
("../../images/g2p.webp", pm/2),
("../../images/g3p.webp", pm/2),
("../../images/g4p.webp", pm/2),
("../../images/lora_rnodes.webp", pm/2),
("../../images/nn_an.webp", pm/2),
("../../images/nn_conv.webp", pm/2),
("../../images/nn_init.webp", pm/2),
] ]
import subprocess import subprocess

View File

@@ -1154,6 +1154,7 @@ void update_display(bool blank = false) {
#if BOARD_MODEL == BOARD_HELTEC_T114 #if BOARD_MODEL == BOARD_HELTEC_T114
display.clear(); display.clear();
digitalWrite(PIN_T114_TFT_BLGT, LOW);
#elif BOARD_MODEL != BOARD_TDECK && BOARD_MODEL != BOARD_TECHO #elif BOARD_MODEL != BOARD_TDECK && BOARD_MODEL != BOARD_TECHO
display.clearDisplay(); display.clearDisplay();
#endif #endif

View File

@@ -30,7 +30,6 @@ prep: prep-avr prep-esp32 prep-samd
prep-avr: prep-avr:
arduino-cli core update-index --config-file arduino-cli.yaml arduino-cli core update-index --config-file arduino-cli.yaml
arduino-cli core install arduino:avr --config-file arduino-cli.yaml arduino-cli core install arduino:avr --config-file arduino-cli.yaml
arduino-cli core install unsignedio:avr --config-file arduino-cli.yaml
prep-esp32: prep-esp32:
arduino-cli core update-index --config-file arduino-cli.yaml arduino-cli core update-index --config-file arduino-cli.yaml

Binary file not shown.

View File

@@ -1336,17 +1336,33 @@ int getTxPower() {
} }
#if HAS_LORA_PA #if HAS_LORA_PA
#if LORA_PA_AUTO_DETECT #if BOARD_MODEL == BOARD_HELTEC32_V4
static const int gc1109_tx_gain[PA_GAIN_POINTS] = {PA_GC1109_GAIN_VALUES}; bool pa_values_determined = false;
static const int kct8103l_tx_gain[PA_GAIN_POINTS] = {PA_KCT8103L_GAIN_VALUES}; int tx_gain[PA_GAIN_POINTS] = {100};
const int* tx_gain = sx126x_modem.isKCT8103L() ? kct8103l_tx_gain : gc1109_tx_gain; #else
#else bool pa_values_determined = true;
const int tx_gain[PA_GAIN_POINTS] = {PA_GAIN_VALUES}; const int tx_gain[PA_GAIN_POINTS] = {PA_GAIN_VALUES};
#endif #endif
#endif #endif
extern uint8_t lora_pa_model;
void determine_pa_values() {
#if BOARD_MODEL == BOARD_HELTEC32_V4
if (lora_pa_model == LORA_PA_GC1109) {
for (int i=0; i < PA_GAIN_POINTS; i++) { tx_gain[i] = PA_GC1109_VALUES[i]; }
pa_values_determined = true;
for (int i=0; i < PA_GAIN_POINTS; i++) { Serial.print(" "); Serial.printf("%d", tx_gain[i]); }
} else if (lora_pa_model == LORA_PA_KCT8103L) {
for (int i=0; i < PA_GAIN_POINTS; i++) { tx_gain[i] = PA_KCT8103L_VALUES[i]; }
pa_values_determined = true;
for (int i=0; i < PA_GAIN_POINTS; i++) { Serial.print(" "); Serial.printf("%d", tx_gain[i]); }
}
#endif
}
int map_target_power_to_modem_output(int target_tx_power) { int map_target_power_to_modem_output(int target_tx_power) {
#if HAS_LORA_PA #if HAS_LORA_PA
if (!pa_values_determined) { determine_pa_values(); }
int modem_output_dbm = -9; int modem_output_dbm = -9;
for (int i = 0; i < PA_GAIN_POINTS; i++) { for (int i = 0; i < PA_GAIN_POINTS; i++) {
int gain = tx_gain[i]; int gain = tx_gain[i];

View File

@@ -4,7 +4,3 @@ board_manager:
- https://raw.githubusercontent.com/RAKwireless/RAKwireless-Arduino-BSP-Index/main/package_rakwireless_index.json - https://raw.githubusercontent.com/RAKwireless/RAKwireless-Arduino-BSP-Index/main/package_rakwireless_index.json
- https://github.com/HelTecAutomation/Heltec_nRF52/releases/download/1.7.0/package_heltec_nrf_index.json - https://github.com/HelTecAutomation/Heltec_nRF52/releases/download/1.7.0/package_heltec_nrf_index.json
- https://adafruit.github.io/arduino-board-index/package_adafruit_index.json - https://adafruit.github.io/arduino-board-index/package_adafruit_index.json
## proMicro
# - https://raw.githubusercontent.com/pdcook/nRFMicro-Arduino-Core/main/package_nRFMicro_index.json #can't use it bc the name has spaces, i created the file below
- https://gist.githubusercontent.com/gargomoma/b5cffc40e5df88462f2b488492feb6ca/raw/198c5706289014dd64be31607495e02f056d52a3/nrf_cli_fix.json
- https://files.seeedstudio.com/arduino/package_seeeduino_boards_index.json

View File

@@ -96,6 +96,14 @@
#define SPI spiModem #define SPI spiModem
#endif #endif
#if HAS_LORA_PA
uint8_t lora_pa_model = LORA_PA_MODEL;
#endif
#if HAS_LORA_LNA
int lora_lna_gain = LORA_LNA_GAIN;
#endif
extern SPIClass SPI; extern SPIClass SPI;
#define MAX_PKT_LENGTH 255 #define MAX_PKT_LENGTH 255
@@ -274,6 +282,21 @@ void sx126x::setPacketParams(long preamble_symbols, uint8_t headermode, uint8_t
buf[7] = 0x00; buf[7] = 0x00;
buf[8] = 0x00; buf[8] = 0x00;
executeOpcode(OP_PACKET_PARAMS_6X, buf, 9); executeOpcode(OP_PACKET_PARAMS_6X, buf, 9);
// SX1262 errata section 15.4: IQ polarity is inverted compared to
// SX1276. The SetPacketParams command resets register 0x0736 to an
// incorrect default. For standard IQ (no inversion), bit 2 must be
// SET after every SetPacketParams call. For inverted IQ, bit 2 must
// be CLEARED. Without this fix, LoRa RX demodulation fails silently
// while TX continues to work.
uint8_t iqreg = readRegister(0x0736);
if (buf[5] == 0x00) {
// Standard IQ: set bit 2
writeRegister(0x0736, iqreg | 0x04);
} else {
// Inverted IQ: clear bit 2
writeRegister(0x0736, iqreg & ~0x04);
}
} }
void sx126x::reset(void) { void sx126x::reset(void) {
@@ -373,30 +396,66 @@ int sx126x::begin(long frequency) {
setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode);
#if HAS_LORA_PA #if HAS_LORA_PA
#if LORA_PA_AUTO_DETECT if (lora_pa_model == LORA_PA_UNKNOWN) {
// Power up the FEM, then read GPIO LORA_PA_CSD as input. #if BOARD_MODEL == BOARD_HELTEC32_V4
// The V4.2 (GC1109) board pulls it LOW; V4.3 (KCT8103L) pulls it HIGH.
pinMode(LORA_PA_PWR_EN, OUTPUT);
pinMode(LORA_PA_CSD, INPUT);
digitalWrite(LORA_PA_PWR_EN, HIGH); delay(5);
if (digitalRead(LORA_PA_CSD) == HIGH) {
lora_pa_model = LORA_PA_KCT8103L;
lora_lna_gain = LORA_LNA_KCT8103L_GAIN;
} else {
lora_pa_model = LORA_PA_GC1109;
}
#endif
}
if (lora_pa_model == LORA_PA_GC1109) {
// Enable Vfem_ctl for supply to
// PA power net.
pinMode(LORA_PA_PWR_EN, OUTPUT); pinMode(LORA_PA_PWR_EN, OUTPUT);
digitalWrite(LORA_PA_PWR_EN, HIGH); digitalWrite(LORA_PA_PWR_EN, HIGH);
delay(1);
pinMode(LORA_PA_CSD, INPUT);
delay(1);
_kct8103l = (digitalRead(LORA_PA_CSD) == HIGH);
if (_kct8103l) { // Enable PA LNA and TX standby
// KCT8103L (V4.3): CSD=HIGH enables chip, CTX=LOW=LNA/RX, CTX=HIGH=PA/TX pinMode(LORA_PA_CSD, OUTPUT);
pinMode(LORA_PA_CSD, OUTPUT); digitalWrite(LORA_PA_CSD, HIGH);
digitalWrite(LORA_PA_CSD, HIGH);
pinMode(LORA_PA_CTX, OUTPUT); // Keep PA CPS low until actual
digitalWrite(LORA_PA_CTX, LOW); // LNA enabled by default // transmit. Does it save power?
} else { // Who knows? Will have to measure.
// GC1109 (V4.2): PA_EN=HIGH enables chip, CPS=HIGH=full PA mode // Note from the future: Nope.
pinMode(LORA_PA_CSD, OUTPUT); // Power consumption is the same,
digitalWrite(LORA_PA_CSD, HIGH); // and turning it on and off is
pinMode(LORA_PA_CPS, OUTPUT); // not something that it likes.
digitalWrite(LORA_PA_CPS, HIGH); // Keeping it high for now.
} pinMode(LORA_PA_CPS, OUTPUT);
#endif digitalWrite(LORA_PA_CPS, HIGH);
// On Heltec V4, the PA CTX pin
// is driven by the SX1262 DIO2
// pin directly, so we do not
// need to manually raise this.
} else if (lora_pa_model == LORA_PA_KCT8103L) {
// Enable Vfem_ctl for supply to
// PA power net.
pinMode(LORA_PA_PWR_EN, OUTPUT);
digitalWrite(LORA_PA_PWR_EN, HIGH);
// Enable KCT8103L chip
pinMode(LORA_PA_CSD, OUTPUT);
digitalWrite(LORA_PA_CSD, HIGH);
// Enable receive LNA
pinMode(LORA_PA_CTX, OUTPUT);
digitalWrite(LORA_PA_CTX, LOW);
// On Heltec V4.3, the PA CPS pin
// is driven by the SX1262 DIO2
// pin directly, so we do not
// need to manually raise this.
}
#endif #endif
return 1; return 1;
@@ -406,13 +465,15 @@ void sx126x::end() { sleep(); SPI.end(); _preinit_done = false; }
int sx126x::beginPacket(int implicitHeader) { int sx126x::beginPacket(int implicitHeader) {
#if HAS_LORA_PA #if HAS_LORA_PA
#if LORA_PA_AUTO_DETECT if (lora_pa_model == LORA_PA_GC1109) {
if (_kct8103l) { // Enable PA CPS for transmit
// CTX=HIGH: switch KCT8103L to PA/TX mode. // digitalWrite(LORA_PA_CPS, HIGH);
digitalWrite(LORA_PA_CTX, HIGH); // Disabled since we're keeping it
} // on permanently as long as the
// GC1109: CPS kept HIGH permanently, no action needed. // radio is powered up.
#endif } else if (lora_pa_model == LORA_PA_KCT8103L) {
digitalWrite(LORA_PA_CTX, HIGH);
}
#endif #endif
if (_txen != -1) { digitalWrite(_txen, HIGH); } //Set TXen high when transmitting if (_txen != -1) { digitalWrite(_txen, HIGH); } //Set TXen high when transmitting
@@ -505,7 +566,7 @@ int ISR_VECT sx126x::currentRssi() {
executeOpcodeRead(OP_CURRENT_RSSI_6X, &byte, 1); executeOpcodeRead(OP_CURRENT_RSSI_6X, &byte, 1);
int rssi = -(int(byte)) / 2; int rssi = -(int(byte)) / 2;
#if HAS_LORA_LNA #if HAS_LORA_LNA
rssi -= LORA_LNA_GAIN; rssi -= lora_lna_gain;
#endif #endif
return rssi; return rssi;
} }
@@ -521,7 +582,7 @@ int ISR_VECT sx126x::packetRssi() {
executeOpcodeRead(OP_PACKET_STATUS_6X, buf, 3); executeOpcodeRead(OP_PACKET_STATUS_6X, buf, 3);
int pkt_rssi = -buf[0] / 2; int pkt_rssi = -buf[0] / 2;
#if HAS_LORA_LNA #if HAS_LORA_LNA
pkt_rssi -= LORA_LNA_GAIN; pkt_rssi -= lora_lna_gain;
#endif #endif
return pkt_rssi; return pkt_rssi;
} }
@@ -628,13 +689,17 @@ void sx126x::onReceive(void(*callback)(int)){
void sx126x::receive(int size) { void sx126x::receive(int size) {
#if HAS_LORA_PA #if HAS_LORA_PA
#if LORA_PA_AUTO_DETECT if (lora_pa_model == LORA_PA_GC1109) {
if (_kct8103l) { // Disable PA CPS for receive
// CTX=LOW: switch KCT8103L to LNA/RX mode. // digitalWrite(LORA_PA_CPS, LOW);
digitalWrite(LORA_PA_CTX, LOW); // That turned out to be a bad idea.
} // The LNA goes wonky if it's toggled
// GC1109: CPS kept HIGH permanently, no action needed. // on and off too quickly. We'll keep
#endif // it on permanently, as long as the
// radio is powered up.
} else if (lora_pa_model == LORA_PA_KCT8103L) {
digitalWrite(LORA_PA_CTX, LOW);
}
#endif #endif
if (size > 0) { if (size > 0) {
@@ -776,7 +841,17 @@ void sx126x::handleLowDataRate() {
} }
// TODO: Check if there's anything the sx1262 can do here // TODO: Check if there's anything the sx1262 can do here
void sx126x::optimizeModemSensitivity(){ } // SX1262 errata section 15.1: Modulation quality with 500 kHz LoRa BW.
// Register 0x0889 bit 2 must be cleared for 500 kHz, set for all other
// bandwidths. Improves receiver sensitivity at non-500 kHz bandwidths.
void sx126x::optimizeModemSensitivity(){
uint8_t reg = readRegister(0x0889);
if (getSignalBandwidth() == 500E3) {
writeRegister(0x0889, reg & 0xFB); // clear bit 2
} else {
writeRegister(0x0889, reg | 0x04); // set bit 2
}
}
void sx126x::setSignalBandwidth(long sbw) { void sx126x::setSignalBandwidth(long sbw) {
if (sbw <= 7.8E3) { _bw = 0x00; } if (sbw <= 7.8E3) { _bw = 0x00; }