Skip to content
Closed
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
fd619b4
Enable suport for the lr2021 on native
jessm33 May 22, 2026
c995d94
Merge branch 'meshtastic:develop' into Enable-lr2021-on-native
jessm33 May 22, 2026
3281071
add config for usb lr2021
jessm33 May 27, 2026
77b6915
Merge branch 'Enable-lr2021-on-native' of github.com:jessm33/firmware…
jessm33 May 27, 2026
fa3227c
fix filename
jessm33 May 27, 2026
4dd7630
Merge remote-tracking branch 'origin/develop' into Enable-lr2021-on-n…
jessm33 May 28, 2026
cce6d1d
Fix build of lr11x0 variants
jessm33 May 29, 2026
038b8e0
Speel good
jessm33 May 29, 2026
b2cbee8
config file updates
jessm33 Jun 2, 2026
ea0f59f
Merge remote-tracking branch 'upstream/develop' into Enable-lr2021-on…
jessm33 Jun 2, 2026
938a22e
Merge branch 'develop' into Enable-lr2021-on-native
jessm33 Jun 2, 2026
ad43637
fix: format rfswitch_table in LoRa configuration files for consistency
jessm33 Jun 3, 2026
cbd829c
Merge branch 'develop' into Enable-lr2021-on-native
jessm33 Jun 14, 2026
c176cc1
Merge branch 'develop' into Enable-lr2021-on-native
jessm33 Jun 23, 2026
0aacaf9
rename meshtoad config file to be clear on what module is on the hard…
jessm33 Jun 23, 2026
95f3ad9
remove config for test hardware
jessm33 Jun 23, 2026
d41d055
Merge branch 'develop' into Enable-lr2021-on-native
jessm33 Jul 1, 2026
fac2969
format files
jessm33 Jul 1, 2026
ead1ba4
adreess coderabbit issues
jessm33 Jul 1, 2026
ef490be
Merge branch 'develop' into Enable-lr2021-on-native
jessm33 Jul 20, 2026
7f8c194
Merge branch 'meshtastic:develop' into Enable-lr2021-on-native
jessm33 Jul 27, 2026
068a557
fix(portduino): resolve enum value conflict for LR2021 and LR11x0 modes
jessm33 Jul 27, 2026
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
31 changes: 31 additions & 0 deletions bin/config.d/lora-usb-meshtoad-nicerflora2021f33.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
Meta:
name: meshtoad2021
support: community
compatible:
- usb

Lora:
Module: lr2021
CS: 0
IRQ: 6
Reset: 2
Busy: 4
DIO3_TCXO_VOLTAGE: 1.8
# Defaults for LR2021, but can be overridden by user config
# LR2021_MAX_POWER: 22
# LR2021_MAX_POWER_HF: 22
# LR2021_IRQ_DIO_NUM: 9
spidev: ch341
USB_PID: 0x5512
USB_VID: 0x1A86
# Optional: Set the serial number for multi-radio support
# USB_Serialnum: 13374201

# NiceRF LoRa2021F33 module
rfswitch_table:
pins: [DIO5, DIO6, DIO8]
MODE_STBY: [LOW, LOW, LOW]
MODE_RX: [LOW, LOW, LOW]
MODE_TX: [LOW, HIGH, LOW]
MODE_RX_HF: [HIGH, LOW, LOW]
MODE_TX_HF: [LOW, LOW, HIGH]
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
2 changes: 1 addition & 1 deletion src/mesh/InterfacesTemplates.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ template class LR11x0Interface<LR1110>;
template class LR11x0Interface<LR1120>;
template class LR11x0Interface<LR1121>;
#endif
#if defined(USE_LR2021) && RADIOLIB_EXCLUDE_LR2021 != 1
#if RADIOLIB_EXCLUDE_LR2021 != 1
template class LR20x0Interface<LR2021>;
#endif
Comment on lines +30 to 32
#ifdef ARCH_STM32WL
Expand Down
16 changes: 9 additions & 7 deletions src/mesh/LR11x0Interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
#include "mesh/NodeDB.h"
#ifdef LR11X0_DIO_AS_RF_SWITCH
#include "rfswitch.h"
#define lr11x0_rfswitch_dio_pins rfswitch_dio_pins
#define lr11x0_rfswitch_table rfswitch_table
#elif ARCH_PORTDUINO
#include "PortduinoGlue.h"
#define rfswitch_dio_pins portduino_config.rfswitch_dio_pins
#define rfswitch_table portduino_config.rfswitch_table
#define lr11x0_rfswitch_dio_pins portduino_config.rfswitch_dio_pins
#define lr11x0_rfswitch_table portduino_config.rfswitch_table
#else
static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC};
static const Module::RfSwitchMode_t rfswitch_table[] = {
static const uint32_t lr11x0_rfswitch_dio_pins[] = {RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC};
static const Module::RfSwitchMode_t lr11x0_rfswitch_table[] = {
{LR11x0::MODE_STBY, {}}, {LR11x0::MODE_RX, {}}, {LR11x0::MODE_TX, {}}, {LR11x0::MODE_TX_HP, {}},
{LR11x0::MODE_TX_HF, {}}, {LR11x0::MODE_GNSS, {}}, {LR11x0::MODE_WIFI, {}}, END_OF_MODE_TABLE,
};
Expand Down Expand Up @@ -146,7 +148,7 @@ template <typename T> bool LR11x0Interface<T>::init()
#endif

if (dioAsRfSwitch) {
lora.setRfSwitchTable(rfswitch_dio_pins, rfswitch_table);
lora.setRfSwitchTable(lr11x0_rfswitch_dio_pins, lr11x0_rfswitch_table);
LOG_DEBUG("Set DIO RF switch");
}

Expand Down Expand Up @@ -205,7 +207,7 @@ template <typename T> bool LR11x0Interface<T>::reconfigure()
err = lora.setOutputPower(power);
assert(err == RADIOLIB_ERR_NONE);

// Apply RX gain mode — valid in STDBY, matches resetAGC() pattern
// Apply RX gain mode - valid in STDBY, matches resetAGC() pattern
err = lora.setRxBoostedGainMode(config.lora.sx126x_rx_boosted_gain);
if (err != RADIOLIB_ERR_NONE)
LOG_WARN("LR11x0 setRxBoostedGainMode %s%d", radioLibErr, err);
Expand Down Expand Up @@ -326,7 +328,7 @@ template <typename T> void LR11x0Interface<T>::resetAGC()

LOG_DEBUG("LR11x0 AGC reset: warm sleep + Calibrate(0x3F)");

// 1. Warm sleep — powers down the analog frontend, resetting AGC state
// 1. Warm sleep - powers down the analog frontend, resetting AGC state
lora.sleep(true, 0);

// 2. Wake to RC standby for stable calibration
Expand Down
2 changes: 1 addition & 1 deletion src/mesh/LR2021Interface.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#include "configuration.h"

#if defined(USE_LR2021) && RADIOLIB_EXCLUDE_LR2021 != 1
#if RADIOLIB_EXCLUDE_LR2021 != 1

#include "LR2021Interface.h"
#include "error.h"
Expand Down
15 changes: 11 additions & 4 deletions src/mesh/LR20x0Interface.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#include "configuration.h"

#if defined(USE_LR2021) && RADIOLIB_EXCLUDE_LR2021 != 1
#if RADIOLIB_EXCLUDE_LR2021 != 1
#include "LR20x0Interface.h"
#include "error.h"
#include "mesh/NodeDB.h"
Expand All @@ -12,6 +12,10 @@

#ifdef LR2021_DIO_AS_RF_SWITCH
#include "rfswitch.h"
#ifndef LR20X0_RFSWITCH_NATIVE
#define lr20x0_rfswitch_dio_pins rfswitch_dio_pins
#define lr20x0_rfswitch_table rfswitch_table
#endif
#elif ARCH_PORTDUINO
#include "PortduinoGlue.h"
#define lr20x0_rfswitch_dio_pins portduino_config.rfswitch_dio_pins
Expand Down Expand Up @@ -80,7 +84,10 @@ template <typename T> bool LR20x0Interface<T>::init()

RadioLibInterface::init();

#ifdef LR2021_IRQ_DIO_NUM
#if ARCH_PORTDUINO
lora.irqDioNum = portduino_config.lr2021_irq_dio_num;
LOG_DEBUG("Set irqDioNum %d", lora.irqDioNum);
#elif defined(LR2021_IRQ_DIO_NUM)
lora.irqDioNum = LR2021_IRQ_DIO_NUM;
LOG_DEBUG("Set irqDioNum %d", lora.irqDioNum);
#elif defined(IRQ_DIO_NUM)
Expand Down Expand Up @@ -211,7 +218,7 @@ template <typename T> bool LR20x0Interface<T>::reconfigure()
err = lora.setOutputPower(power);
assert(err == RADIOLIB_ERR_NONE);

// Apply RX gain mode — valid in STDBY, matches resetAGC() pattern
// Apply RX gain mode - valid in STDBY, matches resetAGC() pattern
err = lora.setRxBoostedGainMode(config.lora.sx126x_rx_boosted_gain);
if (err != RADIOLIB_ERR_NONE)
LOG_WARN("LR20x0 setRxBoostedGainMode %s%d", radioLibErr, err);
Expand Down Expand Up @@ -332,7 +339,7 @@ template <typename T> void LR20x0Interface<T>::resetAGC()

LOG_DEBUG("LR20x0 AGC reset: warm sleep + Calibrate(0x3F)");

// 1. Warm sleep — powers down the analog frontend, resetting AGC state
// 1. Warm sleep - powers down the analog frontend, resetting AGC state
lora.sleep(true, 0);

// 2. Wake to RC standby for stable calibration
Expand Down
6 changes: 4 additions & 2 deletions src/mesh/RadioInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ const RegionInfo regions[] = {
/*
https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf
https://standard.nbtc.go.th/getattachment/Standards/%E0%B8%A1%E0%B8%B2%E0%B8%95%E0%B8%A3%E0%B8%90%E0%B8%B2%E0%B8%99%E0%B8%97%E0%B8%B2%E0%B8%87%E0%B9%80%E0%B8%97%E0%B8%84%E0%B8%99%E0%B8%B4%E0%B8%84%E0%B8%82%E0%B8%AD%E0%B8%87%E0%B9%80%E0%B8%84%E0%B8%A3%E0%B8%B7%E0%B9%88%E0%B8%AD%E0%B8%87%E0%B9%82%E0%B8%97%E0%B8%A3%E0%B8%84%E0%B8%A1%E0%B8%99%E0%B8%B2%E0%B8%84%E0%B8%A1/1033-2565.pdf.aspx?lang=th-TH
Thailand 920–925 MHz set max TX power to 27 dBm and enforce 10% duty cycle, aligned with NBTC regulations.
Thailand 920-925 MHz set max TX power to 27 dBm and enforce 10% duty cycle, aligned with NBTC regulations.
*/
RDEF(TH, 920.0f, 925.0f, 10, 27, false, false, PROFILE_STD, PRESET(LONG_FAST), 0),

Expand Down Expand Up @@ -246,7 +246,7 @@ const RegionInfo regions[] = {

/*
ITU Region 1 (Europe, Africa, Middle East, former USSR) amateur 2m allocation: 144.000 - 146.000 MHz.
Power limit is the regulatory ceiling (1 W / 30 dBm) — individual hardware will cap below this
Power limit is the regulatory ceiling (1 W / 30 dBm) - individual hardware will cap below this
via its own PA curve; the field here is just the legal upper bound.

Default slot: 26 (144.510 MHz)
Expand Down Expand Up @@ -338,6 +338,8 @@ std::unique_ptr<RadioInterface> initLoRa()
return std::unique_ptr<RadioInterface>(new LR1120Interface(hal, cs, irq, rst, busy));
case use_lr1121:
return std::unique_ptr<RadioInterface>(new LR1121Interface(hal, cs, irq, rst, busy));
case use_lr2021:
return std::unique_ptr<RadioInterface>(new LR2021Interface(hal, cs, irq, rst, busy));
case use_llcc68:
return std::unique_ptr<RadioInterface>(new LLCC68Interface(hal, cs, irq, rst, busy));
case use_simradio:
Expand Down
15 changes: 11 additions & 4 deletions src/platform/portduino/PortduinoGlue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ void getMacAddr(uint8_t *dmac)
// the same kind of stable, host-derived identifier that the BlueZ
// path provides on Linux. If en0 isn't found or has no MAC, dmac is
// left untouched and the caller's "Blank MAC Address not allowed!"
// check will still fire — preserving existing behavior for users
// check will still fire - preserving existing behavior for users
// who deliberately rely on --hwid or YAML override.
struct ifaddrs *ifap = nullptr;
if (getifaddrs(&ifap) == 0) {
Expand Down Expand Up @@ -548,7 +548,7 @@ void portduinoSetup()
// Pass the full buffer size (9 = 8 chars + null) to getSerialString,
// not 8. The function treats `len` as buffer size and reserves one
// slot for the null terminator, so passing 8 produced a 7-char serial
// and broke the `strlen(serial) == 8` check below — masked on Linux
// and broke the `strlen(serial) == 8` check below - masked on Linux
// by the BlueZ HCI MAC fallback in getMacAddr(), but on macOS (where
// the BlueZ path is __linux__-guarded) it left mac_address empty and
// meshtasticd refused to start.
Expand Down Expand Up @@ -830,9 +830,14 @@ bool loadConfig(const char *configPath)
portduino_config.lr1110_max_power = yamlConfig["Lora"]["LR1110_MAX_POWER"].as<int>(22);
if (yamlConfig["Lora"]["LR1120_MAX_POWER"])
portduino_config.lr1120_max_power = yamlConfig["Lora"]["LR1120_MAX_POWER"].as<int>(13);
if (yamlConfig["Lora"]["LR2021_MAX_POWER"])
portduino_config.lr2021_max_power = yamlConfig["Lora"]["LR2021_MAX_POWER"].as<int>(22);
if (yamlConfig["Lora"]["LR2021_MAX_POWER_HF"])
portduino_config.lr2021_max_power_hf = yamlConfig["Lora"]["LR2021_MAX_POWER_HF"].as<int>(22);
if (yamlConfig["Lora"]["LR2021_IRQ_DIO_NUM"])
portduino_config.lr2021_irq_dio_num = yamlConfig["Lora"]["LR2021_IRQ_DIO_NUM"].as<int>(9);
if (yamlConfig["Lora"]["RF95_MAX_POWER"])
portduino_config.rf95_max_power = yamlConfig["Lora"]["RF95_MAX_POWER"].as<int>(20);

if (yamlConfig["Lora"]["TX_GAIN_LORA"]) {
YAML::Node tx_gain_node = yamlConfig["Lora"]["TX_GAIN_LORA"];
if (tx_gain_node.IsSequence() && tx_gain_node.size() != 0) {
Expand Down Expand Up @@ -928,6 +933,8 @@ bool loadConfig(const char *configPath)
portduino_config.rfswitch_table[2].values[i] = HIGH;
if (yamlConfig["Lora"]["rfswitch_table"]["MODE_TX_HP"][i].as<std::string>("") == "HIGH")
portduino_config.rfswitch_table[3].values[i] = HIGH;
if (yamlConfig["Lora"]["rfswitch_table"]["MODE_RX_HF"][i].as<std::string>("") == "HIGH")
portduino_config.rfswitch_table[3].values[i] = HIGH;
if (yamlConfig["Lora"]["rfswitch_table"]["MODE_TX_HF"][i].as<std::string>("") == "HIGH")
portduino_config.rfswitch_table[4].values[i] = HIGH;
if (yamlConfig["Lora"]["rfswitch_table"]["MODE_GNSS"][i].as<std::string>("") == "HIGH")
Expand Down Expand Up @@ -1170,7 +1177,7 @@ bool MAC_from_string(std::string mac_str, uint8_t *dmac)
std::string exec(const char *cmd)
{ // https://stackoverflow.com/a/478960
#ifdef ARCH_PORTDUINO_WASM
(void)cmd; // no shell/popen in the browser — shell-outs degrade to empty
(void)cmd; // no shell/popen in the browser - shell-outs degrade to empty
return "";
#endif
std::array<char, 128> buffer;
Expand Down
11 changes: 8 additions & 3 deletions src/platform/portduino/PortduinoGlue.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ enum lora_module_enum {
use_lr1110,
use_lr1120,
use_lr1121,
use_lr2021,
use_llcc68
};

Expand Down Expand Up @@ -76,9 +77,10 @@ std::string exec(const char *cmd);

extern struct portduino_config_struct {
// Lora
std::map<lora_module_enum, std::string> loraModules = {
{use_simradio, "sim"}, {use_autoconf, "auto"}, {use_rf95, "RF95"}, {use_sx1262, "sx1262"}, {use_sx1268, "sx1268"},
{use_sx1280, "sx1280"}, {use_lr1110, "lr1110"}, {use_lr1120, "lr1120"}, {use_lr1121, "lr1121"}, {use_llcc68, "LLCC68"}};
std::map<lora_module_enum, std::string> loraModules = {{use_simradio, "sim"}, {use_autoconf, "auto"}, {use_rf95, "RF95"},
{use_sx1262, "sx1262"}, {use_sx1268, "sx1268"}, {use_sx1280, "sx1280"},
{use_lr1110, "lr1110"}, {use_lr1120, "lr1120"}, {use_lr1121, "lr1121"},
{use_lr2021, "lr2021"}, {use_llcc68, "LLCC68"}};

std::map<screen_modules, std::string> screen_names = {{x11, "X11"}, {fb, "FB"}, {st7789, "ST7789"},
{st7735, "ST7735"}, {st7735s, "ST7735S"}, {st7796, "ST7796"},
Expand All @@ -100,6 +102,9 @@ extern struct portduino_config_struct {
int sx128x_max_power = 13;
int lr1110_max_power = 22;
int lr1120_max_power = 13;
int lr2021_max_power = 22;
int lr2021_max_power_hf = 22;
int lr2021_irq_dio_num = 9;
int rf95_max_power = 20;
bool dio2_as_rf_switch = false;
int dio3_tcxo_voltage = 0;
Expand Down
12 changes: 10 additions & 2 deletions variants/nrf52840/diy/nrf52_promicro_diy_tcxo/rfswitch.h
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
#pragma once
#include "RadioLib.h"

// Keep LR20x0 naming while RadioLib exposes LR2021 symbols.
Comment on lines 1 to 3
#ifndef LR20x0
#define LR20x0 LR2021
#endif

#ifndef RFSWITCH_NRF52TCXO_LR11X0_DEFINED
#define RFSWITCH_NRF52TCXO_LR11X0_DEFINED
// This is rewritten to match the requirements of the E80-900M2213S
// The E80 does not conform to the reference Semtech switches(!) and therefore needs a custom matrix.
// See footnote #3 in "https://www.cdebyte.com/products/E80-900M2213S/2#Pin"
Expand All @@ -30,13 +31,19 @@ static const Module::RfSwitchMode_t rfswitch_table[] = {
END_OF_MODE_TABLE,
// clang-format on
};
#endif

// LR2021 RF switch matrix following the standard Semtech / Seeed T1000-E reference topology.
// DIO5 -> antenna path select (HIGH = sub-GHz LF)
// DIO6 -> TX enable / HP PA select
// DIO7 -> not connected (no GNSS on LR2021)
// DIO8 -> RF front-end power enable

//
// LR20X0_RFSWITCH_NATIVE tells LR20x0Interface.cpp that lr20x0_rfswitch_* are defined here
// directly, so it should not alias rfswitch_dio_pins (which points to the LR11x0 table above).
#ifndef RFSWITCH_NRF52TCXO_LR20X0_DEFINED
#define RFSWITCH_NRF52TCXO_LR20X0_DEFINED
#define LR20X0_RFSWITCH_NATIVE
static const uint32_t lr20x0_rfswitch_dio_pins[] = {RADIOLIB_LR2021_DIO5, RADIOLIB_LR2021_DIO6, RADIOLIB_LR2021_DIO7,
RADIOLIB_LR2021_DIO8, RADIOLIB_NC};
Comment on lines 49 to 50

Expand All @@ -51,3 +58,4 @@ static const Module::RfSwitchMode_t lr20x0_rfswitch_table[] = {
END_OF_MODE_TABLE,
// clang-format on
};
#endif