From 2ba68c9b6e96e679197087c17fc047fe0fa7e6ba Mon Sep 17 00:00:00 2001 From: Vladislav Osmanov <7123463+osmanovv@users.noreply.github.com> Date: Fri, 3 Sep 2021 17:15:58 +0300 Subject: [PATCH] added SX1268 module adapter --- src/main.cpp | 20 +++ src/mesh/SX1268Interface.cpp | 249 +++++++++++++++++++++++++++++++++++ src/mesh/SX1268Interface.h | 62 +++++++++ 3 files changed, 331 insertions(+) create mode 100644 src/mesh/SX1268Interface.cpp create mode 100644 src/mesh/SX1268Interface.h diff --git a/src/main.cpp b/src/main.cpp index a1de8dc6..273c7adc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -39,6 +39,7 @@ #include "RF95Interface.h" #include "SX1262Interface.h" +#include "SX1268Interface.h" #ifdef NRF52_SERIES #include "variant.h" @@ -497,6 +498,12 @@ void setup() digitalWrite(SX1262_ANT_SW, 1); #endif +#ifdef SX1268_ANT_SW + // make analog PA vs not PA switch on SX1268 eval board work properly + pinMode(SX1268_ANT_SW, OUTPUT); + digitalWrite(SX1268_ANT_SW, 1); +#endif + // radio init MUST BE AFTER service.init, so we have our radio config settings (from nodedb init) #if defined(RF95_IRQ) @@ -525,6 +532,19 @@ void setup() } #endif +#if defined(SX1268_CS) + if (!rIf) { + rIf = new SX1268Interface(SX1268_CS, SX1268_DIO1, SX1268_RESET, SX1268_BUSY, SPI); + if (!rIf->init()) { + DEBUG_MSG("Warning: Failed to find SX1268 radio\n"); + delete rIf; + rIf = NULL; + } else { + DEBUG_MSG("SX1268 Radio init succeeded, using SX1268 radio\n"); + } + } +#endif + #ifdef USE_SIM_RADIO if (!rIf) { rIf = new SimRadio; diff --git a/src/mesh/SX1268Interface.cpp b/src/mesh/SX1268Interface.cpp new file mode 100644 index 00000000..9d0df82c --- /dev/null +++ b/src/mesh/SX1268Interface.cpp @@ -0,0 +1,249 @@ +#include "configuration.h" +#include "SX1268Interface.h" +#include "error.h" + +// Particular boards might define a different max power based on what their hardware can do +#ifndef SX1268_MAX_POWER +#define SX1268_MAX_POWER 22 +#endif + +SX1268Interface::SX1268Interface(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. +bool SX1268Interface::init() +{ +#ifdef SX1268_POWER_EN + digitalWrite(SX1268_POWER_EN, HIGH); + pinMode(SX1268_POWER_EN, OUTPUT); +#endif + +#ifdef SX1268_RXEN // set not rx or tx mode + digitalWrite(SX1268_RXEN, LOW); // Set low before becoming an output + pinMode(SX1268_RXEN, OUTPUT); +#endif +#ifdef SX1268_TXEN + digitalWrite(SX1268_TXEN, LOW); + pinMode(SX1268_TXEN, OUTPUT); +#endif + +#ifndef SX1268_E22 + float tcxoVoltage = 0; // None - we use an XTAL +#else + // Use DIO3 to power tcxo per https://github.com/jgromes/RadioLib/issues/12#issuecomment-520695575 + float tcxoVoltage = 1.8; +#endif + bool useRegulatorLDO = false; // Seems to depend on the connection to pin 9/DCC_SW - if an inductor DCDC? + + RadioLibInterface::init(); + + if (power == 0) + power = SX1268_MAX_POWER; + + if (power > SX1268_MAX_POWER) // This chip has lower power limits than some + power = SX1268_MAX_POWER; + + limitPower(); + + int res = lora.begin(freq, bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage, useRegulatorLDO); + DEBUG_MSG("SX1268 init result %d\n", res); + +#ifdef SX1268_TXEN + // lora.begin sets Dio2 as RF switch control, which is not true if we are manually controlling RX and TX + if (res == ERR_NONE) + res = lora.setDio2AsRfSwitch(false); +#endif + +#if 0 + // Read/write a register we are not using (only used for FSK mode) to test SPI comms + uint8_t crcLSB = 0; + int err = lora.readRegister(SX126X_REG_CRC_POLYNOMIAL_LSB, &crcLSB, 1); + if(err != ERR_NONE) + RECORD_CRITICALERROR(CriticalErrorCode_SX1268Failure); + + //if(crcLSB != 0x0f) + // RECORD_CRITICALERROR(CriticalErrorCode_SX1268Failure); + + crcLSB = 0x5a; + err = lora.writeRegister(SX126X_REG_CRC_POLYNOMIAL_LSB, &crcLSB, 1); + if(err != ERR_NONE) + RECORD_CRITICALERROR(CriticalErrorCode_SX1268Failure); + + err = lora.readRegister(SX126X_REG_CRC_POLYNOMIAL_LSB, &crcLSB, 1); + if(err != ERR_NONE) + RECORD_CRITICALERROR(CriticalErrorCode_SX1268Failure); + + if(crcLSB != 0x5a) + RECORD_CRITICALERROR(CriticalErrorCode_SX1268Failure); + // If we got this far register accesses (and therefore SPI comms) are good +#endif + + if (res == ERR_NONE) + res = lora.setCRC(SX126X_LORA_CRC_ON); + + if (res == ERR_NONE) + startReceive(); // start receiving + + return res == ERR_NONE; +} + +bool SX1268Interface::reconfigure() +{ + RadioLibInterface::reconfigure(); + + // set mode to standby + setStandby(); + + // configure publicly accessible settings + int err = lora.setSpreadingFactor(sf); + if (err != ERR_NONE) + RECORD_CRITICALERROR(CriticalErrorCode_InvalidRadioSetting); + + err = lora.setBandwidth(bw); + if (err != ERR_NONE) + RECORD_CRITICALERROR(CriticalErrorCode_InvalidRadioSetting); + + err = lora.setCodingRate(cr); + if (err != ERR_NONE) + RECORD_CRITICALERROR(CriticalErrorCode_InvalidRadioSetting); + + // Hmm - seems to lower SNR when the signal levels are high. Leaving off for now... + err = lora.setRxGain(true); + assert(err == ERR_NONE); + + err = lora.setSyncWord(syncWord); + assert(err == ERR_NONE); + + err = lora.setCurrentLimit(currentLimit); + assert(err == ERR_NONE); + + err = lora.setPreambleLength(preambleLength); + assert(err == ERR_NONE); + + err = lora.setFrequency(freq); + if (err != ERR_NONE) + RECORD_CRITICALERROR(CriticalErrorCode_InvalidRadioSetting); + + if (power > 22) // This chip has lower power limits than some + power = 22; + err = lora.setOutputPower(power); + assert(err == ERR_NONE); + + startReceive(); // restart receiving + + return ERR_NONE; +} + +void INTERRUPT_ATTR SX1268Interface::disableInterrupt() +{ + lora.clearDio1Action(); +} + +void SX1268Interface::setStandby() +{ + int err = lora.standby(); + assert(err == ERR_NONE); + +#ifdef SX1268_RXEN // we have RXEN/TXEN control - turn off RX and TX power + digitalWrite(SX1268_RXEN, LOW); +#endif +#ifdef SX1268_TXEN + digitalWrite(SX1268_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 + */ +void SX1268Interface::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. + */ +void SX1268Interface::configHardwareForSend() +{ +#ifdef SX1268_TXEN // we have RXEN/TXEN control - turn on TX power / off RX power + digitalWrite(SX1268_TXEN, HIGH); +#endif + + RadioLibInterface::configHardwareForSend(); +} + +// For power draw measurements, helpful to force radio to stay sleeping +// #define SLEEP_ONLY + +void SX1268Interface::startReceive() +{ +#ifdef SLEEP_ONLY + sleep(); +#else + + setStandby(); + +#ifdef SX1268_RXEN // we have RXEN/TXEN control - turn on RX power / off TX power + digitalWrite(SX1268_RXEN, HIGH); +#endif + + // int err = lora.startReceive(); + int err = lora.startReceiveDutyCycleAuto(); // We use a 32 bit preamble so this should save some power by letting radio sit in + // standby mostly. + assert(err == 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)? */ +bool SX1268Interface::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. + // FIXME: it would be better to check for preamble, but we currently have our ISR not set to fire for packets that + // never even get a valid header, so we don't want preamble to get set and stay set due to noise on the network. + + uint16_t irq = lora.getIrqStatus(); + bool hasPreamble = (irq & SX126X_IRQ_HEADER_VALID); + + // this is not correct - often always true - need to add an extra conditional + // size_t bytesPending = lora.getPacketLength(); + + // if (hasPreamble) DEBUG_MSG("rx hasPreamble\n"); + return hasPreamble; +} + +bool SX1268Interface::sleep() +{ + // Not keeping config is busted - next time nrf52 board boots lora sending fails tcxo related? - see datasheet + DEBUG_MSG("SX1268 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 SX1268_POWER_EN + digitalWrite(SX1268_POWER_EN, LOW); +#endif + + return true; +} \ No newline at end of file diff --git a/src/mesh/SX1268Interface.h b/src/mesh/SX1268Interface.h new file mode 100644 index 00000000..33f696c9 --- /dev/null +++ b/src/mesh/SX1268Interface.h @@ -0,0 +1,62 @@ +#pragma once + +#include "RadioLibInterface.h" + +/** + * Our adapter for SX1268 radios + */ +class SX1268Interface : public RadioLibInterface +{ + SX1268 lora; + + public: + SX1268Interface(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(); + + /// Apply any radio provisioning changes + /// Make sure the Driver is properly configured before calling init(). + /// \return true if initialisation succeeded. + virtual bool reconfigure(); + + /// Prepare hardware for sleep. Call this _only_ for deep sleep, not needed for light sleep. + virtual bool sleep(); + + bool isIRQPending() { return lora.getIrqStatus() != 0; } + + protected: + /** + * Glue functions called from ISR land + */ + virtual void disableInterrupt(); + + /** + * Enable a particular ISR callback glue function + */ + virtual void enableInterrupt(void (*callback)()) { lora.setDio1Action(callback); } + + /** are we actively receiving a packet (only called during receiving state) */ + virtual bool isActivelyReceiving(); + + /** + * Start waiting to receive a message + */ + virtual void startReceive(); + + /** + * We override to turn on transmitter power as needed. + */ + virtual void configHardwareForSend(); + + /** + * Add SNR data to received messages + */ + virtual void addReceiveMetadata(MeshPacket *mp); + + virtual void setStandby(); + + private: +}; \ No newline at end of file