From b43c7c0f23690a966c899bb91463861db9b54365 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= <tgoettgens@gmail.com>
Date: Mon, 3 Jun 2024 23:04:40 +0200
Subject: [PATCH] LR1110 support (#3013)

* DOES NOT WORK

* trunk

* DOES NOT WORK

* trunk

* DOES NOT WORK

* trunk

* WIP: LR11x0 non functional interface code. Please don't expect a working firmware out of this! I don't know what i am doing! :-)

* trunk fmt

* use canon toolchain

* update and fix radiolib dependency

* Switch Radiolib back to GIT checkout

* enable tcxo and fix startReceive

* progress

* Correct midjudgement on scope of build defines.

* - enable peripheral power rail during startup init
- fix portduino builds

* add tracker pinout variant

* update to radiolib 6.6.0 API (aka: godmode is not for mere mortals)

* tracker is not so 'extra' any more

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
---
 boards/wio-sdk-wm1110.json                 |  58 ++++
 boards/wio-tracker-wm1110.json             |  58 ++++
 src/detect/LoRaRadioType.h                 |  13 +-
 src/main.cpp                               |  28 ++
 src/mesh/InterfacesTemplates.cpp           |   4 +
 src/mesh/LR1110Interface.cpp               |   9 +
 src/mesh/LR1110Interface.h                 |  13 +
 src/mesh/LR1120Interface.cpp               |   9 +
 src/mesh/LR1120Interface.h                 |  13 +
 src/mesh/LR11x0Interface.cpp               | 299 +++++++++++++++++++++
 src/mesh/LR11x0Interface.h                 |  71 +++++
 src/platform/nrf52/architecture.h          |   2 +
 variants/wio-sdk-wm1110/platformio.ini     |  15 ++
 variants/wio-sdk-wm1110/variant.cpp        |  45 ++++
 variants/wio-sdk-wm1110/variant.h          | 111 ++++++++
 variants/wio-tracker-wm1110/platformio.ini |  14 +
 variants/wio-tracker-wm1110/variant.cpp    |  45 ++++
 variants/wio-tracker-wm1110/variant.h      | 111 ++++++++
 18 files changed, 917 insertions(+), 1 deletion(-)
 create mode 100644 boards/wio-sdk-wm1110.json
 create mode 100644 boards/wio-tracker-wm1110.json
 create mode 100644 src/mesh/LR1110Interface.cpp
 create mode 100644 src/mesh/LR1110Interface.h
 create mode 100644 src/mesh/LR1120Interface.cpp
 create mode 100644 src/mesh/LR1120Interface.h
 create mode 100644 src/mesh/LR11x0Interface.cpp
 create mode 100644 src/mesh/LR11x0Interface.h
 create mode 100644 variants/wio-sdk-wm1110/platformio.ini
 create mode 100644 variants/wio-sdk-wm1110/variant.cpp
 create mode 100644 variants/wio-sdk-wm1110/variant.h
 create mode 100644 variants/wio-tracker-wm1110/platformio.ini
 create mode 100644 variants/wio-tracker-wm1110/variant.cpp
 create mode 100644 variants/wio-tracker-wm1110/variant.h

diff --git a/boards/wio-sdk-wm1110.json b/boards/wio-sdk-wm1110.json
new file mode 100644
index 000000000..029c9c085
--- /dev/null
+++ b/boards/wio-sdk-wm1110.json
@@ -0,0 +1,58 @@
+{
+  "build": {
+    "arduino": {
+      "ldscript": "nrf52840_s140_v6.ld"
+    },
+    "core": "nRF5",
+    "cpu": "cortex-m4",
+    "extra_flags": "-DARDUINO_WIO_WM1110 -DNRF52840_XXAA",
+    "f_cpu": "64000000L",
+    "hwids": [
+      ["0x239A", "0x8029"],
+      ["0x239A", "0x0029"],
+      ["0x239A", "0x002A"],
+      ["0x239A", "0x802A"]
+    ],
+    "usb_product": "WIO-BOOT",
+    "mcu": "nrf52840",
+    "variant": "Seeed_WIO_WM1110",
+    "bsp": {
+      "name": "adafruit"
+    },
+    "softdevice": {
+      "sd_flags": "-DS140",
+      "sd_name": "s140",
+      "sd_version": "6.1.1",
+      "sd_fwid": "0x00B6"
+    },
+    "bootloader": {
+      "settings_addr": "0xFF000"
+    }
+  },
+  "connectivity": ["bluetooth"],
+  "debug": {
+    "jlink_device": "nRF52840_xxAA",
+    "svd_path": "nrf52840.svd"
+  },
+  "frameworks": ["arduino"],
+  "name": "Seeed WIO WM1110",
+  "upload": {
+    "maximum_ram_size": 248832,
+    "maximum_size": 815104,
+    "speed": 115200,
+    "protocol": "nrfutil",
+    "protocols": [
+      "jlink",
+      "nrfjprog",
+      "nrfutil",
+      "stlink",
+      "cmsis-dap",
+      "blackmagic"
+    ],
+    "use_1200bps_touch": true,
+    "require_upload_port": true,
+    "wait_for_upload_port": true
+  },
+  "url": "https://www.seeedstudio.com/Wio-WM1110-Dev-Kit-p-5677.html",
+  "vendor": "Seeed Studio"
+}
diff --git a/boards/wio-tracker-wm1110.json b/boards/wio-tracker-wm1110.json
new file mode 100644
index 000000000..029c9c085
--- /dev/null
+++ b/boards/wio-tracker-wm1110.json
@@ -0,0 +1,58 @@
+{
+  "build": {
+    "arduino": {
+      "ldscript": "nrf52840_s140_v6.ld"
+    },
+    "core": "nRF5",
+    "cpu": "cortex-m4",
+    "extra_flags": "-DARDUINO_WIO_WM1110 -DNRF52840_XXAA",
+    "f_cpu": "64000000L",
+    "hwids": [
+      ["0x239A", "0x8029"],
+      ["0x239A", "0x0029"],
+      ["0x239A", "0x002A"],
+      ["0x239A", "0x802A"]
+    ],
+    "usb_product": "WIO-BOOT",
+    "mcu": "nrf52840",
+    "variant": "Seeed_WIO_WM1110",
+    "bsp": {
+      "name": "adafruit"
+    },
+    "softdevice": {
+      "sd_flags": "-DS140",
+      "sd_name": "s140",
+      "sd_version": "6.1.1",
+      "sd_fwid": "0x00B6"
+    },
+    "bootloader": {
+      "settings_addr": "0xFF000"
+    }
+  },
+  "connectivity": ["bluetooth"],
+  "debug": {
+    "jlink_device": "nRF52840_xxAA",
+    "svd_path": "nrf52840.svd"
+  },
+  "frameworks": ["arduino"],
+  "name": "Seeed WIO WM1110",
+  "upload": {
+    "maximum_ram_size": 248832,
+    "maximum_size": 815104,
+    "speed": 115200,
+    "protocol": "nrfutil",
+    "protocols": [
+      "jlink",
+      "nrfjprog",
+      "nrfutil",
+      "stlink",
+      "cmsis-dap",
+      "blackmagic"
+    ],
+    "use_1200bps_touch": true,
+    "require_upload_port": true,
+    "wait_for_upload_port": true
+  },
+  "url": "https://www.seeedstudio.com/Wio-WM1110-Dev-Kit-p-5677.html",
+  "vendor": "Seeed Studio"
+}
diff --git a/src/detect/LoRaRadioType.h b/src/detect/LoRaRadioType.h
index eadd92e64..3975153b5 100644
--- a/src/detect/LoRaRadioType.h
+++ b/src/detect/LoRaRadioType.h
@@ -1,5 +1,16 @@
 #pragma once
 
-enum LoRaRadioType { NO_RADIO, STM32WLx_RADIO, SIM_RADIO, RF95_RADIO, SX1262_RADIO, SX1268_RADIO, LLCC68_RADIO, SX1280_RADIO };
+enum LoRaRadioType {
+    NO_RADIO,
+    STM32WLx_RADIO,
+    SIM_RADIO,
+    RF95_RADIO,
+    SX1262_RADIO,
+    SX1268_RADIO,
+    LLCC68_RADIO,
+    SX1280_RADIO,
+    LR1110_RADIO,
+    LR1120_RADIO
+};
 
 extern LoRaRadioType radioType;
\ No newline at end of file
diff --git a/src/main.cpp b/src/main.cpp
index 3c1893690..52de93e83 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -65,6 +65,8 @@ NRF52Bluetooth *nrf52Bluetooth;
 #endif
 
 #include "LLCC68Interface.h"
+#include "LR1110Interface.h"
+#include "LR1120Interface.h"
 #include "RF95Interface.h"
 #include "SX1262Interface.h"
 #include "SX1268Interface.h"
@@ -882,6 +884,32 @@ void setup()
     }
 #endif
 
+#if defined(USE_LR1110)
+    if (!rIf) {
+        rIf = new LR1110Interface(RadioLibHAL, LR1110_SPI_NSS_PIN, LR1110_IRQ_PIN, LR1110_NRESER_PIN, LR1110_BUSY_PIN);
+        if (!rIf->init()) {
+            LOG_WARN("Failed to find LR1110 radio\n");
+            delete rIf;
+            rIf = NULL;
+        } else {
+            LOG_INFO("LR1110 Radio init succeeded, using LR1110 radio\n");
+        }
+    }
+#endif
+
+#if defined(USE_LR1120)
+    if (!rIf) {
+        rIf = new LR1120Interface(RadioLibHAL, LR1120_SPI_NSS_PIN, LR1120_IRQ_PIN, LR1120_NRESER_PIN, LR1120_BUSY_PIN);
+        if (!rIf->init()) {
+            LOG_WARN("Failed to find LR1120 radio\n");
+            delete rIf;
+            rIf = NULL;
+        } else {
+            LOG_INFO("LR1120 Radio init succeeded, using LR1120 radio\n");
+        }
+    }
+#endif
+
 #if defined(USE_SX1280)
     if (!rIf) {
         rIf = new SX1280Interface(RadioLibHAL, SX128X_CS, SX128X_DIO1, SX128X_RESET, SX128X_BUSY);
diff --git a/src/mesh/InterfacesTemplates.cpp b/src/mesh/InterfacesTemplates.cpp
index c732829e9..f2cac8028 100644
--- a/src/mesh/InterfacesTemplates.cpp
+++ b/src/mesh/InterfacesTemplates.cpp
@@ -1,3 +1,5 @@
+#include "LR11x0Interface.cpp"
+#include "LR11x0Interface.h"
 #include "SX126xInterface.cpp"
 #include "SX126xInterface.h"
 #include "SX128xInterface.cpp"
@@ -10,6 +12,8 @@ template class SX126xInterface<SX1262>;
 template class SX126xInterface<SX1268>;
 template class SX126xInterface<LLCC68>;
 template class SX128xInterface<SX1280>;
+template class LR11x0Interface<LR1110>;
+template class LR11x0Interface<LR1120>;
 #ifdef ARCH_STM32WL
 template class SX126xInterface<STM32WLx>;
 #endif
diff --git a/src/mesh/LR1110Interface.cpp b/src/mesh/LR1110Interface.cpp
new file mode 100644
index 000000000..c000bd838
--- /dev/null
+++ b/src/mesh/LR1110Interface.cpp
@@ -0,0 +1,9 @@
+#include "LR1110Interface.h"
+#include "configuration.h"
+#include "error.h"
+
+LR1110Interface::LR1110Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
+                                 RADIOLIB_PIN_TYPE busy)
+    : LR11x0Interface(hal, cs, irq, rst, busy)
+{
+}
\ No newline at end of file
diff --git a/src/mesh/LR1110Interface.h b/src/mesh/LR1110Interface.h
new file mode 100644
index 000000000..79e7c36ca
--- /dev/null
+++ b/src/mesh/LR1110Interface.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include "LR11x0Interface.h"
+
+/**
+ * Our adapter for LR1110 radios
+ */
+class LR1110Interface : public LR11x0Interface<LR1110>
+{
+  public:
+    LR1110Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
+                    RADIOLIB_PIN_TYPE busy);
+};
\ No newline at end of file
diff --git a/src/mesh/LR1120Interface.cpp b/src/mesh/LR1120Interface.cpp
new file mode 100644
index 000000000..94f3568f7
--- /dev/null
+++ b/src/mesh/LR1120Interface.cpp
@@ -0,0 +1,9 @@
+#include "LR1120Interface.h"
+#include "configuration.h"
+#include "error.h"
+
+LR1120Interface::LR1120Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
+                                 RADIOLIB_PIN_TYPE busy)
+    : LR11x0Interface(hal, cs, irq, rst, busy)
+{
+}
\ No newline at end of file
diff --git a/src/mesh/LR1120Interface.h b/src/mesh/LR1120Interface.h
new file mode 100644
index 000000000..fc59293ec
--- /dev/null
+++ b/src/mesh/LR1120Interface.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include "LR11x0Interface.h"
+
+/**
+ * Our adapter for LR1120 wideband radios
+ */
+class LR1120Interface : public LR11x0Interface<LR1120>
+{
+  public:
+    LR1120Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
+                    RADIOLIB_PIN_TYPE busy);
+};
\ No newline at end of file
diff --git a/src/mesh/LR11x0Interface.cpp b/src/mesh/LR11x0Interface.cpp
new file mode 100644
index 000000000..bffca0c44
--- /dev/null
+++ b/src/mesh/LR11x0Interface.cpp
@@ -0,0 +1,299 @@
+#include "LR11x0Interface.h"
+#include "configuration.h"
+#include "error.h"
+#include "mesh/NodeDB.h"
+#ifdef ARCH_PORTDUINO
+#include "PortduinoGlue.h"
+#endif
+
+// Particular boards might define a different max power based on what their hardware can do, default to max power output if not
+// specified (may be dangerous if using external PA and LR11x0 power config forgotten)
+#ifndef LR11X0_MAX_POWER
+#define LR11X0_MAX_POWER 22
+#endif
+
+template <typename T>
+LR11x0Interface<T>::LR11x0Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
+                                    RADIOLIB_PIN_TYPE busy)
+    : RadioLibInterface(hal, cs, irq, rst, busy, &lora), lora(&module)
+{
+    LOG_WARN("LR11x0Interface(cs=%d, irq=%d, rst=%d, busy=%d)\n", cs, irq, rst, busy);
+}
+
+/// Initialise the Driver transport hardware and software.
+/// Make sure the Driver is properly configured before calling init().
+/// \return true if initialisation succeeded.
+template <typename T> bool LR11x0Interface<T>::init()
+{
+#ifdef LR11X0_POWER_EN
+    pinMode(LR11X0_POWER_EN, OUTPUT);
+    digitalWrite(LR11X0_POWER_EN, HIGH);
+#endif
+
+// FIXME: correct logic to default to not using TCXO if no voltage is specified for LR11x0_DIO3_TCXO_VOLTAGE
+#if !defined(LR11X0_DIO3_TCXO_VOLTAGE)
+    float tcxoVoltage =
+        0; // "TCXO reference voltage to be set on DIO3. Defaults to 1.6 V, set to 0 to skip." per
+           // https://github.com/jgromes/RadioLib/blob/690a050ebb46e6097c5d00c371e961c1caa3b52e/src/modules/LR11x0/LR11x0.h#L471C26-L471C104
+    // (DIO3 is free to be used as an IRQ)
+    LOG_DEBUG("LR11X0_DIO3_TCXO_VOLTAGE not defined, not using DIO3 as TCXO reference voltage\n");
+#else
+    float tcxoVoltage = LR11X0_DIO3_TCXO_VOLTAGE;
+    LOG_DEBUG("LR11X0_DIO3_TCXO_VOLTAGE defined, using DIO3 as TCXO reference voltage at %f V\n", LR11X0_DIO3_TCXO_VOLTAGE);
+    // (DIO3 is not free to be used as an IRQ)
+#endif
+
+    RadioLibInterface::init();
+
+    if (power > LR11X0_MAX_POWER) // Clamp power to maximum defined level
+        power = LR11X0_MAX_POWER;
+
+    limitPower();
+
+    // set RF switch configuration for Wio WM1110
+    // Wio WM1110 uses DIO5 and DIO6 for RF switching
+    // NOTE: other boards may be different. If you are
+    // using a different board, you may need to wrap
+    // this in a conditional.
+
+    static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_NC, RADIOLIB_NC,
+                                                 RADIOLIB_NC};
+
+    static const Module::RfSwitchMode_t rfswitch_table[] = {
+        // mode                  DIO5  DIO6
+        {LR11x0::MODE_STBY, {LOW, LOW}},  {LR11x0::MODE_RX, {HIGH, LOW}},
+        {LR11x0::MODE_TX, {HIGH, HIGH}},  {LR11x0::MODE_TX_HP, {LOW, HIGH}},
+        {LR11x0::MODE_TX_HF, {LOW, LOW}}, {LR11x0::MODE_GNSS, {LOW, LOW}},
+        {LR11x0::MODE_WIFI, {LOW, LOW}},  END_OF_MODE_TABLE,
+    };
+
+// We need to do this before begin() call
+#ifdef LR11X0_DIO_AS_RF_SWITCH
+    LOG_DEBUG("Setting DIO RF switch\n");
+    bool dioAsRfSwitch = true;
+#elif defined(ARCH_PORTDUINO)
+    bool dioAsRfSwitch = false;
+    if (settingsMap[dio2_as_rf_switch]) {
+        LOG_DEBUG("Setting DIO RF switch\n");
+        dioAsRfSwitch = true;
+    }
+#else
+    bool dioAsRfSwitch = false;
+#endif
+
+    if (dioAsRfSwitch)
+        lora.setRfSwitchTable(rfswitch_dio_pins, rfswitch_table);
+
+    int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage);
+    // \todo Display actual typename of the adapter, not just `LR11x0`
+    LOG_INFO("LR11x0 init result %d\n", res);
+    if (res == RADIOLIB_ERR_CHIP_NOT_FOUND)
+        return false;
+
+    LOG_INFO("Frequency set to %f\n", getFreq());
+    LOG_INFO("Bandwidth set to %f\n", bw);
+    LOG_INFO("Power output set to %d\n", power);
+
+    if (res == RADIOLIB_ERR_NONE)
+        res = lora.setCRC(2);
+
+    // FIXME: May want to set depending on a definition, currently all LR1110 variant files use the DC-DC regulator option
+    if (res == RADIOLIB_ERR_NONE)
+        res = lora.setRegulatorDCDC();
+
+    if (res == RADIOLIB_ERR_NONE) {
+        if (config.lora.sx126x_rx_boosted_gain) { // the name is unfortunate but historically accurate
+            res = lora.setRxBoostedGainMode(true);
+            LOG_INFO("Set RX gain to boosted mode; result: %d\n", res);
+        } else {
+            res = lora.setRxBoostedGainMode(false);
+            LOG_INFO("Set RX gain to power saving mode (boosted mode off); result: %d\n", res);
+        }
+    }
+
+    if (res == RADIOLIB_ERR_NONE)
+        startReceive(); // start receiving
+
+    return res == RADIOLIB_ERR_NONE;
+}
+
+template <typename T> bool LR11x0Interface<T>::reconfigure()
+{
+    RadioLibInterface::reconfigure();
+
+    // set mode to standby
+    setStandby();
+
+    // configure publicly accessible settings
+    int err = lora.setSpreadingFactor(sf);
+    if (err != RADIOLIB_ERR_NONE)
+        RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
+
+    err = lora.setBandwidth(bw);
+    if (err != RADIOLIB_ERR_NONE)
+        RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
+
+    err = lora.setCodingRate(cr);
+    if (err != RADIOLIB_ERR_NONE)
+        RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
+
+    // Hmm - seems to lower SNR when the signal levels are high.  Leaving off for now...
+    // TODO: Confirm gain registers are okay now
+    // err = lora.setRxGain(true);
+    // assert(err == RADIOLIB_ERR_NONE);
+
+    err = lora.setSyncWord(syncWord);
+    assert(err == RADIOLIB_ERR_NONE);
+
+    err = lora.setPreambleLength(preambleLength);
+    assert(err == RADIOLIB_ERR_NONE);
+
+    err = lora.setFrequency(getFreq());
+    if (err != RADIOLIB_ERR_NONE)
+        RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
+
+    if (power > LR11X0_MAX_POWER) // This chip has lower power limits than some
+        power = LR11X0_MAX_POWER;
+
+    err = lora.setOutputPower(power);
+    assert(err == RADIOLIB_ERR_NONE);
+
+    startReceive(); // restart receiving
+
+    return RADIOLIB_ERR_NONE;
+}
+
+template <typename T> void INTERRUPT_ATTR LR11x0Interface<T>::disableInterrupt()
+{
+    lora.clearIrqAction();
+}
+
+template <typename T> void LR11x0Interface<T>::setStandby()
+{
+    checkNotification(); // handle any pending interrupts before we force standby
+
+    int err = lora.standby();
+
+    if (err != RADIOLIB_ERR_NONE) {
+        LOG_DEBUG("LR11x0 standby failed with error %d\n", err);
+    }
+
+    assert(err == RADIOLIB_ERR_NONE);
+
+    isReceiving = false; // If we were receiving, not any more
+    activeReceiveStart = 0;
+    disableInterrupt();
+    completeSending(); // If we were sending, not anymore
+}
+
+/**
+ * Add SNR data to received messages
+ */
+template <typename T> void LR11x0Interface<T>::addReceiveMetadata(meshtastic_MeshPacket *mp)
+{
+    // LOG_DEBUG("PacketStatus %x\n", lora.getPacketStatus());
+    mp->rx_snr = lora.getSNR();
+    mp->rx_rssi = lround(lora.getRSSI());
+}
+
+/** We override to turn on transmitter power as needed.
+ */
+template <typename T> void LR11x0Interface<T>::configHardwareForSend()
+{
+    RadioLibInterface::configHardwareForSend();
+}
+
+// For power draw measurements, helpful to force radio to stay sleeping
+// #define SLEEP_ONLY
+
+template <typename T> void LR11x0Interface<T>::startReceive()
+{
+#ifdef SLEEP_ONLY
+    sleep();
+#else
+
+    setStandby();
+
+    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.
+    // Furthermore, we need the PREAMBLE_DETECTED and HEADER_VALID IRQ flag to detect whether we are actively receiving
+    int err = lora.startReceive(
+        RADIOLIB_LR11X0_RX_TIMEOUT_INF, RADIOLIB_LR11X0_IRQ_RX_DONE,
+        0); // only RX_DONE IRQ is needed, we'll check for PREAMBLE_DETECTED and HEADER_VALID in isActivelyReceiving
+    assert(err == RADIOLIB_ERR_NONE);
+
+    isReceiving = true;
+
+    // Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits
+    enableInterrupt(isrRxLevel0);
+#endif
+}
+
+/** Is the channel currently active? */
+template <typename T> bool LR11x0Interface<T>::isChannelActive()
+{
+    // check if we can detect a LoRa preamble on the current channel
+    int16_t result;
+
+    setStandby();
+    result = lora.scanChannel();
+    if (result == RADIOLIB_LORA_DETECTED)
+        return true;
+
+    assert(result != RADIOLIB_ERR_WRONG_MODEM);
+
+    return false;
+}
+
+/** Could we send right now (i.e. either not actively receiving or transmitting)? */
+template <typename T> bool LR11x0Interface<T>::isActivelyReceiving()
+{
+    // The IRQ status will be cleared when we start our read operation. Check if we've started a header, but haven't yet
+    // received and handled the interrupt for reading the packet/handling errors.
+
+    uint16_t irq = lora.getIrqStatus();
+    bool detected = (irq & (RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID | RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED));
+    // Handle false detections
+    if (detected) {
+        uint32_t now = millis();
+        if (!activeReceiveStart) {
+            activeReceiveStart = now;
+        } else if ((now - activeReceiveStart > 2 * preambleTimeMsec) && !(irq & RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID)) {
+            // The HEADER_VALID flag should be set by now if it was really a packet, so ignore PREAMBLE_DETECTED flag
+            activeReceiveStart = 0;
+            LOG_DEBUG("Ignore false preamble detection.\n");
+            return false;
+        } else if (now - activeReceiveStart > maxPacketTimeMsec) {
+            // We should have gotten an RX_DONE IRQ by now if it was really a packet, so ignore HEADER_VALID flag
+            activeReceiveStart = 0;
+            LOG_DEBUG("Ignore false header detection.\n");
+            return false;
+        }
+    }
+
+    // if (detected) LOG_DEBUG("rx detected\n");
+    return detected;
+}
+
+template <typename T> bool LR11x0Interface<T>::sleep()
+{
+    // Not keeping config is busted - next time nrf52 board boots lora sending fails  tcxo related? - see datasheet
+    // \todo Display actual typename of the adapter, not just `LR11x0`
+    LOG_DEBUG("LR11x0 entering sleep mode (FIXME, don't keep config)\n");
+    setStandby(); // Stop any pending operations
+
+    // turn off TCXO if it was powered
+    // FIXME - this isn't correct
+    // lora.setTCXO(0);
+
+    // put chipset into sleep mode (we've already disabled interrupts by now)
+    bool keepConfig = true;
+    lora.sleep(keepConfig, 0); // Note: we do not keep the config, full reinit will be needed
+
+#ifdef LR11X0_POWER_EN
+    digitalWrite(LR11X0_POWER_EN, LOW);
+#endif
+
+    return true;
+}
\ No newline at end of file
diff --git a/src/mesh/LR11x0Interface.h b/src/mesh/LR11x0Interface.h
new file mode 100644
index 000000000..11a389d25
--- /dev/null
+++ b/src/mesh/LR11x0Interface.h
@@ -0,0 +1,71 @@
+#pragma once
+
+#include "RadioLibInterface.h"
+
+/**
+ * \brief Adapter for LR11x0 radio family. Implements common logic for child classes.
+ * \tparam T RadioLib module type for LR11x0: SX1262, SX1268.
+ */
+template <class T> class LR11x0Interface : public RadioLibInterface
+{
+  public:
+    LR11x0Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
+                    RADIOLIB_PIN_TYPE busy);
+
+    /// Initialise the Driver transport hardware and software.
+    /// Make sure the Driver is properly configured before calling init().
+    /// \return true if initialisation succeeded.
+    virtual bool init() override;
+
+    /// Apply any radio provisioning changes
+    /// Make sure the Driver is properly configured before calling init().
+    /// \return true if initialisation succeeded.
+    virtual bool reconfigure() override;
+
+    /// Prepare hardware for sleep.  Call this _only_ for deep sleep, not needed for light sleep.
+    virtual bool sleep() override;
+
+    bool isIRQPending() override { return lora.getIrqStatus() != 0; }
+
+  protected:
+    /**
+     * Specific module instance
+     */
+    T lora;
+
+    /**
+     * Glue functions called from ISR land
+     */
+    virtual void disableInterrupt() override;
+
+    /**
+     * Enable a particular ISR callback glue function
+     */
+    virtual void enableInterrupt(void (*callback)()) { lora.setIrqAction(callback); }
+
+    /** can we detect a LoRa preamble on the current channel? */
+    virtual bool isChannelActive() override;
+
+    /** are we actively receiving a packet (only called during receiving state) */
+    virtual bool isActivelyReceiving() override;
+
+    /**
+     * Start waiting to receive a message
+     */
+    virtual void startReceive() override;
+
+    /**
+     *  We override to turn on transmitter power as needed.
+     */
+    virtual void configHardwareForSend() override;
+
+    /**
+     * Add SNR data to received messages
+     */
+    virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) override;
+
+    virtual void setStandby() override;
+
+  private:
+    uint32_t activeReceiveStart = 0;
+};
diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h
index 68bd87801..b91c57c5e 100644
--- a/src/platform/nrf52/architecture.h
+++ b/src/platform/nrf52/architecture.h
@@ -56,6 +56,8 @@
 #define HW_VENDOR meshtastic_HardwareModel_TWC_MESH_V4
 #elif defined(NRF52_PROMICRO_DIY)
 #define HW_VENDOR meshtastic_HardwareModel_NRF52_PROMICRO_DIY
+#elif defined(WIO_WM1110)
+#define HW_VENDOR meshtastic_HardwareModel_PRIVATE_HW
 #elif defined(PRIVATE_HW) || defined(FEATHER_DIY)
 #define HW_VENDOR meshtastic_HardwareModel_PRIVATE_HW
 #else
diff --git a/variants/wio-sdk-wm1110/platformio.ini b/variants/wio-sdk-wm1110/platformio.ini
new file mode 100644
index 000000000..8b1433dd1
--- /dev/null
+++ b/variants/wio-sdk-wm1110/platformio.ini
@@ -0,0 +1,15 @@
+; The very slick RAK wireless RAK 4631 / 4630 board - Unified firmware for 5005/19003, with or without OLED RAK 1921
+[env:wio-sdk-wm1110]
+extends = nrf52840_base
+board = wio-sdk-wm1110
+board_level = extra
+; platform = https://github.com/maxgerhardt/platform-nordicnrf52#cac6fcf943a41accd2aeb4f3659ae297a73f422e
+build_flags = ${nrf52840_base.build_flags} -Ivariants/wio-sdk-wm1110 -DWIO_WM1110
+  -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard"
+  -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
+build_src_filter = ${nrf52_base.build_src_filter} +<../variants/wio-sdk-wm1110>
+lib_deps = 
+  ${nrf52840_base.lib_deps}
+debug_tool = jlink
+; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm)
+upload_protocol = jlink
\ No newline at end of file
diff --git a/variants/wio-sdk-wm1110/variant.cpp b/variants/wio-sdk-wm1110/variant.cpp
new file mode 100644
index 000000000..5a3587982
--- /dev/null
+++ b/variants/wio-sdk-wm1110/variant.cpp
@@ -0,0 +1,45 @@
+/*
+  Copyright (c) 2014-2015 Arduino LLC.  All right reserved.
+  Copyright (c) 2016 Sandeep Mistry All right reserved.
+  Copyright (c) 2018, Adafruit Industries (adafruit.com)
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License as published by the Free Software Foundation; either
+  version 2.1 of the License, or (at your option) any later version.
+
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+  See the GNU Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with this library; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#include "variant.h"
+#include "nrf.h"
+#include "wiring_constants.h"
+#include "wiring_digital.h"
+
+const uint32_t g_ADigitalPinMap[] = {
+    // P0
+    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+
+    // P1
+    32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
+
+void initVariant()
+{
+    // LED1 & LED2
+    pinMode(PIN_LED1, OUTPUT);
+    ledOff(PIN_LED1);
+
+    pinMode(PIN_LED2, OUTPUT);
+    ledOff(PIN_LED2);
+
+    // 3V3 Power Rail
+    pinMode(PIN_3V3_EN, OUTPUT);
+    digitalWrite(PIN_3V3_EN, HIGH);
+}
\ No newline at end of file
diff --git a/variants/wio-sdk-wm1110/variant.h b/variants/wio-sdk-wm1110/variant.h
new file mode 100644
index 000000000..f027b469f
--- /dev/null
+++ b/variants/wio-sdk-wm1110/variant.h
@@ -0,0 +1,111 @@
+/*
+  Copyright (c) 2014-2015 Arduino LLC.  All right reserved.
+  Copyright (c) 2016 Sandeep Mistry All right reserved.
+  Copyright (c) 2018, Adafruit Industries (adafruit.com)
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License as published by the Free Software Foundation; either
+  version 2.1 of the License, or (at your option) any later version.
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+  See the GNU Lesser General Public License for more details.
+  You should have received a copy of the GNU Lesser General Public
+  License along with this library; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#ifndef _VARIANT_WIO_SDK_WM1110_
+#define _VARIANT_WIO_SDK_WM1110_
+
+/** Master clock frequency */
+#define VARIANT_MCK (64000000ul)
+
+#define USE_LFXO // Board uses 32khz crystal for LF
+// define USE_LFRC    // Board uses RC for LF
+
+/*----------------------------------------------------------------------------
+ *        Headers
+ *----------------------------------------------------------------------------*/
+
+#include "WVariant.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+// Number of pins defined in PinDescription array
+#define PINS_COUNT (48)
+#define NUM_DIGITAL_PINS (48)
+#define NUM_ANALOG_INPUTS (6)
+#define NUM_ANALOG_OUTPUTS (0)
+
+#define WIRE_INTERFACES_COUNT 1
+
+#define PIN_3V3_EN (0 + 7) // P0.7, Power to Sensors
+
+#define PIN_WIRE_SDA (0 + 27) // P0.27
+#define PIN_WIRE_SCL (0 + 26) // P0.26
+
+#define PIN_LED1 (0 + 13) // P0.13
+#define PIN_LED2 (0 + 14) // P0.14
+
+#define LED_BUILTIN PIN_LED1
+
+#define LED_GREEN PIN_LED1
+#define LED_BLUE PIN_LED2 // Actually red
+
+#define LED_STATE_ON 1 // State when LED is lit
+
+#define BUTTON_PIN (0 + 23) // P0.23
+
+/*
+ * Serial interfaces
+ */
+#define PIN_SERIAL1_RX (0 + 22) // P0.22
+#define PIN_SERIAL1_TX (0 + 24) // P0.24
+
+#define PIN_SERIAL2_RX (0 + 6) // P0.06
+#define PIN_SERIAL2_TX (0 + 8) // P0.08
+
+#define SPI_INTERFACES_COUNT 1
+
+#define PIN_SPI_MISO (32 + 15) // P1.15 47
+#define PIN_SPI_MOSI (32 + 14) // P1.14 46
+#define PIN_SPI_SCK (32 + 13)  // P1.13 45
+#define PIN_SPI_NSS (32 + 12)  // P1.12 44
+
+#define LORA_RESET (32 + 10) // P1.10 42 // RST
+#define LORA_DIO1 (32 + 8)   // P1.08 40 // IRQ
+#define LORA_DIO2 (32 + 11)  // P1.11 43 // BUSY
+#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_LR1110
+
+#define LR1110_IRQ_PIN LORA_DIO1
+#define LR1110_NRESER_PIN LORA_RESET
+#define LR1110_BUSY_PIN LORA_DIO2
+#define LR1110_SPI_NSS_PIN LORA_CS
+#define LR1110_SPI_SCK_PIN LORA_SCK
+#define LR1110_SPI_MOSI_PIN LORA_MOSI
+#define LR1110_SPI_MISO_PIN LORA_MISO
+
+#define LR11X0_DIO3_TCXO_VOLTAGE 1.6
+#define LR11X0_DIO_AS_RF_SWITCH
+
+#define LR1110_GNSS_ANT_PIN (32 + 5) // P1.05 37
+
+#ifdef __cplusplus
+}
+#endif
+
+/*----------------------------------------------------------------------------
+ *        Arduino objects - C++ only
+ *----------------------------------------------------------------------------*/
+
+#endif // _VARIANT_WIO_SDK_WM1110_
diff --git a/variants/wio-tracker-wm1110/platformio.ini b/variants/wio-tracker-wm1110/platformio.ini
new file mode 100644
index 000000000..cba1b8741
--- /dev/null
+++ b/variants/wio-tracker-wm1110/platformio.ini
@@ -0,0 +1,14 @@
+; The very slick RAK wireless RAK 4631 / 4630 board - Unified firmware for 5005/19003, with or without OLED RAK 1921
+[env:wio-tracker-wm1110]
+extends = nrf52840_base
+board = wio-tracker-wm1110
+; platform = https://github.com/maxgerhardt/platform-nordicnrf52#cac6fcf943a41accd2aeb4f3659ae297a73f422e
+build_flags = ${nrf52840_base.build_flags} -Ivariants/wio-tracker-wm1110 -DWIO_WM1110
+  -L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard"
+  -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
+build_src_filter = ${nrf52_base.build_src_filter} +<../variants/wio-tracker-wm1110>
+lib_deps = 
+  ${nrf52840_base.lib_deps}
+debug_tool = jlink
+; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm)
+;upload_protocol = jlink
\ No newline at end of file
diff --git a/variants/wio-tracker-wm1110/variant.cpp b/variants/wio-tracker-wm1110/variant.cpp
new file mode 100644
index 000000000..5a3587982
--- /dev/null
+++ b/variants/wio-tracker-wm1110/variant.cpp
@@ -0,0 +1,45 @@
+/*
+  Copyright (c) 2014-2015 Arduino LLC.  All right reserved.
+  Copyright (c) 2016 Sandeep Mistry All right reserved.
+  Copyright (c) 2018, Adafruit Industries (adafruit.com)
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License as published by the Free Software Foundation; either
+  version 2.1 of the License, or (at your option) any later version.
+
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+  See the GNU Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with this library; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#include "variant.h"
+#include "nrf.h"
+#include "wiring_constants.h"
+#include "wiring_digital.h"
+
+const uint32_t g_ADigitalPinMap[] = {
+    // P0
+    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+
+    // P1
+    32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
+
+void initVariant()
+{
+    // LED1 & LED2
+    pinMode(PIN_LED1, OUTPUT);
+    ledOff(PIN_LED1);
+
+    pinMode(PIN_LED2, OUTPUT);
+    ledOff(PIN_LED2);
+
+    // 3V3 Power Rail
+    pinMode(PIN_3V3_EN, OUTPUT);
+    digitalWrite(PIN_3V3_EN, HIGH);
+}
\ No newline at end of file
diff --git a/variants/wio-tracker-wm1110/variant.h b/variants/wio-tracker-wm1110/variant.h
new file mode 100644
index 000000000..e929332e6
--- /dev/null
+++ b/variants/wio-tracker-wm1110/variant.h
@@ -0,0 +1,111 @@
+/*
+  Copyright (c) 2014-2015 Arduino LLC.  All right reserved.
+  Copyright (c) 2016 Sandeep Mistry All right reserved.
+  Copyright (c) 2018, Adafruit Industries (adafruit.com)
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License as published by the Free Software Foundation; either
+  version 2.1 of the License, or (at your option) any later version.
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+  See the GNU Lesser General Public License for more details.
+  You should have received a copy of the GNU Lesser General Public
+  License along with this library; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#ifndef _VARIANT_WIO_TRACKER_WM1110_
+#define _VARIANT_WIO_TRACKER_WM1110_
+
+/** Master clock frequency */
+#define VARIANT_MCK (64000000ul)
+
+#define USE_LFXO // Board uses 32khz crystal for LF
+// define USE_LFRC    // Board uses RC for LF
+
+/*----------------------------------------------------------------------------
+ *        Headers
+ *----------------------------------------------------------------------------*/
+
+#include "WVariant.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+// Number of pins defined in PinDescription array
+#define PINS_COUNT (48)
+#define NUM_DIGITAL_PINS (48)
+#define NUM_ANALOG_INPUTS (6)
+#define NUM_ANALOG_OUTPUTS (0)
+
+#define WIRE_INTERFACES_COUNT 1
+
+#define PIN_3V3_EN (32 + 1) // P1.01, Power to Sensors
+
+#define PIN_WIRE_SDA (0 + 5) // P0.05
+#define PIN_WIRE_SCL (0 + 4) // P0.04
+
+#define PIN_LED1 (0 + 6)      // P0.06
+#define PIN_LED2 (PINS_COUNT) // P0.14
+
+#define LED_BUILTIN PIN_LED1
+
+#define LED_GREEN PIN_LED1
+#define LED_BLUE PIN_LED2
+
+#define LED_STATE_ON 0
+
+#define BUTTON_PIN (32 + 2) // P1.02
+
+/*
+ * Serial interfaces
+ */
+#define PIN_SERIAL1_RX (0 + 24) // P0.24
+#define PIN_SERIAL1_TX (0 + 25) // P0.25
+
+#define PIN_SERIAL2_RX (0 + 6) // P0.06
+#define PIN_SERIAL2_TX (0 + 8) // P0.08
+
+#define SPI_INTERFACES_COUNT 1
+
+#define PIN_SPI_MISO (32 + 15) // P1.15 47
+#define PIN_SPI_MOSI (32 + 14) // P1.14 46
+#define PIN_SPI_SCK (32 + 13)  // P1.13 45
+#define PIN_SPI_NSS (32 + 12)  // P1.12 44
+
+#define LORA_RESET (0 + 18) // P0.18 18 // RST
+#define LORA_DIO1 (0 + 2)   // P0.02 2 // IRQ
+#define LORA_DIO2 (32 + 11) // P1.11 43 // BUSY
+#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_LR1110
+
+#define LR1110_IRQ_PIN LORA_DIO1
+#define LR1110_NRESER_PIN LORA_RESET
+#define LR1110_BUSY_PIN LORA_DIO2
+#define LR1110_SPI_NSS_PIN LORA_CS
+#define LR1110_SPI_SCK_PIN LORA_SCK
+#define LR1110_SPI_MOSI_PIN LORA_MOSI
+#define LR1110_SPI_MISO_PIN LORA_MISO
+
+#define LR11X0_DIO3_TCXO_VOLTAGE 1.6
+#define LR11X0_DIO_AS_RF_SWITCH
+
+#define LR1110_GNSS_ANT_PIN (32 + 5) // P1.05 37
+
+#ifdef __cplusplus
+}
+#endif
+
+/*----------------------------------------------------------------------------
+ *        Arduino objects - C++ only
+ *----------------------------------------------------------------------------*/
+
+#endif // _VARIANT_WIO_TRACKER_WM1110_