From e65d9e8ccd6bb60c13cf35a56814ddc6efea281e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 17 Oct 2022 15:33:41 +0200 Subject: [PATCH] Add support for SX1281 on 2.4 GHz (#1809) * Add support for SX1281 on 2.4 GHz * only allow wide BW settings when the right chip is detected * portduino cannot use this chip yet as it uses an old modified version of radiolib * missed a spot * Attempt to supress false positive * Attempt to supress false positive * Trying casing from the cpp-check manual * Trying casing from the cpp-check manual * Inline suppr should be default but... * Maybe casting it will make the damn thing shut up Co-authored-by: Ben Meadors --- platformio.ini | 3 + src/main.cpp | 17 +++ src/main.h | 1 + src/mesh/InterfacesTemplates.cpp | 8 +- src/mesh/MeshRadio.h | 1 + src/mesh/RadioInterface.cpp | 54 ++++--- src/mesh/SX1281Interface.cpp | 13 ++ src/mesh/SX1281Interface.h | 17 +++ src/mesh/SX128xInterface.cpp | 247 +++++++++++++++++++++++++++++++ src/mesh/SX128xInterface.h | 75 ++++++++++ 10 files changed, 412 insertions(+), 24 deletions(-) create mode 100644 src/mesh/SX1281Interface.cpp create mode 100644 src/mesh/SX1281Interface.h create mode 100644 src/mesh/SX128xInterface.cpp create mode 100644 src/mesh/SX128xInterface.h diff --git a/platformio.ini b/platformio.ini index 01591153f..1f97cfb19 100644 --- a/platformio.ini +++ b/platformio.ini @@ -57,6 +57,9 @@ lib_deps = ; Used for the code analysis in PIO Home / Inspect check_tool = cppcheck check_skip_packages = yes +check_flags = + --common-flag + cppcheck: --enable=--inline-suppr ; Common settings for conventional (non Portduino) Arduino targets [arduino_base] diff --git a/src/main.cpp b/src/main.cpp index 3e233d4ee..104731ef3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -45,6 +45,7 @@ #include "RF95Interface.h" #include "SX1262Interface.h" #include "SX1268Interface.h" +#include "SX1281Interface.h" #if !HAS_RADIO && defined(ARCH_PORTDUINO) #include "platform/portduino/SimRadio.h" #endif @@ -80,6 +81,8 @@ uint8_t kb_model; // The I2C address of the RTC Module (if found) uint8_t rtc_found; +bool rIf_wide_lora = false; + // Keystore Chips uint8_t keystore_found; #ifndef ARCH_PORTDUINO @@ -364,6 +367,20 @@ void setup() } #endif +#if defined(USE_SX1281) && !defined(ARCH_PORTDUINO) + if (!rIf) { + rIf = new SX1281Interface(SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY, SPI); + if (!rIf->init()) { + DEBUG_MSG("Warning: Failed to find SX1281 radio\n"); + delete rIf; + rIf = NULL; + } else { + DEBUG_MSG("SX1281 Radio init succeeded, using SX1281 radio\n"); + rIf_wide_lora = true; + } + } +#endif + #if defined(USE_SX1262) if (!rIf) { rIf = new SX1262Interface(SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY, SPI); diff --git a/src/main.h b/src/main.h index 3744c8acd..bd24dec76 100644 --- a/src/main.h +++ b/src/main.h @@ -17,6 +17,7 @@ extern uint8_t kb_model; extern uint8_t rtc_found; extern uint8_t keystore_found; +extern bool rIf_wide_lora; extern bool eink_found; extern bool pmu_found; extern bool isCharging; diff --git a/src/mesh/InterfacesTemplates.cpp b/src/mesh/InterfacesTemplates.cpp index 2c8d9b290..6707813db 100644 --- a/src/mesh/InterfacesTemplates.cpp +++ b/src/mesh/InterfacesTemplates.cpp @@ -1,7 +1,13 @@ #include "SX126xInterface.h" #include "SX126xInterface.cpp" +#include "SX128xInterface.h" +#include "SX128xInterface.cpp" // We need this declaration for proper linking in derived classes template class SX126xInterface; template class SX126xInterface; -template class SX126xInterface; \ No newline at end of file +template class SX126xInterface; + +#if !defined(ARCH_PORTDUINO) +template class SX128xInterface; +#endif \ No newline at end of file diff --git a/src/mesh/MeshRadio.h b/src/mesh/MeshRadio.h index 961d62192..2cb7f4e6d 100644 --- a/src/mesh/MeshRadio.h +++ b/src/mesh/MeshRadio.h @@ -15,6 +15,7 @@ struct RegionInfo { uint8_t powerLimit; // Or zero for not set bool audioPermitted; bool freqSwitching; + bool wideLora; const char *name; // EU433 etc }; diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 9674f0c88..07bb01108 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -6,15 +6,16 @@ #include "Router.h" #include "assert.h" #include "configuration.h" +#include "main.h" #include "sleep.h" #include #include #include -#define RDEF(name, freq_start, freq_end, duty_cycle, spacing, power_limit, audio_permitted, frequency_switching) \ +#define RDEF(name, freq_start, freq_end, duty_cycle, spacing, power_limit, audio_permitted, frequency_switching, wide_lora) \ { \ Config_LoRaConfig_RegionCode_##name, freq_start, freq_end, duty_cycle, spacing, power_limit, audio_permitted, \ - frequency_switching, #name \ + frequency_switching, wide_lora, #name \ } const RegionInfo regions[] = { @@ -22,12 +23,12 @@ const RegionInfo regions[] = { https://link.springer.com/content/pdf/bbm%3A978-1-4842-4357-2%2F1.pdf https://www.thethingsnetwork.org/docs/lorawan/regional-parameters/ */ - RDEF(US, 902.0f, 928.0f, 100, 0, 30, true, false), + RDEF(US, 902.0f, 928.0f, 100, 0, 30, true, false, false), /* https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf */ - RDEF(EU_433, 433.0f, 434.0f, 10, 0, 12, true, false), + RDEF(EU_433, 433.0f, 434.0f, 10, 0, 12, true, false, false), /* https://www.thethingsnetwork.org/docs/lorawan/duty-cycle/ @@ -43,23 +44,23 @@ const RegionInfo regions[] = { (Please refer to section 4.21 in the following document) https://ec.europa.eu/growth/tools-databases/tris/index.cfm/ro/search/?trisaction=search.detail&year=2021&num=528&dLang=EN */ - RDEF(EU_868, 869.4f, 869.65f, 10, 0, 27, false, false), + RDEF(EU_868, 869.4f, 869.65f, 10, 0, 27, false, false, false), /* https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf */ - RDEF(CN, 470.0f, 510.0f, 100, 0, 19, true, false), + RDEF(CN, 470.0f, 510.0f, 100, 0, 19, true, false, false), /* https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf */ - RDEF(JP, 920.8f, 927.8f, 100, 0, 16, true, false), + RDEF(JP, 920.8f, 927.8f, 100, 0, 16, true, false, false), /* https://www.iot.org.au/wp/wp-content/uploads/2016/12/IoTSpectrumFactSheet.pdf https://iotalliance.org.nz/wp-content/uploads/sites/4/2019/05/IoT-Spectrum-in-NZ-Briefing-Paper.pdf */ - RDEF(ANZ, 915.0f, 928.0f, 100, 0, 30, true, false), + RDEF(ANZ, 915.0f, 928.0f, 100, 0, 30, true, false, false), /* https://digital.gov.ru/uploaded/files/prilozhenie-12-k-reshenyu-gkrch-18-46-03-1.pdf @@ -67,38 +68,44 @@ const RegionInfo regions[] = { Note: - We do LBT, so 100% is allowed. */ - RDEF(RU, 868.7f, 869.2f, 100, 0, 20, true, false), + RDEF(RU, 868.7f, 869.2f, 100, 0, 20, true, false, false), /* ??? */ - RDEF(KR, 920.0f, 923.0f, 100, 0, 0, true, false), + RDEF(KR, 920.0f, 923.0f, 100, 0, 0, true, false, false), /* ??? */ - RDEF(TW, 920.0f, 925.0f, 100, 0, 0, true, false), + RDEF(TW, 920.0f, 925.0f, 100, 0, 0, true, false, false), /* https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf */ - RDEF(IN, 865.0f, 867.0f, 100, 0, 30, true, false), + RDEF(IN, 865.0f, 867.0f, 100, 0, 30, true, false, false), /* https://rrf.rsm.govt.nz/smart-web/smart/page/-smart/domain/licence/LicenceSummary.wdk?id=219752 https://iotalliance.org.nz/wp-content/uploads/sites/4/2019/05/IoT-Spectrum-in-NZ-Briefing-Paper.pdf */ - RDEF(NZ_865, 864.0f, 868.0f, 100, 0, 0, true, false), + RDEF(NZ_865, 864.0f, 868.0f, 100, 0, 0, true, false, false), /* https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf */ - RDEF(TH, 920.0f, 925.0f, 100, 0, 16, true, false), + RDEF(TH, 920.0f, 925.0f, 100, 0, 16, true, false, false), + + /* + 2.4 GHZ WLAN Band equivalent. Only for SX128x chips. + */ + + RDEF(LORA_24, 2400.0f, 2483.5f, 100, 0, 10, true, false, true), /* This needs to be last. Same as US. */ - RDEF(UNSET, 902.0f, 928.0f, 100, 0, 30, true, false) + RDEF(UNSET, 902.0f, 928.0f, 100, 0, 30, true, false, false) }; @@ -355,39 +362,40 @@ void RadioInterface::applyModemConfig() // No Sync Words in LORA mode Config_LoRaConfig &loraConfig = config.lora; if (loraConfig.spread_factor == 0) { + switch (loraConfig.modem_preset) { case Config_LoRaConfig_ModemPreset_SHORT_FAST: - bw = 250; + bw = (myRegion->wideLora && rIf_wide_lora) ? 800 : 250; cr = 8; sf = 7; break; case Config_LoRaConfig_ModemPreset_SHORT_SLOW: - bw = 250; + bw = (myRegion->wideLora && rIf_wide_lora) ? 800 : 250; cr = 8; sf = 8; break; case Config_LoRaConfig_ModemPreset_MEDIUM_FAST: - bw = 250; + bw = (myRegion->wideLora && rIf_wide_lora) ? 800 : 250; cr = 8; sf = 9; break; case Config_LoRaConfig_ModemPreset_MEDIUM_SLOW: - bw = 250; + bw = (myRegion->wideLora && rIf_wide_lora) ? 800 : 250; cr = 8; sf = 10; break; case Config_LoRaConfig_ModemPreset_LONG_FAST: - bw = 250; + bw = (myRegion->wideLora && rIf_wide_lora) ? 800 : 250; cr = 8; sf = 11; break; case Config_LoRaConfig_ModemPreset_LONG_SLOW: - bw = 125; + bw = (myRegion->wideLora && rIf_wide_lora) ? 400 : 125; cr = 8; sf = 12; break; case Config_LoRaConfig_ModemPreset_VERY_LONG_SLOW: - bw = 31.25; + bw = (myRegion->wideLora && rIf_wide_lora) ? 200 : 31.25; cr = 8; sf = 12; break; @@ -415,7 +423,7 @@ void RadioInterface::applyModemConfig() power = 17; // Default to default power if we don't have a valid power // Set final tx_power back onto config - loraConfig.tx_power = power; + loraConfig.tx_power = (int8_t)power; // cppcheck-suppress assignmentAddressToInteger // Calculate the number of channels uint32_t numChannels = floor((myRegion->freqEnd - myRegion->freqStart) / (myRegion->spacing + (bw / 1000))); diff --git a/src/mesh/SX1281Interface.cpp b/src/mesh/SX1281Interface.cpp new file mode 100644 index 000000000..50805cfe0 --- /dev/null +++ b/src/mesh/SX1281Interface.cpp @@ -0,0 +1,13 @@ +#include "configuration.h" +#include "SX1281Interface.h" +#include "error.h" + +#if !defined(ARCH_PORTDUINO) + +SX1281Interface::SX1281Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy, + SPIClass &spi) + : SX128xInterface(cs, irq, rst, busy, spi) +{ +} + +#endif \ No newline at end of file diff --git a/src/mesh/SX1281Interface.h b/src/mesh/SX1281Interface.h new file mode 100644 index 000000000..3bd65309a --- /dev/null +++ b/src/mesh/SX1281Interface.h @@ -0,0 +1,17 @@ +#pragma once + +#include "SX128xInterface.h" + +/** + * Our adapter for SX1281 radios + */ + +#if !defined(ARCH_PORTDUINO) + +class SX1281Interface : public SX128xInterface +{ + public: + SX1281Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy, SPIClass &spi); +}; + +#endif \ No newline at end of file diff --git a/src/mesh/SX128xInterface.cpp b/src/mesh/SX128xInterface.cpp new file mode 100644 index 000000000..10d791847 --- /dev/null +++ b/src/mesh/SX128xInterface.cpp @@ -0,0 +1,247 @@ +#include "configuration.h" +#include "SX128xInterface.h" +#include "error.h" + +#if !defined(ARCH_PORTDUINO) + +// Particular boards might define a different max power based on what their hardware can do +#ifndef SX128X_MAX_POWER +#define SX128X_MAX_POWER 22 +#endif + +template +SX128xInterface::SX128xInterface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy, + SPIClass &spi) + : RadioLibInterface(cs, irq, rst, busy, spi, &lora), lora(&module) +{ +} + +/// Initialise the Driver transport hardware and software. +/// Make sure the Driver is properly configured before calling init(). +/// \return true if initialisation succeeded. +template +bool SX128xInterface::init() +{ +#ifdef SX128X_POWER_EN + digitalWrite(SX128X_POWER_EN, HIGH); + pinMode(SX128X_POWER_EN, OUTPUT); +#endif + +#ifdef SX128X_RXEN // set not rx or tx mode + digitalWrite(SX128X_RXEN, LOW); // Set low before becoming an output + pinMode(SX128X_RXEN, OUTPUT); +#endif +#ifdef SX128X_TXEN + digitalWrite(SX128X_TXEN, LOW); + pinMode(SX128X_TXEN, OUTPUT); +#endif + + RadioLibInterface::init(); + + if (power == 0) + power = SX128X_MAX_POWER; + + if (power > SX128X_MAX_POWER) // This chip has lower power limits than some + power = SX128X_MAX_POWER; + + limitPower(); + + int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength); + // \todo Display actual typename of the adapter, not just `SX128x` + DEBUG_MSG("SX128x init result %d\n", res); + + DEBUG_MSG("Frequency set to %f\n", getFreq()); + DEBUG_MSG("Bandwidth set to %f\n", bw); + DEBUG_MSG("Power output set to %d\n", power); + +#ifdef SX128X_TXEN + // lora.begin sets Dio2 as RF switch control, which is not true if we are manually controlling RX and TX + if (res == RADIOLIB_ERR_NONE) + res = lora.setDio2AsRfSwitch(true); +#endif + + if (res == RADIOLIB_ERR_NONE) + res = lora.setCRC(RADIOLIB_SX128X_LORA_CRC_ON); + + if (res == RADIOLIB_ERR_NONE) + startReceive(); // start receiving + + return res == RADIOLIB_ERR_NONE; +} + +template +bool SX128xInterface::reconfigure() +{ + RadioLibInterface::reconfigure(); + + // set mode to standby + setStandby(); + + // configure publicly accessible settings + int err = lora.setSpreadingFactor(sf); + if (err != RADIOLIB_ERR_NONE) + RECORD_CRITICALERROR(CriticalErrorCode_INVALID_RADIO_SETTING); + + err = lora.setBandwidth(bw); + if (err != RADIOLIB_ERR_NONE) + RECORD_CRITICALERROR(CriticalErrorCode_INVALID_RADIO_SETTING); + + err = lora.setCodingRate(cr); + if (err != RADIOLIB_ERR_NONE) + RECORD_CRITICALERROR(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(CriticalErrorCode_INVALID_RADIO_SETTING); + + if (power > 22) // This chip has lower power limits than some + power = 22; + err = lora.setOutputPower(power); + assert(err == RADIOLIB_ERR_NONE); + + startReceive(); // restart receiving + + return RADIOLIB_ERR_NONE; +} + +template +void INTERRUPT_ATTR SX128xInterface::disableInterrupt() +{ + lora.clearDio1Action(); +} + +template +void SX128xInterface::setStandby() +{ + checkNotification(); // handle any pending interrupts before we force standby + + int err = lora.standby(); + assert(err == RADIOLIB_ERR_NONE); + +#ifdef SX128X_RXEN // we have RXEN/TXEN control - turn off RX and TX power + digitalWrite(SX128X_RXEN, LOW); +#endif +#ifdef SX128X_TXEN + digitalWrite(SX128X_TXEN, LOW); +#endif + + isReceiving = false; // If we were receiving, not any more + disableInterrupt(); + completeSending(); // If we were sending, not anymore +} + +/** + * Add SNR data to received messages + */ +template +void SX128xInterface::addReceiveMetadata(MeshPacket *mp) +{ + // DEBUG_MSG("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 +void SX128xInterface::configHardwareForSend() +{ +#ifdef SX128X_TXEN // we have RXEN/TXEN control - turn on TX power / off RX power + digitalWrite(SX128X_TXEN, HIGH); +#endif +#ifdef SX128X_RXEN + digitalWrite(SX128X_RXEN, LOW); +#endif + + RadioLibInterface::configHardwareForSend(); +} + +// For power draw measurements, helpful to force radio to stay sleeping +// #define SLEEP_ONLY + +template +void SX128xInterface::startReceive() +{ +#ifdef SLEEP_ONLY + sleep(); +#else + + setStandby(); + +#ifdef SX128X_RXEN // we have RXEN/TXEN control - turn on RX power / off TX power + digitalWrite(SX128X_RXEN, HIGH); +#endif +#ifdef SX128X_TXEN + digitalWrite(SX128X_TXEN, LOW); +#endif + + int err = lora.startReceive(); + + 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 +} + +/** Could we send right now (i.e. either not actively receving or transmitting)? */ +template +bool SX128xInterface::isChannelActive() +{ + // check if we can detect a LoRa preamble on the current channel + int16_t result; + + setStandby(); + result = lora.scanChannel(); + if (result == RADIOLIB_PREAMBLE_DETECTED) + return true; + + assert(result != RADIOLIB_ERR_WRONG_MODEM); + + return false; +} + +/** Could we send right now (i.e. either not actively receving or transmitting)? */ +template +bool SX128xInterface::isActivelyReceiving() +{ + return isChannelActive(); +} + +template +bool SX128xInterface::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 `SX128x` + DEBUG_MSG("SX128x 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); // Note: we do not keep the config, full reinit will be needed + +#ifdef SX128X_POWER_EN + digitalWrite(SX128X_POWER_EN, LOW); +#endif + + return true; +} + +#endif \ No newline at end of file diff --git a/src/mesh/SX128xInterface.h b/src/mesh/SX128xInterface.h new file mode 100644 index 000000000..d01dfc510 --- /dev/null +++ b/src/mesh/SX128xInterface.h @@ -0,0 +1,75 @@ +#pragma once + +#if !defined(ARCH_PORTDUINO) + +#include "RadioLibInterface.h" + +/** + * \brief Adapter for SX128x radio family. Implements common logic for child classes. + * \tparam T RadioLib module type for SX128x: SX1281. + */ +template +class SX128xInterface : public RadioLibInterface +{ + public: + SX128xInterface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy, SPIClass &spi); + + /// 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; + + protected: + + float currentLimit = 140; // Higher OCP limit for SX128x PA + + /** + * 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.setDio1Action(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(MeshPacket *mp) override; + + virtual void setStandby() override; + + private: +}; + +#endif \ No newline at end of file