From 01208574d94f5d54e34979a66472b7e342bae7ca Mon Sep 17 00:00:00 2001 From: jgromes Date: Sat, 20 Apr 2024 21:29:32 +0200 Subject: [PATCH] [LR11x0] Added CAD support (#679) --- ...x0_Channel_Activity_Detection_Blocking.ino | 75 ++++++++++++ ...0_Channel_Activity_Detection_Interrupt.ino | 110 +++++++++++++++++ src/modules/LR11x0/LR11x0.cpp | 112 +++++++++++++++++- src/modules/LR11x0/LR11x0.h | 40 +++++++ 4 files changed, 335 insertions(+), 2 deletions(-) create mode 100644 examples/LR11x0/LR11x0_Channel_Activity_Detection_Blocking/LR11x0_Channel_Activity_Detection_Blocking.ino create mode 100644 examples/LR11x0/LR11x0_Channel_Activity_Detection_Interrupt/LR11x0_Channel_Activity_Detection_Interrupt.ino diff --git a/examples/LR11x0/LR11x0_Channel_Activity_Detection_Blocking/LR11x0_Channel_Activity_Detection_Blocking.ino b/examples/LR11x0/LR11x0_Channel_Activity_Detection_Blocking/LR11x0_Channel_Activity_Detection_Blocking.ino new file mode 100644 index 00000000..096c160e --- /dev/null +++ b/examples/LR11x0/LR11x0_Channel_Activity_Detection_Blocking/LR11x0_Channel_Activity_Detection_Blocking.ino @@ -0,0 +1,75 @@ +/* + RadioLib LR11x0 Blocking Channel Activity Detection Example + + This example uses LR1110 to scan the current LoRa + channel and detect ongoing LoRa transmissions. + Unlike SX127x CAD, LR11x0 can detect any part + of LoRa transmission, not just the preamble. + + Other modules from LR11x0 family can also be used. + + Using blocking CAD is not recommended, as it will lead + to significant amount of timeouts, inefficient use of processor + time and can some miss packets! + Instead, interrupt CAD is recommended. + + For default module settings, see the wiki page + https://github.com/jgromes/RadioLib/wiki/Default-configuration#lr11x0---lora-modem + + For full API reference, see the GitHub Pages + https://jgromes.github.io/RadioLib/ +*/ + +// include the library +#include + +// LR1110 has the following connections: +// NSS pin: 10 +// DIO1 pin: 2 +// NRST pin: 3 +// BUSY pin: 9 +LR1110 radio = new Module(10, 2, 3, 9); + +// or using RadioShield +// https://github.com/jgromes/RadioShield +//LR1110 radio = RadioShield.ModuleA; + +void setup() { + Serial.begin(9600); + + // initialize LR1110 with default settings + Serial.print(F("[LR1110] Initializing ... ")); + int state = radio.begin(); + if (state == RADIOLIB_ERR_NONE) { + Serial.println(F("success!")); + } else { + Serial.print(F("failed, code ")); + Serial.println(state); + while (true); + } +} + +void loop() { + Serial.print(F("[LR1110] Scanning channel for LoRa transmission ... ")); + + // start scanning current channel + int state = radio.scanChannel(); + + if (state == RADIOLIB_LORA_DETECTED) { + // LoRa preamble was detected + Serial.println(F("detected!")); + + } else if (state == RADIOLIB_CHANNEL_FREE) { + // no preamble was detected, channel is free + Serial.println(F("channel is free!")); + + } else { + // some other error occurred + Serial.print(F("failed, code ")); + Serial.println(state); + + } + + // wait 100 ms before new scan + delay(100); +} diff --git a/examples/LR11x0/LR11x0_Channel_Activity_Detection_Interrupt/LR11x0_Channel_Activity_Detection_Interrupt.ino b/examples/LR11x0/LR11x0_Channel_Activity_Detection_Interrupt/LR11x0_Channel_Activity_Detection_Interrupt.ino new file mode 100644 index 00000000..41b9c89a --- /dev/null +++ b/examples/LR11x0/LR11x0_Channel_Activity_Detection_Interrupt/LR11x0_Channel_Activity_Detection_Interrupt.ino @@ -0,0 +1,110 @@ +/* + RadioLib LR11x0 Channel Activity Detection Example + + This example uses LR1110 to scan the current LoRa + channel and detect ongoing LoRa transmissions. + Unlike SX127x CAD, LR11x0 can detect any part + of LoRa transmission, not just the preamble. + + Other modules from LR11x0 family can also be used. + + For default module settings, see the wiki page + https://github.com/jgromes/RadioLib/wiki/Default-configuration#lr11x0---lora-modem + + For full API reference, see the GitHub Pages + https://jgromes.github.io/RadioLib/ +*/ + +// include the library +#include + +// LR1110 has the following connections: +// NSS pin: 10 +// DIO1 pin: 2 +// NRST pin: 3 +// BUSY pin: 9 +LR1110 radio = new Module(10, 2, 3, 9); + +// or using RadioShield +// https://github.com/jgromes/RadioShield +//LR1110 radio = RadioShield.ModuleA; + +void setup() { + Serial.begin(9600); + + // initialize LR1110 with default settings + Serial.print(F("[LR1110] Initializing ... ")); + int state = radio.begin(); + if (state == RADIOLIB_ERR_NONE) { + Serial.println(F("success!")); + } else { + Serial.print(F("failed, code ")); + Serial.println(state); + while (true); + } + + // set the function that will be called + // when LoRa packet or timeout is detected + radio.setDio1Action(setFlag); + + // start scanning the channel + Serial.print(F("[LR1110] Starting scan for LoRa preamble ... ")); + state = radio.startChannelScan(); + if (state == RADIOLIB_ERR_NONE) { + Serial.println(F("success!")); + } else { + Serial.print(F("failed, code ")); + Serial.println(state); + } +} + +// flag to indicate that a packet was detected or CAD timed out +volatile bool scanFlag = false; + +// this function is called when a complete packet +// is received by the module +// IMPORTANT: this function MUST be 'void' type +// and MUST NOT have any arguments! +#if defined(ESP8266) || defined(ESP32) + ICACHE_RAM_ATTR +#endif +void setFlag(void) { + // something happened, set the flag + scanFlag = true; +} + +void loop() { + // check if the flag is set + if(scanFlag) { + // reset flag + scanFlag = false; + + // check CAD result + int state = radio.getChannelScanResult(); + + if (state == RADIOLIB_LORA_DETECTED) { + // LoRa packet was detected + Serial.println(F("[LR1110] Packet detected!")); + + } else if (state == RADIOLIB_CHANNEL_FREE) { + // channel is free + Serial.println(F("[LR1110] Channel is free!")); + + } else { + // some other error occurred + Serial.print(F("[LR1110] Failed, code ")); + Serial.println(state); + + } + + // start scanning the channel again + Serial.print(F("[LR1110] Starting scan for LoRa preamble ... ")); + state = radio.startChannelScan(); + if (state == RADIOLIB_ERR_NONE) { + Serial.println(F("success!")); + } else { + Serial.print(F("failed, code ")); + Serial.println(state); + } + } +} diff --git a/src/modules/LR11x0/LR11x0.cpp b/src/modules/LR11x0/LR11x0.cpp index 6272f2d6..ffae2c46 100644 --- a/src/modules/LR11x0/LR11x0.cpp +++ b/src/modules/LR11x0/LR11x0.cpp @@ -315,6 +315,24 @@ int16_t LR11x0::receiveDirect() { return(RADIOLIB_ERR_UNKNOWN); } +int16_t LR11x0::scanChannel() { + return(this->scanChannel(RADIOLIB_LR11X0_CAD_PARAM_DEFAULT, RADIOLIB_LR11X0_CAD_PARAM_DEFAULT, RADIOLIB_LR11X0_CAD_PARAM_DEFAULT)); +} + +int16_t LR11x0::scanChannel(uint8_t symbolNum, uint8_t detPeak, uint8_t detMin) { + // set mode to CAD + int state = startChannelScan(symbolNum, detPeak, detMin); + RADIOLIB_ASSERT(state); + + // wait for channel activity detected or timeout + while(!this->mod->hal->digitalRead(this->mod->getIrq())) { + this->mod->hal->yield(); + } + + // check CAD result + return(getChannelScanResult()); +} + int16_t LR11x0::standby() { return(LR11x0::standby(RADIOLIB_LR11X0_STANDBY_RC)); } @@ -539,6 +557,62 @@ int16_t LR11x0::readData(uint8_t* data, size_t len) { return(state); } +int16_t LR11x0::startChannelScan() { + return(this->startChannelScan(RADIOLIB_LR11X0_CAD_PARAM_DEFAULT, RADIOLIB_LR11X0_CAD_PARAM_DEFAULT, RADIOLIB_LR11X0_CAD_PARAM_DEFAULT)); +} + +int16_t LR11x0::startChannelScan(uint8_t symbolNum, uint8_t detPeak, uint8_t detMin) { + // check active modem + int16_t state = RADIOLIB_ERR_NONE; + uint8_t modem = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + state = getPacketType(&modem); + RADIOLIB_ASSERT(state); + if(modem != RADIOLIB_LR11X0_PACKET_TYPE_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // set mode to standby + state = standby(); + RADIOLIB_ASSERT(state); + + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_RX); + + // set DIO pin mapping + state = setDioIrqParams(RADIOLIB_LR11X0_IRQ_CAD_DETECTED | RADIOLIB_LR11X0_IRQ_CAD_DONE, RADIOLIB_LR11X0_IRQ_NONE); + RADIOLIB_ASSERT(state); + + // clear interrupt flags + state = clearIrq(RADIOLIB_LR11X0_IRQ_ALL); + RADIOLIB_ASSERT(state); + + // set mode to CAD + return(startCad(symbolNum, detPeak, detMin)); +} + +int16_t LR11x0::getChannelScanResult() { + // check active modem + int16_t state = RADIOLIB_ERR_NONE; + uint8_t modem = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + state = getPacketType(&modem); + RADIOLIB_ASSERT(state); + if(modem != RADIOLIB_LR11X0_PACKET_TYPE_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // check CAD result + uint32_t cadResult = getIrqStatus(); + if(cadResult & RADIOLIB_LR11X0_IRQ_CAD_DETECTED) { + // detected some LoRa activity + return(RADIOLIB_LORA_DETECTED); + } else if(cadResult & RADIOLIB_LR11X0_IRQ_CAD_DONE) { + // channel is free + return(RADIOLIB_CHANNEL_FREE); + } + + return(RADIOLIB_ERR_UNKNOWN); +} + int16_t LR11x0::setBandwidth(float bw) { // check active modem uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; @@ -1282,8 +1356,6 @@ int16_t LR11x0::config(uint8_t modem) { state = this->setRxTxFallbackMode(RADIOLIB_LR11X0_FALLBACK_MODE_STBY_RC); RADIOLIB_ASSERT(state); - // TODO set some CAD parameters - will be overwritten when calling CAD anyway - // clear IRQ state = this->clearIrq(RADIOLIB_LR11X0_IRQ_ALL); state |= this->setDioIrqParams(RADIOLIB_LR11X0_IRQ_NONE, RADIOLIB_LR11X0_IRQ_NONE); @@ -1332,6 +1404,42 @@ int16_t LR11x0::setPacketMode(uint8_t mode, uint8_t len) { return(state); } +int16_t LR11x0::startCad(uint8_t symbolNum, uint8_t detPeak, uint8_t detMin) { + // check active modem + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&type); + RADIOLIB_ASSERT(state); + if(type != RADIOLIB_LR11X0_PACKET_TYPE_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // select CAD parameters + // TODO the magic numbers are based on Semtech examples, this is probably suboptimal + uint8_t num = symbolNum; + if(num == RADIOLIB_LR11X0_CAD_PARAM_DEFAULT) { + num = 2; + } + + uint8_t detPeakValues[8] = { 48, 48, 50, 55, 55, 59, 61, 65 }; + uint8_t peak = detPeak; + if(peak == RADIOLIB_LR11X0_CAD_PARAM_DEFAULT) { + peak = detPeakValues[this->spreadingFactor - 5]; + } + + uint8_t min = detMin; + if(min == RADIOLIB_LR11X0_CAD_PARAM_DEFAULT) { + min = 10; + } + + // set CAD parameters + // TODO add configurable exit mode and timeout + state = setCadParams(num, peak, min, RADIOLIB_LR11X0_CAD_EXIT_MODE_STBY_RC, 0); + RADIOLIB_ASSERT(state); + + // start CAD + return(setCad()); +} + Module* LR11x0::getMod() { return(this->mod); } diff --git a/src/modules/LR11x0/LR11x0.h b/src/modules/LR11x0/LR11x0.h index d9340782..6955fa79 100644 --- a/src/modules/LR11x0/LR11x0.h +++ b/src/modules/LR11x0/LR11x0.h @@ -312,6 +312,7 @@ #define RADIOLIB_LR11X0_CAD_EXIT_MODE_STBY_RC (0x00UL << 0) // 7 0 mode to set after CAD: standby with RC #define RADIOLIB_LR11X0_CAD_EXIT_MODE_RX (0x01UL << 0) // 7 0 receive if activity detected #define RADIOLIB_LR11X0_CAD_EXIT_MODE_LBT (0x10UL << 0) // 7 0 transmit if no activity detected +#define RADIOLIB_LR11X0_CAD_PARAM_DEFAULT (0xFFUL << 0) // 7 0 used by the CAD methods to specify default parameter value // RADIOLIB_LR11X0_CMD_SET_PACKET_TYPE #define RADIOLIB_LR11X0_PACKET_TYPE_NONE (0x00UL << 0) // 2 0 packet type: none @@ -620,6 +621,21 @@ class LR11x0: public PhysicalLayer { */ int16_t receiveDirect() override; + /*! + \brief Performs scan for LoRa transmission in the current channel. Detects both preamble and payload. + \returns \ref status_codes + */ + int16_t scanChannel() override; + + /*! + \brief Performs scan for LoRa transmission in the current channel. Detects both preamble and payload. + \param symbolNum Number of symbols for CAD detection. + \param detPeak Peak value for CAD detection. + \param detMin Minimum value for CAD detection. + \returns \ref status_codes + */ + int16_t scanChannel(uint8_t symbolNum, uint8_t detPeak, uint8_t detMin); + /*! \brief Sets the module to standby mode (overload for PhysicalLayer compatibility, uses 13 MHz RC oscillator). \returns \ref status_codes @@ -731,6 +747,29 @@ class LR11x0: public PhysicalLayer { \returns \ref status_codes */ int16_t readData(uint8_t* data, size_t len) override; + + /*! + \brief Interrupt-driven channel activity detection method. DIO1 will be activated + when LoRa preamble is detected, or upon timeout. Defaults to CAD parameter values recommended by AN1200.48. + \returns \ref status_codes + */ + int16_t startChannelScan() override; + + /*! + \brief Interrupt-driven channel activity detection method. DIO1 will be activated + when LoRa preamble is detected, or upon timeout. + \param symbolNum Number of symbols for CAD detection. + \param detPeak Peak value for CAD detection. + \param detMin Minimum value for CAD detection. + \returns \ref status_codes + */ + int16_t startChannelScan(uint8_t symbolNum, uint8_t detPeak, uint8_t detMin); + + /*! + \brief Read the channel scan result + \returns \ref status_codes + */ + int16_t getChannelScanResult() override; // configuration methods @@ -1133,6 +1172,7 @@ class LR11x0: public PhysicalLayer { bool findChip(uint8_t ver); int16_t config(uint8_t modem); int16_t setPacketMode(uint8_t mode, uint8_t len); + int16_t startCad(uint8_t symbolNum, uint8_t detPeak, uint8_t detMin); // common methods to avoid some copy-paste int16_t bleBeaconCommon(uint16_t cmd, uint8_t chan, uint8_t* payload, size_t len);