Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
6 changes: 2 additions & 4 deletions src/helpers/radiolib/CustomSX1262.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <RadioLib.h>
#include "MeshCore.h"
#include "SX126xReset.h"

class CustomSX1262 : public SX1262 {
uint32_t _preambleMillis = 66;
Expand Down Expand Up @@ -89,10 +90,7 @@ class CustomSX1262 : public SX1262 {

// for improved RX with Heltec v4
#ifdef SX126X_REGISTER_PATCH
uint8_t r_data = 0;
readRegister(0x8B5, &r_data, 1);
r_data |= 0x01;
writeRegister(0x8B5, &r_data, 1);
sx126xApplyRegisterPatch(this);
#endif

MESH_DEBUG_PRINTLN("SX1262 status=0x%02X device_errors=0x%04X", getStatus(), getDeviceErrors());
Expand Down
59 changes: 40 additions & 19 deletions src/helpers/radiolib/LinuxSX1262.h
Original file line number Diff line number Diff line change
@@ -1,26 +1,47 @@
#pragma once

#include <RadioLib.h>
#include "MeshCore.h"
#include "CustomSX1262.h"
#include "SX126xReset.h"
// For the LinuxBoard definition behind `board` below. Reached transitively via
// target.h today, but named here so this header does not depend on include
// order -- LinuxSX1262Wrapper.h calls methods on it.
#include "LinuxBoard.h"

#define SX126X_IRQ_HEADER_VALID 0b0000010000 // 4 4 valid LoRa header received
#define SX126X_IRQ_PREAMBLE_DETECTED 0x04
#define SX126X_PREAMBLE_LENGTH 16

extern LinuxBoard board;

class LinuxSX1262 : public SX1262 {
// The Linux build's SX1262.
//
// Everything that is not Linux-specific comes from CustomSX1262 unchanged --
// the RX watchdog (startReceive/isReceiving and their preamble/header
// deadlines), the millis setters, the RX-boost readback. That logic is shared
// with every SX126x board and upstream keeps fixing it, so forking it here
// would mean re-copying each fix by hand and silently missing the ones nobody
// notices.
//
// What genuinely differs is initialisation: the MCU variants pick frequency,
// regulator, RF-switch and gain per board at compile time, but one binary here
// serves every HAT, so all of it comes from meshcored.ini at runtime.
class LinuxSX1262 : public CustomSX1262 {
public:
LinuxSX1262(Module *mod) : SX1262(mod) { }
LinuxSX1262(Module *mod) : CustomSX1262(mod) { }

// Shadows (not overrides) CustomSX1262::std_init(), which is non-virtual
// and reads LORA_*/SX126X_* build flags this variant does not define.
bool std_init(SPIClass* spi = NULL)
{
LinuxConfig config = board.config;
const LinuxConfig& config = board.config;

Serial.printf("Radio begin %f %f %d %d %f\n", config.lora_freq, config.lora_bw, config.lora_sf, config.lora_cr, config.lora_tcxo);
int status = begin(config.lora_freq, config.lora_bw, config.lora_sf, config.lora_cr, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, config.lora_tx_power, SX126X_PREAMBLE_LENGTH, config.lora_tcxo);
MESH_DEBUG_PRINTLN("SX1262 regulator requested: %s", config.use_regulator_ldo ? "LDO" : "DC-DC");
int status = begin(config.lora_freq, config.lora_bw, config.lora_sf, config.lora_cr, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, config.lora_tx_power, SX126X_PREAMBLE_LENGTH, config.lora_tcxo, config.use_regulator_ldo);
// if radio init fails with -707/-706, try again with tcxo voltage set to 0.0f
if (status == RADIOLIB_ERR_SPI_CMD_FAILED || status == RADIOLIB_ERR_SPI_CMD_INVALID) {
status = begin(config.lora_freq, config.lora_bw, config.lora_sf, config.lora_cr, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, config.lora_tx_power, SX126X_PREAMBLE_LENGTH, 0.0f);
MESH_DEBUG_PRINTLN("SX1262 init failed with error %d, retrying with TCXO at 0.0V", status);
status = begin(config.lora_freq, config.lora_bw, config.lora_sf, config.lora_cr, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, config.lora_tx_power, SX126X_PREAMBLE_LENGTH, 0.0f, config.use_regulator_ldo);
}
if (status != RADIOLIB_ERR_NONE) {
Serial.print("ERROR: radio init failed: ");
Expand All @@ -31,24 +52,24 @@ class LinuxSX1262 : public SX1262 {
setCRC(1);

setCurrentLimit(config.current_limit);
setDio2AsRfSwitch(config.dio2_as_rf_switch);
setRxBoostedGainMode(config.rx_boosted_gain);
sx126xApplyRxSettings(this, rxSettings());
if (config.lora_rxen_pin != RADIOLIB_NC || config.lora_txen_pin != RADIOLIB_NC) {
setRfSwitchPins(config.lora_rxen_pin, config.lora_txen_pin);
}

return true;
}
MESH_DEBUG_PRINTLN("SX1262 status=0x%02X device_errors=0x%04X", getStatus(), getDeviceErrors());

bool isReceiving() {
uint16_t irq = getIrqFlags();
bool detected = (irq & SX126X_IRQ_HEADER_VALID) || (irq & SX126X_IRQ_PREAMBLE_DETECTED);
return detected;
return true;
}

bool getRxBoostedGainMode() {
uint8_t rxGain = 0;
readRegister(RADIOLIB_SX126X_REG_RX_GAIN, &rxGain, 1);
return (rxGain == RADIOLIB_SX126X_RX_GAIN_BOOSTED);
// The RX settings for this node, as configured in meshcored.ini. Applied at
// init and re-applied after every AGC reset, both via sx126xApplyRxSettings()
// -- which is where the MCU variants instead read their SX126X_* build flags.
SX126xRxSettings rxSettings() const {
SX126xRxSettings s;
s.dio2_as_rf_switch = board.config.dio2_as_rf_switch;
s.rx_boosted_gain = board.config.rx_boosted_gain;
s.register_patch = board.config.rx_register_patch;
return s;
}
};
56 changes: 43 additions & 13 deletions src/helpers/radiolib/LinuxSX1262Wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,68 @@

#include "LinuxSX1262.h"
#include "RadioLibWrappers.h"
#include "SX126xReset.h"

class LinuxSX1262Wrapper : public RadioLibWrapper {
// _radio is held as the base mesh::Radio, so every use here needs the
// downcast. It is always a LinuxSX1262 -- the constructor takes one by
// reference -- so this is a naming convenience, not a checked conversion.
LinuxSX1262* r() const { return (LinuxSX1262 *)_radio; }

public:
LinuxSX1262Wrapper(LinuxSX1262& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { }

void setParams(float freq, float bw, uint8_t sf, uint8_t cr) override {
((LinuxSX1262 *)_radio)->setFrequency(freq);
((LinuxSX1262 *)_radio)->setSpreadingFactor(sf);
((LinuxSX1262 *)_radio)->setBandwidth(bw);
((LinuxSX1262 *)_radio)->setCodingRate(cr);
r()->setFrequency(freq);
r()->setSpreadingFactor(sf);
r()->setBandwidth(bw);
r()->setCodingRate(cr);
updatePreamble(sf);
PacketMillis pm = calcMaxPacketMillis(sf, bw, cr, preambleLengthForSF(sf));
r()->setPreambleMillis(pm.preambleMillis);
r()->setMaxPayloadMillis(pm.payloadMillis);
}

// Full SX126x receiver reset (warm sleep, recalibrate, re-image the configured
// band), the same one every other SX126x board gets. Without this override the
// base class falls back to a bare sleep(), so `set agc_int` was quietly a
// weaker knob on Linux than everywhere else.
//
// The settings argument is the Linux-specific part: recalibration drops
// DIO2-as-RF-switch, RX boosted gain and the 0x8B5 patch, and the shared
// helper restores them from the SX126X_* build flags, none of which are
// defined here -- those come from meshcored.ini. Handing it the runtime values
// makes it restore the right ones, rather than restoring the wrong ones for
// this caller to redo afterwards.
void doResetAGC() override {
sx126xResetAGC(r(), r()->rxSettings());
}

// Cold sleep, matching CustomSX1262Wrapper. The only caller shuts the daemon
// down straight afterwards, so there is no configuration worth retaining and
// the deeper state is the better one to leave the modem in.
void powerOff() override {
r()->sleep(false);
}

bool isReceivingPacket() override {
return ((LinuxSX1262 *)_radio)->isReceiving();
return r()->isReceiving();
}
float getCurrentRSSI() override {
return ((LinuxSX1262 *)_radio)->getRSSI(false);
return r()->getRSSI(false);
}
float getLastRSSI() const override { return ((LinuxSX1262 *)_radio)->getRSSI(); }
float getLastSNR() const override { return ((LinuxSX1262 *)_radio)->getSNR(); }
float getLastRSSI() const override { return r()->getRSSI(); }
float getLastSNR() const override { return r()->getSNR(); }

float packetScore(float snr, int packet_len) override {
int sf = ((LinuxSX1262 *)_radio)->spreadingFactor;
return packetScoreInt(snr, sf, packet_len);
return packetScoreInt(snr, r()->spreadingFactor, packet_len);
}
uint8_t getSpreadingFactor() const override { return ((LinuxSX1262 *)_radio)->spreadingFactor; }
uint8_t getSpreadingFactor() const override { return r()->spreadingFactor; }

bool setRxBoostedGainMode(bool en) override {
return ((LinuxSX1262 *)_radio)->setRxBoostedGainMode(en) == RADIOLIB_ERR_NONE;
return r()->setRxBoostedGainMode(en) == RADIOLIB_ERR_NONE;
}
bool getRxBoostedGainMode() const override {
return ((LinuxSX1262 *)_radio)->getRxBoostedGainMode();
return r()->getRxBoostedGainMode();
}
};
59 changes: 52 additions & 7 deletions src/helpers/radiolib/SX126xReset.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,41 @@

#include <RadioLib.h>

// RX settings that Calibrate(0x7F) does not preserve, so every reset has to
// restate them. Boards that fix these at build time (every MCU variant) do not
// need this -- see sx126xResetAGC() below, which reads their SX126X_* flags
// directly. It exists for targets configured at runtime instead: the Linux
// daemon serves every HAT from one binary, so its values come from
// meshcored.ini and cannot be macros.
struct SX126xRxSettings {
bool dio2_as_rf_switch = false;
bool rx_boosted_gain = false;
bool register_patch = false; // 0x8B5 RX-sensitivity patch
};

// The RX-sensitivity patch upstream added for the Heltec v4. Undocumented by
// Semtech, hence the bare register number.
inline void sx126xApplyRegisterPatch(SX126x* radio) {
uint8_t r_data = 0;
radio->readRegister(0x8B5, &r_data, 1);
r_data |= 0x01;
radio->writeRegister(0x8B5, &r_data, 1);
}

// Apply the RX settings calibration does not preserve. Both initial
// configuration and every later AGC reset go through here, so a setting added
// to SX126xRxSettings reaches both rather than having to be remembered twice.
inline void sx126xApplyRxSettings(SX126x* radio, const SX126xRxSettings& rx) {
radio->setDio2AsRfSwitch(rx.dio2_as_rf_switch);
radio->setRxBoostedGainMode(rx.rx_boosted_gain);
if (rx.register_patch) sx126xApplyRegisterPatch(radio);
}

// Full receiver reset for all SX126x-family chips (SX1262, SX1268, LLCC68, STM32WLx).
// Warm sleep powers down analog, Calibrate(0x7F) refreshes ADC/PLL/image calibration,
// then re-applies RX settings that calibration may reset.
inline void sx126xResetAGC(SX126x* radio, bool rx_boost_gain) {
// Warm sleep powers down analog, Calibrate(0x7F) refreshes ADC/PLL/image calibration.
// The caller then re-applies the RX settings calibration may have reset, via one of
// the two sx126xResetAGC() entry points below.
inline void sx126xRecalibrate(SX126x* radio) {
radio->sleep(true);
radio->standby(RADIOLIB_SX126X_STANDBY_RC, true);

Expand All @@ -21,6 +52,13 @@ inline void sx126xResetAGC(SX126x* radio, bool rx_boost_gain) {
// Calibrate(0x7F) defaults image calibration to 902-928MHz band.
// Re-calibrate for the actual operating frequency.
radio->calibrateImage(radio->freqMHz);
}

// MCU variants. The SX126X_* build flags still decide *which* settings apply,
// but the boosted-gain value is passed in -- callers read it back off the chip
// -- so a reset no longer clobbers a gain mode that was changed at runtime.
inline void sx126xResetAGC(SX126x* radio, bool rx_boost_gain) {
sx126xRecalibrate(radio);

#ifdef SX126X_DIO2_AS_RF_SWITCH
radio->setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH);
Expand All @@ -29,9 +67,16 @@ inline void sx126xResetAGC(SX126x* radio, bool rx_boost_gain) {
radio->setRxBoostedGainMode(rx_boost_gain);
#endif
#ifdef SX126X_REGISTER_PATCH
uint8_t r_data = 0;
radio->readRegister(0x8B5, &r_data, 1);
r_data |= 0x01;
radio->writeRegister(0x8B5, &r_data, 1);
sx126xApplyRegisterPatch(radio);
#endif
}

// Runtime-configured targets. Every setting comes from the caller and none of
// the SX126X_* macros are consulted, which makes this the single place they are
// applied rather than having the caller re-apply them afterwards: a setting
// added to SX126xRxSettings then reaches these targets too, instead of being
// silently dropped on them until someone notices.
inline void sx126xResetAGC(SX126x* radio, const SX126xRxSettings& rx) {
sx126xRecalibrate(radio);
sx126xApplyRxSettings(radio, rx);
}
2 changes: 2 additions & 0 deletions variants/linux/LinuxBoard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,8 @@ LinuxConfig::LoadResult LinuxConfig::load(const char *filename) {
else if (strcmp(key, "current_limit") == 0) { if (parse_float(key, value, &fval, &result.bad_values)) current_limit = fval; }
else if (strcmp(key, "dio2_as_rf_switch") == 0) { if (parse_bool(key, value, &bval, &result.bad_values)) dio2_as_rf_switch = bval; }
else if (strcmp(key, "rx_boosted_gain") == 0) { if (parse_bool(key, value, &bval, &result.bad_values)) rx_boosted_gain = bval; }
else if (strcmp(key, "use_regulator_ldo") == 0) { if (parse_bool(key, value, &bval, &result.bad_values)) use_regulator_ldo = bval; }
else if (strcmp(key, "rx_register_patch") == 0) { if (parse_bool(key, value, &bval, &result.bad_values)) rx_register_patch = bval; }

else if (strcmp(key, "lora_irq_pin") == 0) { if (parse_pin(key, value, 0, &pin, &result.bad_values)) lora_irq_pin = (uint32_t) pin; }
else if (strcmp(key, "lora_reset_pin") == 0) { if (parse_pin(key, value, 0, &pin, &result.bad_values)) lora_reset_pin = (uint32_t) pin; }
Expand Down
7 changes: 7 additions & 0 deletions variants/linux/LinuxBoard.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ class LinuxConfig {
bool dio2_as_rf_switch = false;
bool rx_boosted_gain = true;

// The MCU variants pick these per board at compile time, via the
// SX126X_USE_REGULATOR_LDO and SX126X_REGISTER_PATCH build flags. One binary
// here serves every HAT, so they are runtime config instead. Defaults match
// the compile-time defaults: DC-DC, no patch.
bool use_regulator_ldo = false;
bool rx_register_patch = false;

const char* spidev = "/dev/spidev0.0";
const char* lora_gpiochip = "gpiochip0";

Expand Down
9 changes: 5 additions & 4 deletions variants/linux/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,14 +119,16 @@ Key settings:
| `current_limit` | `140` | Radio over-current protection limit in mA |
| `dio2_as_rf_switch` | `0` | `1` = use DIO2 to drive the TX/RX RF switch. **Required for the Waveshare Core1262** (without it the radio inits but TX/RX are dead); depends on module wiring |
| `rx_boosted_gain` | `1` | `1` enables the SX126x RX boosted-gain mode; `0` disables |
| `use_regulator_ldo` | `0` | `1` powers the radio from the LDO instead of the DC-DC converter. Only for modules built without the DC-DC inductor |
| `rx_register_patch` | `0` | `1` applies the SX126x RX-sensitivity patch (bit 0 of register `0x8B5`). Try it if a HAT receives poorly |
| `advert_name` | `"Linux Repeater"` | Node name, first-run default only |
| `admin_password` | `"password"` | Admin password, **change this**, first-run default only |
| `lat` / `lon` | `0.0` | GPS coordinates for advertisement, first-run default only |

Comments (`#`, `;`), blank lines and `[section]` headers are ignored. Boolean
settings (`dio2_as_rf_switch`, `rx_boosted_gain`) accept `1`/`0`,
`true`/`false`, `on`/`off` or `yes`/`no`, case-insensitively; anything else is
a fatal invalid value.
settings (`dio2_as_rf_switch`, `rx_boosted_gain`, `use_regulator_ldo`,
`rx_register_patch`) accept `1`/`0`, `true`/`false`, `on`/`off` or `yes`/`no`,
case-insensitively; anything else is a fatal invalid value.

#### Config validation

Expand Down Expand Up @@ -276,4 +278,3 @@ sudo systemctl start meshcored
- **Only repeater firmware**, there is no `linux_companion` target yet; companion radio support (BLE/serial interface to a phone app) is not implemented for Linux.
- **Serial `erase` command is a no-op**, `formatFileSystem()` returns `false` on Linux, so the interactive serial `erase` command reports failure. To wipe the filesystem, use the `--erase` *startup* flag (or clear the VFS dir) instead, see step 5.
- **No power management**, `board.sleep()` is a no-op; the power-saving loop in `main.cpp` never actually sleeps.
- **Upstream-sync fragility**, the radio wrapper (`LinuxSX1262Wrapper`) implements the `RadioLibWrapper` interface by hand, so it can drift from upstream in two ways: a new **pure-virtual** method breaks the Linux build (e.g. `setParams()`), and a new **virtual-with-default** method silently no-ops on Linux until overridden (e.g. `set`/`getRxBoostedGainMode()`, which reported and applied the wrong state until added). Mirror `CustomSX1262Wrapper` when syncing.
2 changes: 2 additions & 0 deletions variants/linux/meshcored.ini
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,5 @@ lora_tcxo = 1.8
#current_limit = 140
#dio2_as_rf_switch = 1
#rx_boosted_gain = 1
#use_regulator_ldo = 1 # LDO instead of DC-DC; only for modules wired without a DC-DC inductor
#rx_register_patch = 1 # RX sensitivity patch (register 0x8B5); harmless, helps some modules
Loading