From 3cc299d17be4ea6a3ea2a6e33f57ccf72839bc33 Mon Sep 17 00:00:00 2001 From: Nathan Seidle Date: Thu, 13 Jan 2022 12:25:02 -0700 Subject: [PATCH] Add FHSS support --- .../SX127x_Receive_FHSS.ino | 122 +++++++++++++++++ .../SX127x_Transmit_FHSS.ino | 124 ++++++++++++++++++ keywords.txt | 4 + src/modules/SX127x/SX1276.cpp | 22 ++++ src/modules/SX127x/SX1276.h | 28 ++++ src/modules/SX127x/SX127x.cpp | 17 ++- 6 files changed, 313 insertions(+), 4 deletions(-) create mode 100644 examples/SX127x/SX127x_Receive_FHSS/SX127x_Receive_FHSS.ino create mode 100644 examples/SX127x/SX127x_Transmit_FHSS/SX127x_Transmit_FHSS.ino diff --git a/examples/SX127x/SX127x_Receive_FHSS/SX127x_Receive_FHSS.ino b/examples/SX127x/SX127x_Receive_FHSS/SX127x_Receive_FHSS.ino new file mode 100644 index 00000000..d45bec3b --- /dev/null +++ b/examples/SX127x/SX127x_Receive_FHSS/SX127x_Receive_FHSS.ino @@ -0,0 +1,122 @@ +/* + RadioLib SX127x Transmit with Frequency Hopping Example + + This example transmits packets using SX1278 LoRa radio module. + Each packet contains up to 256 bytes of data, in the form of: + - Arduino String + - null-terminated char array (C-string) + - arbitrary binary data (byte array) + + Other modules from SX127x/RFM9x family can also be used. + + For default module settings, see the wiki page + https://github.com/jgromes/RadioLib/wiki/Default-configuration#sx127xrfm9x---lora-modem + + For full API reference, see the GitHub Pages + https://jgromes.github.io/RadioLib/ + + The SX1276 / 7 / 8 / 9 supports FHSS or Frequency Hopping Spread Spectrum. + Once a hopping period is set and a transmission is started the radio + will begin triggering interrupts every hop period where the radio frequency + is changed to the next channel. This allows a simple mechanism to abide by + the FCC 400ms max dwell time rule. + https://www.govinfo.gov/content/pkg/CFR-2019-title47-vol1/pdf/CFR-2019-title47-vol1-sec15-247.pdf +*/ + +#include //Click here to get the library: http://librarymanager/All#RadioLib + +//Pins for RFM97 100mW Shield to SparkFun ESP32 Thing Plus C +int pin_cs = 15; +int pin_dio0 = 26; +int pin_dio1 = 25; +int pin_rst = 32; +SX1276 radio = new Module(pin_cs, pin_dio0, pin_rst, pin_dio1); + +int counter = 0; + +volatile bool rxComplete = false; +volatile bool fhssChange = false; + +//The channel frequencies can be generated randomly or hard coded +float channels[] = {908.0, 906.0, 907.0, 905.0, 903.0, 910.0, 909.0}; +int numberOfChannels = sizeof(channels) / sizeof(float); + +int hopsCompleted = 0; + +void setup() +{ + Serial.begin(115200); + + //Begin radio on home channel + Serial.print(F("[SX127x] Initializing ... ")); + int state = radio.begin(channels[0]); + if (state != RADIOLIB_ERR_NONE) + { + Serial.print(F("Failed with code: ")); + Serial.println(state); + } + else + Serial.println(F("Success!")); + + // Set hop period to enable FHSS + // We set an artifically short period to show lots of hops + // HoppingPeriod = Tsym * FreqHoppingPeriod + // Given defaults of spreadfactor = 9, bandwidth = 125, it follows Tsym = 4.10ms + // HoppingPeriod = 4.10 * 9 = 36.9ms. Can be as high as 400ms to be within regulatory limits + radio.setFHSSHoppingPeriod(9); + + Serial.print(F("Hopping period: ")); + Serial.println(radio.getFHSSHoppingPeriod()); + + radio.setDio0Action(dio0ISR); //Called when transmission is finished + radio.setDio1Action(dio1ISR); //Called after a transmission has started, so we can move to next freq + + radio.startReceive(); + + Serial.println(F("Waiting for new packet")); +} + +void loop() +{ + if (rxComplete == true) + { + uint8_t incomingBuffer[255]; + radio.readData(incomingBuffer, 255); + uint8_t receivedBytes = radio.getPacketLength(); + Serial.write(incomingBuffer, receivedBytes); + Serial.println(); + + Serial.print(F("Hops completed: ")); + Serial.println(hopsCompleted); + hopsCompleted = 0; + + radio.startReceive(); + + rxComplete = false; + } + + if (fhssChange == true) + { + radio.setFrequency(channels[radio.getFHSSChannel() % numberOfChannels]); + //Serial.print(F("Radio on channel: ")); + //Serial.println(radio.getFHSSChannel()); + + hopsCompleted++; + radio.clearFHSSInt(); + fhssChange = false; + } +} + +//ISR when DIO0 goes low +//Called when transmission is complete or when RX is received +void dio0ISR(void) +{ + rxComplete = true; +} + +//ISR when DIO1 goes low +//Called when FhssChangeChannel interrupt occurs (at the beginning of each transmission) +void dio1ISR(void) +{ + fhssChange = true; +} diff --git a/examples/SX127x/SX127x_Transmit_FHSS/SX127x_Transmit_FHSS.ino b/examples/SX127x/SX127x_Transmit_FHSS/SX127x_Transmit_FHSS.ino new file mode 100644 index 00000000..504852e0 --- /dev/null +++ b/examples/SX127x/SX127x_Transmit_FHSS/SX127x_Transmit_FHSS.ino @@ -0,0 +1,124 @@ +/* + RadioLib SX127x Transmit with Frequency Hopping Example + + This example transmits packets using SX1278 LoRa radio module. + Each packet contains up to 256 bytes of data, in the form of: + - Arduino String + - null-terminated char array (C-string) + - arbitrary binary data (byte array) + + Other modules from SX127x/RFM9x family can also be used. + + For default module settings, see the wiki page + https://github.com/jgromes/RadioLib/wiki/Default-configuration#sx127xrfm9x---lora-modem + + For full API reference, see the GitHub Pages + https://jgromes.github.io/RadioLib/ + + The SX1276 / 7 / 8 / 9 supports FHSS or Frequency Hopping Spread Spectrum. + Once a hopping period is set and a transmission is started the radio + will begin triggering interrupts every hop period where the radio frequency + is changed to the next channel. This allows a simple mechanism to abide by + the FCC 400ms max dwell time rule. + https://www.govinfo.gov/content/pkg/CFR-2019-title47-vol1/pdf/CFR-2019-title47-vol1-sec15-247.pdf +*/ + +#include //Click here to get the library: http://librarymanager/All#RadioLib + +//Pins for SparkFun 1W EBYTE Breakout to Uno +int pin_cs = 7; +int pin_dio0 = 3; +int pin_dio1 = 2; +int pin_rst = A2; +SX1276 radio = new Module(pin_cs, pin_dio0, pin_rst, pin_dio1); + +volatile bool xmitComplete = false; +volatile bool fhssChange = false; + +//The channel frequencies can be generated randomly or hard coded +float channels[] = {908.0, 906.0, 907.0, 905.0, 903.0, 910.0, 909.0}; +int numberOfChannels = sizeof(channels) / sizeof(float); + +int hopsCompleted = 0; +int counter = 0; + +void setup() +{ + Serial.begin(115200); + + //Begin radio on home channel + Serial.print(F("[SX127x] Initializing ... ")); + int state = radio.begin(channels[0]); + if (state != RADIOLIB_ERR_NONE) + { + Serial.print(F("Failed with code: ")); + Serial.println(state); + } + else + Serial.println(F("Success!")); + + // Set hop period to enable FHSS + // We set an artifically short period to show lots of hops + // HoppingPeriod = Tsym * FreqHoppingPeriod + // Given defaults of spreadfactor = 9, bandwidth = 125, it follows Tsym = 4.10ms + // HoppingPeriod = 4.10 * 9 = 36.9ms. Can be as high as 400ms to be within regulatory limits + radio.setFHSSHoppingPeriod(9); + + Serial.print(F("Hopping period: ")); + Serial.println(radio.getFHSSHoppingPeriod()); + + radio.setDio0Action(dio0ISR); //Called when transmission is finished + radio.setDio1Action(dio1ISR); //Called after a transmission has started, so we can move to next freq + + Serial.print(F("Transmitting packet...")); + + char output[256]; + sprintf(output, "Let's create a really long packet to trigger lots of hop interrupts. A packet can be up to 256 bytes long. This packet is 222 bytes so using sf = 9, bw = 125, timeOnAir is 1488ms. 1488ms / (9*4.10ms) = 40 hops. Counter: %d", counter++); + + radio.startTransmit(output, strlen(output) - 1); +} + +void loop() +{ + if (xmitComplete == true) + { + xmitComplete = false; + Serial.println(F("Transmit complete")); + Serial.print(F("Radio after xmit is on channel: ")); + Serial.println(radio.getFHSSChannel()); + //The FHSS channel is automatically reset to 0 upon end of transmission + + radio.setFrequency(channels[radio.getFHSSChannel() % numberOfChannels]); //Return to home channel before next transaction + + Serial.print(F("Hops completed: ")); + Serial.println(hopsCompleted); + hopsCompleted = 0; + + radio.startReceive(); + } + + if (fhssChange == true) + { + radio.setFrequency(channels[radio.getFHSSChannel() % numberOfChannels]); + //Serial.print(F("Radio on channel: ")); + //Serial.println(radio.getFHSSChannel()); + + hopsCompleted++; + fhssChange = false; + radio.clearFHSSInt(); + } +} + +//ISR when DIO0 goes low +//Called when transmission is complete or when RX is received +void dio0ISR(void) +{ + xmitComplete = true; +} + +//ISR when DIO1 goes low +//Called when FhssChangeChannel interrupt occurs (at regular HoppingPeriods) +void dio1ISR(void) +{ + fhssChange = true; +} diff --git a/keywords.txt b/keywords.txt index 9167c2a6..31db163d 100644 --- a/keywords.txt +++ b/keywords.txt @@ -142,6 +142,10 @@ setDirectAction KEYWORD2 readBit KEYWORD2 enableBitSync KEYWORD2 disableBitSync KEYWORD2 +setFHSSHoppingPeriod KEYWORD2 +getFHSSHoppingPeriod KEYWORD2 +getFHSSChannel KEYWORD2 +clearFHSSInt KEYWORD2 # RF69-specific setAESKey KEYWORD2 diff --git a/src/modules/SX127x/SX1276.cpp b/src/modules/SX127x/SX1276.cpp index c4c32f9c..fa1b46b8 100644 --- a/src/modules/SX127x/SX1276.cpp +++ b/src/modules/SX127x/SX1276.cpp @@ -70,4 +70,26 @@ int16_t SX1276::setFrequency(float freq) { return(state); } +int16_t SX1276::setFHSSHoppingPeriod(uint8_t freqHoppingPeriod) { + return(_mod->SPIsetRegValue(RADIOLIB_SX127X_REG_HOP_PERIOD, freqHoppingPeriod)); +} + +uint8_t SX1276::getFHSSHoppingPeriod(void) { + return(_mod->SPIgetRegValue(RADIOLIB_SX127X_REG_HOP_PERIOD)); +} + +uint8_t SX1276::getFHSSChannel(void) { + return(_mod->SPIgetRegValue(RADIOLIB_SX127X_REG_HOP_CHANNEL, 5, 0)); +} + +void SX1276::clearFHSSInt(void) { + int16_t modem = getActiveModem(); + if(modem == RADIOLIB_SX127X_LORA) { + _mod->SPIwriteRegister(RADIOLIB_SX127X_REG_IRQ_FLAGS, getIRQFlags() | RADIOLIB_SX127X_CLEAR_IRQ_FLAG_FHSS_CHANGE_CHANNEL); + } else if(modem == RADIOLIB_SX127X_FSK_OOK) { + return; //These are not the interrupts you are looking for + } +} + + #endif diff --git a/src/modules/SX127x/SX1276.h b/src/modules/SX127x/SX1276.h index cdc4cac5..15006ce3 100644 --- a/src/modules/SX127x/SX1276.h +++ b/src/modules/SX127x/SX1276.h @@ -84,6 +84,34 @@ class SX1276: public SX1278 { */ int16_t setFrequency(float freq); + /*! + \brief Sets the hopping period and enables FHSS + + \param freqHoppingPeriod Integer multiple of symbol periods between hops + + \returns \ref status_codes + */ + int16_t setFHSSHoppingPeriod(uint8_t freqHoppingPeriod); + + /*! + \brief Gets FHSS hopping period + + \returns 8 bit period + */ + uint8_t getFHSSHoppingPeriod(void); + + /*! + \brief Gets the FHSS channel in use + + \returns 6 bit channel number + */ + uint8_t getFHSSChannel(void); + + /*! + \brief Clear the FHSS interrupt + */ + void clearFHSSInt(void); + #if !defined(RADIOLIB_GODMODE) private: #endif diff --git a/src/modules/SX127x/SX127x.cpp b/src/modules/SX127x/SX127x.cpp index 24323447..9b099bbc 100644 --- a/src/modules/SX127x/SX127x.cpp +++ b/src/modules/SX127x/SX127x.cpp @@ -375,7 +375,10 @@ int16_t SX127x::startReceive(uint8_t len, uint8_t mode) { int16_t modem = getActiveModem(); if(modem == RADIOLIB_SX127X_LORA) { // set DIO pin mapping - state = _mod->SPIsetRegValue(RADIOLIB_SX127X_REG_DIO_MAPPING_1, RADIOLIB_SX127X_DIO0_RX_DONE | RADIOLIB_SX127X_DIO1_RX_TIMEOUT, 7, 4); + if(_mod->SPIgetRegValue(RADIOLIB_SX127X_REG_HOP_PERIOD) > RADIOLIB_SX127X_HOP_PERIOD_OFF) + state = _mod->SPIsetRegValue(RADIOLIB_SX127X_REG_DIO_MAPPING_1, RADIOLIB_SX127X_DIO0_RX_DONE | RADIOLIB_SX127X_DIO1_FHSS_CHANGE_CHANNEL, 7, 4); + else + state = _mod->SPIsetRegValue(RADIOLIB_SX127X_REG_DIO_MAPPING_1, RADIOLIB_SX127X_DIO0_RX_DONE | RADIOLIB_SX127X_DIO1_RX_TIMEOUT, 7, 4); // set expected packet length for SF6 if(_sf == 6) { @@ -448,7 +451,10 @@ int16_t SX127x::startTransmit(uint8_t* data, size_t len, uint8_t addr) { } // set DIO mapping - _mod->SPIsetRegValue(RADIOLIB_SX127X_REG_DIO_MAPPING_1, RADIOLIB_SX127X_DIO0_TX_DONE, 7, 6); + if(_mod->SPIgetRegValue(RADIOLIB_SX127X_REG_HOP_PERIOD) > RADIOLIB_SX127X_HOP_PERIOD_OFF) + _mod->SPIsetRegValue(RADIOLIB_SX127X_REG_DIO_MAPPING_1, RADIOLIB_SX127X_DIO0_TX_DONE | RADIOLIB_SX127X_DIO1_FHSS_CHANGE_CHANNEL, 7, 4); + else + _mod->SPIsetRegValue(RADIOLIB_SX127X_REG_DIO_MAPPING_1, RADIOLIB_SX127X_DIO0_TX_DONE, 7, 6); // apply fixes to errata RADIOLIB_ERRATA_SX127X(false); @@ -987,8 +993,11 @@ int16_t SX127x::setOOK(bool enableOOK) { } int16_t SX127x::setFrequencyRaw(float newFreq) { - // set mode to standby - int16_t state = setMode(RADIOLIB_SX127X_STANDBY); + int16_t state = RADIOLIB_ERR_NONE; + + // set mode to standby if not FHSS + if(_mod->SPIgetRegValue(RADIOLIB_SX127X_REG_HOP_PERIOD) == RADIOLIB_SX127X_HOP_PERIOD_OFF) + state = setMode(RADIOLIB_SX127X_STANDBY); // calculate register values uint32_t FRF = (newFreq * (uint32_t(1) << RADIOLIB_SX127X_DIV_EXPONENT)) / RADIOLIB_SX127X_CRYSTAL_FREQ;