LCOV - code coverage report
Current view: top level - src/modules/SX126x - SX126x.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 47 800 5.9 %
Date: 2025-10-24 15:14:50 Functions: 2 62 3.2 %

          Line data    Source code
       1             : #include "SX126x.h"
       2             : #include <string.h>
       3             : #include <math.h>
       4             : #if !RADIOLIB_EXCLUDE_SX126X
       5             : 
       6          15 : SX126x::SX126x(Module* mod) : PhysicalLayer() {
       7          15 :   this->freqStep = RADIOLIB_SX126X_FREQUENCY_STEP_SIZE;
       8          15 :   this->maxPacketLength = RADIOLIB_SX126X_MAX_PACKET_LENGTH;
       9          15 :   this->mod = mod;
      10          15 :   this->XTAL = false;
      11          15 :   this->standbyXOSC = false;
      12          15 :   this->irqMap[RADIOLIB_IRQ_TX_DONE] = RADIOLIB_SX126X_IRQ_TX_DONE;
      13          15 :   this->irqMap[RADIOLIB_IRQ_RX_DONE] = RADIOLIB_SX126X_IRQ_RX_DONE;
      14          15 :   this->irqMap[RADIOLIB_IRQ_PREAMBLE_DETECTED] = RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED;
      15          15 :   this->irqMap[RADIOLIB_IRQ_SYNC_WORD_VALID] = RADIOLIB_SX126X_IRQ_SYNC_WORD_VALID;
      16          15 :   this->irqMap[RADIOLIB_IRQ_HEADER_VALID] = RADIOLIB_SX126X_IRQ_HEADER_VALID;
      17          15 :   this->irqMap[RADIOLIB_IRQ_HEADER_ERR] = RADIOLIB_SX126X_IRQ_HEADER_ERR;
      18          15 :   this->irqMap[RADIOLIB_IRQ_CRC_ERR] = RADIOLIB_SX126X_IRQ_CRC_ERR;
      19          15 :   this->irqMap[RADIOLIB_IRQ_CAD_DONE] = RADIOLIB_SX126X_IRQ_CAD_DONE;
      20          15 :   this->irqMap[RADIOLIB_IRQ_CAD_DETECTED] = RADIOLIB_SX126X_IRQ_CAD_DETECTED;
      21          15 :   this->irqMap[RADIOLIB_IRQ_TIMEOUT] = RADIOLIB_SX126X_IRQ_TIMEOUT;
      22          15 : }
      23             : 
      24           0 : int16_t SX126x::begin(uint8_t cr, uint8_t syncWord, uint16_t preambleLength, float tcxoVoltage, bool useRegulatorLDO) {
      25             :   // BW in kHz and SF are required in order to calculate LDRO for setModulationParams
      26             :   // set the defaults, this will get overwritten later anyway
      27           0 :   this->bandwidthKhz = 500.0;
      28           0 :   this->spreadingFactor = 9;
      29             : 
      30             :   // initialize configuration variables (will be overwritten during public settings configuration)
      31           0 :   this->bandwidth = RADIOLIB_SX126X_LORA_BW_500_0;  // initialized to 500 kHz, since lower values will interfere with LLCC68
      32           0 :   this->codingRate = RADIOLIB_SX126X_LORA_CR_4_7;
      33           0 :   this->ldrOptimize = 0x00;
      34           0 :   this->crcTypeLoRa = RADIOLIB_SX126X_LORA_CRC_ON;
      35           0 :   this->preambleLengthLoRa = preambleLength;
      36           0 :   this->tcxoDelay = 0;
      37           0 :   this->headerType = RADIOLIB_SX126X_LORA_HEADER_EXPLICIT;
      38           0 :   this->implicitLen = 0xFF;
      39             : 
      40             :   // set module properties and perform initial setup
      41           0 :   int16_t state = this->modSetup(tcxoVoltage, useRegulatorLDO, RADIOLIB_SX126X_PACKET_TYPE_LORA);
      42           0 :   RADIOLIB_ASSERT(state);
      43             : 
      44             :   // configure publicly accessible settings
      45           0 :   state = setCodingRate(cr);
      46           0 :   RADIOLIB_ASSERT(state);
      47             : 
      48           0 :   state = setSyncWord(syncWord);
      49           0 :   RADIOLIB_ASSERT(state);
      50             : 
      51           0 :   state = setPreambleLength(preambleLength);
      52           0 :   RADIOLIB_ASSERT(state);
      53             : 
      54             :   // set publicly accessible settings that are not a part of begin method
      55           0 :   state = setCurrentLimit(60.0);
      56           0 :   RADIOLIB_ASSERT(state);
      57             : 
      58           0 :   state = setDio2AsRfSwitch(true);
      59           0 :   RADIOLIB_ASSERT(state);
      60             : 
      61           0 :   state = setCRC(2);
      62           0 :   RADIOLIB_ASSERT(state);
      63             : 
      64           0 :   state = invertIQ(false);
      65           0 :   RADIOLIB_ASSERT(state);
      66             : 
      67           0 :   return(state);
      68             : }
      69             : 
      70           0 : int16_t SX126x::beginFSK(float br, float freqDev, float rxBw, uint16_t preambleLength, float tcxoVoltage, bool useRegulatorLDO) {
      71             :   // initialize configuration variables (will be overwritten during public settings configuration)
      72           0 :   this->bitRate = 21333;                                  // 48.0 kbps
      73           0 :   this->frequencyDev = 52428;                             // 50.0 kHz
      74           0 :   this->rxBandwidth = RADIOLIB_SX126X_GFSK_RX_BW_156_2;
      75           0 :   this->rxBandwidthKhz = 156.2;
      76           0 :   this->pulseShape = RADIOLIB_SX126X_GFSK_FILTER_GAUSS_0_5;
      77           0 :   this->crcTypeFSK = RADIOLIB_SX126X_GFSK_CRC_2_BYTE_INV;     // CCITT CRC configuration
      78           0 :   this->preambleLengthFSK = preambleLength;
      79             : 
      80             :   // set module properties and perform initial setup
      81           0 :   int16_t state = this->modSetup(tcxoVoltage, useRegulatorLDO, RADIOLIB_SX126X_PACKET_TYPE_GFSK);
      82           0 :   RADIOLIB_ASSERT(state);
      83             : 
      84             :   // configure publicly accessible settings
      85           0 :   state = setBitRate(br);
      86           0 :   RADIOLIB_ASSERT(state);
      87             : 
      88           0 :   state = setFrequencyDeviation(freqDev);
      89           0 :   RADIOLIB_ASSERT(state);
      90             : 
      91           0 :   state = setRxBandwidth(rxBw);
      92           0 :   RADIOLIB_ASSERT(state);
      93             : 
      94           0 :   state = setCurrentLimit(60.0);
      95           0 :   RADIOLIB_ASSERT(state);
      96             : 
      97           0 :   state = setPreambleLength(preambleLength);
      98           0 :   RADIOLIB_ASSERT(state);
      99             : 
     100             :   // set publicly accessible settings that are not a part of begin method
     101           0 :   uint8_t sync[] = {0x12, 0xAD};
     102           0 :   state = setSyncWord(sync, 2);
     103           0 :   RADIOLIB_ASSERT(state);
     104             : 
     105           0 :   state = setDataShaping(RADIOLIB_SHAPING_NONE);
     106           0 :   RADIOLIB_ASSERT(state);
     107             : 
     108           0 :   state = setEncoding(RADIOLIB_ENCODING_NRZ);
     109           0 :   RADIOLIB_ASSERT(state);
     110             : 
     111           0 :   state = variablePacketLengthMode(RADIOLIB_SX126X_MAX_PACKET_LENGTH);
     112           0 :   RADIOLIB_ASSERT(state);
     113             : 
     114           0 :   state = setCRC(2);
     115           0 :   RADIOLIB_ASSERT(state);
     116             : 
     117           0 :   state = setDio2AsRfSwitch(true);
     118           0 :   RADIOLIB_ASSERT(state);
     119             : 
     120           0 :   return(state);
     121             : }
     122             : 
     123           0 : int16_t SX126x::beginBPSK(float br, float tcxoVoltage, bool useRegulatorLDO) {
     124             :   // set module properties and perform initial setup
     125           0 :   int16_t state = this->modSetup(tcxoVoltage, useRegulatorLDO, RADIOLIB_SX126X_PACKET_TYPE_BPSK);
     126           0 :   RADIOLIB_ASSERT(state);
     127             : 
     128             :   // configure publicly accessible settings
     129           0 :   state = setBitRate(br);
     130           0 :   RADIOLIB_ASSERT(state);
     131             :   
     132             :   // set publicly accessible settings that are not a part of begin method
     133           0 :   state = setDio2AsRfSwitch(true);
     134           0 :   RADIOLIB_ASSERT(state);
     135             : 
     136           0 :   return(state);
     137             : }
     138             : 
     139           0 : int16_t SX126x::beginLRFHSS(uint8_t bw, uint8_t cr, bool narrowGrid, float tcxoVoltage, bool useRegulatorLDO) {
     140           0 :   this->lrFhssGridNonFcc = narrowGrid;
     141             :   
     142             :   // set module properties and perform initial setup
     143           0 :   int16_t state = this->modSetup(tcxoVoltage, useRegulatorLDO, RADIOLIB_SX126X_PACKET_TYPE_LR_FHSS);
     144           0 :   RADIOLIB_ASSERT(state);
     145             : 
     146             :   // set publicly accessible settings that are not a part of begin method
     147           0 :   state = setCurrentLimit(60.0);
     148           0 :   RADIOLIB_ASSERT(state);
     149             : 
     150           0 :   state = setDio2AsRfSwitch(true);
     151           0 :   RADIOLIB_ASSERT(state);
     152             : 
     153             :   // set all packet params to 0 (packet engine is disabled in LR-FHSS mode)
     154           0 :   state = setPacketParamsFSK(0, 0, 0, 0, 0, 0, 0, 0);
     155           0 :   RADIOLIB_ASSERT(state);
     156             : 
     157             :   // set bit rate
     158           0 :   this->rxBandwidth = 0;
     159           0 :   this->frequencyDev = 0;
     160           0 :   this->pulseShape = RADIOLIB_SX126X_GFSK_FILTER_GAUSS_1;
     161           0 :   state = setBitRate(0.48828125f);
     162           0 :   RADIOLIB_ASSERT(state);
     163             : 
     164           0 :   return(setLrFhssConfig(bw, cr));
     165             : }
     166             : 
     167           0 : int16_t SX126x::setLrFhssConfig(uint8_t bw, uint8_t cr, uint8_t hdrCount, uint16_t hopSeqId) {
     168             :   // check and cache all parameters
     169           0 :   RADIOLIB_CHECK_RANGE((int8_t)cr, (int8_t)RADIOLIB_SX126X_LR_FHSS_CR_5_6, (int8_t)RADIOLIB_SX126X_LR_FHSS_CR_1_3, RADIOLIB_ERR_INVALID_CODING_RATE);
     170           0 :   this->lrFhssCr = cr;
     171           0 :   RADIOLIB_CHECK_RANGE((int8_t)bw, (int8_t)RADIOLIB_SX126X_LR_FHSS_BW_39_06, (int8_t)RADIOLIB_SX126X_LR_FHSS_BW_1574_2, RADIOLIB_ERR_INVALID_BANDWIDTH);
     172           0 :   this->lrFhssBw = bw;
     173           0 :   RADIOLIB_CHECK_RANGE(hdrCount, 1, 4, RADIOLIB_ERR_INVALID_BIT_RANGE);
     174           0 :   this->lrFhssHdrCount = hdrCount;
     175           0 :   RADIOLIB_CHECK_RANGE((int16_t)hopSeqId, (int16_t)0x000, (int16_t)0x1FF, RADIOLIB_ERR_INVALID_DATA_SHAPING);
     176           0 :   this->lrFhssHopSeqId = hopSeqId;
     177           0 :   return(RADIOLIB_ERR_NONE);
     178             : }
     179             : 
     180           0 : int16_t SX126x::reset(bool verify) {
     181             :   // run the reset sequence
     182           0 :   this->mod->hal->pinMode(this->mod->getRst(), this->mod->hal->GpioModeOutput);
     183           0 :   this->mod->hal->digitalWrite(this->mod->getRst(), this->mod->hal->GpioLevelLow);
     184           0 :   this->mod->hal->delay(1);
     185           0 :   this->mod->hal->digitalWrite(this->mod->getRst(), this->mod->hal->GpioLevelHigh);
     186             : 
     187             :   // return immediately when verification is disabled
     188           0 :   if(!verify) {
     189           0 :     return(RADIOLIB_ERR_NONE);
     190             :   }
     191             : 
     192             :   // set mode to standby - SX126x often refuses first few commands after reset
     193           0 :   RadioLibTime_t start = this->mod->hal->millis();
     194             :   while(true) {
     195             :     // try to set mode to standby
     196           0 :     int16_t state = standby();
     197           0 :     if(state == RADIOLIB_ERR_NONE) {
     198             :       // standby command successful
     199           0 :       return(RADIOLIB_ERR_NONE);
     200             :     }
     201             : 
     202             :     // standby command failed, check timeout and try again
     203           0 :     if(this->mod->hal->millis() - start >= 1000) {
     204             :       // timed out, possibly incorrect wiring
     205           0 :       return(state);
     206             :     }
     207             : 
     208             :     // wait a bit to not spam the module
     209           0 :     this->mod->hal->delay(10);
     210           0 :   }
     211             : }
     212             : 
     213           0 : int16_t SX126x::transmit(const uint8_t* data, size_t len, uint8_t addr) {
     214             :   // set mode to standby
     215           0 :   int16_t state = standby();
     216           0 :   RADIOLIB_ASSERT(state);
     217             : 
     218             :   // check packet length
     219           0 :   if(this->codingRate > RADIOLIB_SX126X_LORA_CR_4_8) {
     220             :     // Long Interleaver needs at least 8 bytes
     221           0 :     if(len < 8) {
     222           0 :       return(RADIOLIB_ERR_PACKET_TOO_SHORT);
     223             :     }
     224             : 
     225             :     // Long Interleaver supports up to 253 bytes if CRC is enabled
     226           0 :     if(this->crcTypeLoRa == RADIOLIB_SX126X_LORA_CRC_ON && (len > RADIOLIB_SX126X_MAX_PACKET_LENGTH - 2)) {
     227           0 :       return(RADIOLIB_ERR_PACKET_TOO_LONG);
     228             :     }  
     229             :   } 
     230           0 :   if(len > RADIOLIB_SX126X_MAX_PACKET_LENGTH) {
     231           0 :     return(RADIOLIB_ERR_PACKET_TOO_LONG);
     232             :   }
     233             : 
     234             :   // calculate timeout in ms (5ms + 500 % of expected time-on-air)
     235           0 :   RadioLibTime_t timeout = 5 + (getTimeOnAir(len) * 5) / 1000;
     236             :   RADIOLIB_DEBUG_BASIC_PRINTLN("Timeout in %lu ms", timeout);
     237             : 
     238             :   // start transmission
     239           0 :   state = startTransmit(data, len, addr);
     240           0 :   RADIOLIB_ASSERT(state);
     241             : 
     242             :   // wait for packet transmission or timeout
     243           0 :   uint8_t modem = getPacketType();
     244           0 :   RadioLibTime_t start = this->mod->hal->millis();
     245             :   while(true) {
     246             :     // yield for  multi-threaded platforms
     247           0 :     this->mod->hal->yield();
     248             : 
     249             :     // check timeout
     250           0 :     if(this->mod->hal->millis() - start > timeout) {
     251           0 :       finishTransmit();
     252           0 :       return(RADIOLIB_ERR_TX_TIMEOUT);
     253             :     }
     254             : 
     255             :     // poll the interrupt pin
     256           0 :     if(this->mod->hal->digitalRead(this->mod->getIrq())) {
     257             :       // in LoRa or GFSK, only Tx done interrupt is enabled
     258           0 :       if(modem != RADIOLIB_SX126X_PACKET_TYPE_LR_FHSS) {
     259           0 :         break;
     260             :       }
     261             : 
     262             :       // in LR-FHSS, IRQ signals both Tx done as frequency hop request
     263           0 :       if(this->getIrqFlags() & RADIOLIB_SX126X_IRQ_TX_DONE) {
     264           0 :         break;
     265             :       } else {
     266             :         // handle frequency hop
     267           0 :         this->hopLRFHSS();
     268             :       }
     269             :     }
     270             :   }
     271             : 
     272             :   // update data rate
     273           0 :   RadioLibTime_t elapsed = this->mod->hal->millis() - start;
     274           0 :   this->dataRateMeasured = (len*8.0f)/((float)elapsed/1000.0f);
     275             : 
     276           0 :   return(finishTransmit());
     277             : }
     278             : 
     279           0 : int16_t SX126x::receive(uint8_t* data, size_t len, RadioLibTime_t timeout) {
     280             :   // set mode to standby
     281           0 :   int16_t state = standby();
     282           0 :   RADIOLIB_ASSERT(state);
     283             : 
     284           0 :   RadioLibTime_t timeoutInternal = timeout;
     285           0 :   if(!timeoutInternal) {
     286             :     // calculate timeout (500 % of expected time-one-air)
     287           0 :     size_t maxLen = len;
     288           0 :     if(len == 0) { maxLen = RADIOLIB_SX126X_MAX_PACKET_LENGTH; }
     289           0 :     timeoutInternal = (getTimeOnAir(maxLen) * 5) / 1000;
     290             :   }
     291             : 
     292             :   RADIOLIB_DEBUG_BASIC_PRINTLN("Timeout in %lu ms", timeoutInternal);
     293             : 
     294             :   // start reception
     295           0 :   uint32_t timeoutValue = (uint32_t)(((float)timeoutInternal * 1000.0f) / 15.625f);
     296           0 :   state = startReceive(timeoutValue);
     297           0 :   RADIOLIB_ASSERT(state);
     298             : 
     299             :   // wait for packet reception or timeout
     300           0 :   bool softTimeout = false;
     301           0 :   RadioLibTime_t start = this->mod->hal->millis();
     302           0 :   while(!this->mod->hal->digitalRead(this->mod->getIrq())) {
     303           0 :     this->mod->hal->yield();
     304             :     // safety check, the timeout should be done by the radio
     305           0 :     if(this->mod->hal->millis() - start > timeoutInternal) {
     306           0 :       softTimeout = true;
     307           0 :       break;
     308             :     }
     309             :   }
     310             : 
     311             :   // if it was a timeout, this will return an error code
     312           0 :   state = standby();
     313           0 :   if((state != RADIOLIB_ERR_NONE) && (state != RADIOLIB_ERR_SPI_CMD_TIMEOUT)) {
     314           0 :     return(state);
     315             :   }
     316             : 
     317             :   // check whether this was a timeout or not
     318           0 :   if(softTimeout || (getIrqFlags() & this->irqMap[RADIOLIB_IRQ_TIMEOUT])) {
     319           0 :     (void)finishReceive();
     320           0 :     return(RADIOLIB_ERR_RX_TIMEOUT);
     321             :   }
     322             : 
     323             :   // read the received data
     324           0 :   return(readData(data, len));
     325             : }
     326             : 
     327           0 : int16_t SX126x::transmitDirect(uint32_t frf) {
     328             :   // set RF switch (if present)
     329           0 :   this->mod->setRfSwitchState(this->txMode);
     330             : 
     331             :   // user requested to start transmitting immediately (required for RTTY)
     332           0 :   int16_t state = RADIOLIB_ERR_NONE;
     333           0 :   if(frf != 0) {
     334           0 :     state = setRfFrequency(frf);
     335             :   }
     336           0 :   RADIOLIB_ASSERT(state);
     337             : 
     338             :   // direct mode activation intentionally skipped here, as it seems to lead to much worse results
     339           0 :   const uint8_t data[] = { RADIOLIB_SX126X_CMD_NOP };
     340           0 :   return(this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_TX_CONTINUOUS_WAVE, data, 1));
     341             : }
     342             : 
     343           0 : int16_t SX126x::receiveDirect() {
     344             :   // set RF switch (if present)
     345           0 :   this->mod->setRfSwitchState(Module::MODE_RX);
     346             : 
     347             :   // SX126x is unable to output received data directly
     348           0 :   return(RADIOLIB_ERR_UNKNOWN);
     349             : }
     350             : 
     351           0 : int16_t SX126x::directMode() {
     352             :   // check modem
     353           0 :   if(getPacketType() != RADIOLIB_SX126X_PACKET_TYPE_GFSK) {
     354           0 :     return(RADIOLIB_ERR_WRONG_MODEM);
     355             :   }
     356             : 
     357             :   // set mode to standby
     358           0 :   int16_t state = standby();
     359           0 :   RADIOLIB_ASSERT(state);
     360             : 
     361             :   // disable DIO2 RF switch
     362           0 :   state = setDio2AsRfSwitch(false);
     363           0 :   RADIOLIB_ASSERT(state);
     364             : 
     365             :   // set DIO2 to clock output and DIO3 to data input
     366             :   // this is done exclusively by writing magic values to even more magic registers
     367           0 :   state = this->mod->SPIsetRegValue(RADIOLIB_SX126X_REG_TX_BITBANG_ENABLE_1, RADIOLIB_SX126X_TX_BITBANG_1_ENABLED, 6, 4);
     368           0 :   RADIOLIB_ASSERT(state);
     369           0 :   state = this->mod->SPIsetRegValue(RADIOLIB_SX126X_REG_TX_BITBANG_ENABLE_0, RADIOLIB_SX126X_TX_BITBANG_0_ENABLED, 3, 0);
     370           0 :   RADIOLIB_ASSERT(state);
     371           0 :   state = this->mod->SPIsetRegValue(RADIOLIB_SX126X_REG_DIOX_OUT_ENABLE, RADIOLIB_SX126X_DIO3_OUT_DISABLED, 3, 3);
     372           0 :   RADIOLIB_ASSERT(state);
     373           0 :   state = this->mod->SPIsetRegValue(RADIOLIB_SX126X_REG_DIOX_IN_ENABLE, RADIOLIB_SX126X_DIO3_IN_ENABLED, 3, 3);
     374           0 :   RADIOLIB_ASSERT(state);
     375             : 
     376             :   // enable TxDone interrupt
     377           0 :   state = setDioIrqParams(RADIOLIB_SX126X_IRQ_TX_DONE, RADIOLIB_SX126X_IRQ_TX_DONE);
     378           0 :   RADIOLIB_ASSERT(state);
     379             : 
     380             :   // set preamble length to the maximum to prevent SX126x from exiting Tx mode for a while
     381           0 :   state = setPreambleLength(0xFFFF);
     382           0 :   RADIOLIB_ASSERT(state);
     383             : 
     384           0 :   return(state);
     385             : }
     386             : 
     387           0 : int16_t SX126x::packetMode() {
     388             :   // set mode to standby
     389           0 :   int16_t state = standby();
     390           0 :   RADIOLIB_ASSERT(state);
     391             : 
     392             :   // set preamble length to the default
     393           0 :   state = setPreambleLength(16);
     394           0 :   RADIOLIB_ASSERT(state);
     395             : 
     396             :   // disable TxDone interrupt
     397           0 :   state = setDioIrqParams(RADIOLIB_SX126X_IRQ_NONE, RADIOLIB_SX126X_IRQ_NONE);
     398           0 :   RADIOLIB_ASSERT(state);
     399             : 
     400             :   // restore the magic registers
     401           0 :   state = this->mod->SPIsetRegValue(RADIOLIB_SX126X_REG_DIOX_IN_ENABLE, RADIOLIB_SX126X_DIO3_IN_DISABLED, 3, 3);
     402           0 :   RADIOLIB_ASSERT(state);
     403           0 :   state = this->mod->SPIsetRegValue(RADIOLIB_SX126X_REG_DIOX_OUT_ENABLE, RADIOLIB_SX126X_DIO3_OUT_ENABLED, 3, 3);
     404           0 :   RADIOLIB_ASSERT(state);
     405           0 :   state = this->mod->SPIsetRegValue(RADIOLIB_SX126X_REG_TX_BITBANG_ENABLE_0, RADIOLIB_SX126X_TX_BITBANG_0_DISABLED, 3, 0);
     406           0 :   RADIOLIB_ASSERT(state);
     407           0 :   state = this->mod->SPIsetRegValue(RADIOLIB_SX126X_REG_TX_BITBANG_ENABLE_1, RADIOLIB_SX126X_TX_BITBANG_1_DISABLED, 6, 4);
     408           0 :   RADIOLIB_ASSERT(state);
     409             : 
     410             :   // enable DIO2 RF switch
     411           0 :   state = setDio2AsRfSwitch(true);
     412           0 :   RADIOLIB_ASSERT(state);
     413             : 
     414           0 :   return(state);
     415             : }
     416             : 
     417           0 : int16_t SX126x::scanChannel() {
     418           0 :   ChannelScanConfig_t cfg = {
     419             :     .cad = {
     420             :       .symNum = RADIOLIB_SX126X_CAD_PARAM_DEFAULT,
     421             :       .detPeak = RADIOLIB_SX126X_CAD_PARAM_DEFAULT,
     422             :       .detMin = RADIOLIB_SX126X_CAD_PARAM_DEFAULT,
     423             :       .exitMode = RADIOLIB_SX126X_CAD_PARAM_DEFAULT,
     424             :       .timeout = 0,
     425             :       .irqFlags = RADIOLIB_IRQ_CAD_DEFAULT_FLAGS,
     426             :       .irqMask = RADIOLIB_IRQ_CAD_DEFAULT_MASK,
     427             :     },
     428             :   };
     429           0 :   return(this->scanChannel(cfg));
     430             : }
     431             : 
     432           0 : int16_t SX126x::scanChannel(const ChannelScanConfig_t &config) {
     433             :   // set mode to CAD
     434           0 :   int state = startChannelScan(config);
     435           0 :   RADIOLIB_ASSERT(state);
     436             : 
     437             :   // wait for channel activity detected or timeout
     438           0 :   while(!this->mod->hal->digitalRead(this->mod->getIrq())) {
     439           0 :     this->mod->hal->yield();
     440             :   }
     441             : 
     442             :   // check CAD result
     443           0 :   return(getChannelScanResult());
     444             : }
     445             : 
     446           0 : int16_t SX126x::hopLRFHSS() {
     447           0 :   if(!(this->getIrqFlags() & RADIOLIB_SX126X_IRQ_LR_FHSS_HOP)) {
     448           0 :     return(RADIOLIB_ERR_TX_TIMEOUT);
     449             :   }
     450             : 
     451           0 :   int16_t state = this->setLRFHSSHop(this->lrFhssHopNum % 16);
     452           0 :   RADIOLIB_ASSERT(state);
     453           0 :   return(clearIrqStatus());
     454             : }
     455             : 
     456           0 : int16_t SX126x::finishTransmit() {
     457             :   // clear interrupt flags
     458           0 :   int16_t state = clearIrqStatus();
     459           0 :   RADIOLIB_ASSERT(state);
     460             : 
     461             :   // set mode to standby to disable transmitter/RF switch
     462           0 :   return(standby());
     463             : }
     464             : 
     465           0 : int16_t SX126x::finishReceive() {
     466             :   // set mode to standby to disable RF switch
     467           0 :   int16_t state = standby();
     468           0 :   RADIOLIB_ASSERT(state);
     469             : 
     470             :   // try to fix timeout error in implicit header mode
     471             :   // check for modem type and header mode is done in fixImplicitTimeout()
     472           0 :   state = fixImplicitTimeout();
     473           0 :   RADIOLIB_ASSERT(state);
     474             : 
     475             :   // clear interrupt flags
     476           0 :   return(clearIrqStatus());
     477             : }
     478             : 
     479           0 : int16_t SX126x::startReceive() {
     480           0 :   return(this->startReceive(RADIOLIB_SX126X_RX_TIMEOUT_INF, RADIOLIB_IRQ_RX_DEFAULT_FLAGS, RADIOLIB_IRQ_RX_DEFAULT_MASK, 0));
     481             : }
     482             : 
     483           0 : int16_t SX126x::startReceiveDutyCycle(uint32_t rxPeriod, uint32_t sleepPeriod, RadioLibIrqFlags_t irqFlags, RadioLibIrqFlags_t irqMask) {
     484             :   // datasheet claims time to go to sleep is ~500us, same to wake up, compensate for that with 1 ms + TCXO delay
     485           0 :   uint32_t transitionTime = this->tcxoDelay + 1000;
     486           0 :   sleepPeriod -= transitionTime;
     487             : 
     488             :   // divide by 15.625
     489           0 :   uint32_t rxPeriodRaw = (rxPeriod * 8) / 125;
     490           0 :   uint32_t sleepPeriodRaw = (sleepPeriod * 8) / 125;
     491             : 
     492             :   // check 24 bit limit and zero value (likely not intended)
     493           0 :   if((rxPeriodRaw & 0xFF000000) || (rxPeriodRaw == 0)) {
     494           0 :     return(RADIOLIB_ERR_INVALID_RX_PERIOD);
     495             :   }
     496             : 
     497             :   // this check of the high byte also catches underflow when we subtracted transitionTime
     498           0 :   if((sleepPeriodRaw & 0xFF000000) || (sleepPeriodRaw == 0)) {
     499           0 :     return(RADIOLIB_ERR_INVALID_SLEEP_PERIOD);
     500             :   }
     501             : 
     502           0 :   int16_t state = startReceiveCommon(RADIOLIB_SX126X_RX_TIMEOUT_INF, irqFlags, irqMask);
     503           0 :   RADIOLIB_ASSERT(state);
     504             : 
     505           0 :   const uint8_t data[6] = {(uint8_t)((rxPeriodRaw >> 16) & 0xFF), (uint8_t)((rxPeriodRaw >> 8) & 0xFF), (uint8_t)(rxPeriodRaw & 0xFF),
     506           0 :                      (uint8_t)((sleepPeriodRaw >> 16) & 0xFF), (uint8_t)((sleepPeriodRaw >> 8) & 0xFF), (uint8_t)(sleepPeriodRaw & 0xFF)};
     507           0 :   return(this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_RX_DUTY_CYCLE, data, 6));
     508             : }
     509             : 
     510           0 : int16_t SX126x::startReceiveDutyCycleAuto(uint16_t senderPreambleLength, uint16_t minSymbols, RadioLibIrqFlags_t irqFlags, RadioLibIrqFlags_t irqMask) {
     511           0 :   if(senderPreambleLength == 0) {
     512           0 :     senderPreambleLength = this->preambleLengthLoRa;
     513           0 :   } else if (senderPreambleLength > this->preambleLengthLoRa) {
     514             :     // the unit must be configured to expect a preamble length at least as long as the sender is using
     515           0 :     return(RADIOLIB_ERR_INVALID_PREAMBLE_LENGTH);
     516             :   }
     517           0 :   if(minSymbols == 0) {
     518           0 :     if (this->spreadingFactor <= 6) {
     519           0 :       minSymbols = 12;
     520             :     } else {
     521           0 :       minSymbols = 8;
     522             :     }
     523             :   }
     524             : 
     525             :   // worst case is that the sender starts transmitting when we're just less than minSymbols from going back to sleep.
     526             :   // in this case, we don't catch minSymbols before going to sleep,
     527             :   // so we must be awake for at least that long before the sender stops transmitting.
     528           0 :   uint16_t sleepSymbols = senderPreambleLength - 2 * minSymbols;
     529             : 
     530             :   // if we're not to sleep at all, just use the standard startReceive.
     531           0 :   if(2 * minSymbols > senderPreambleLength) {
     532           0 :     return(startReceive(RADIOLIB_SX126X_RX_TIMEOUT_INF, irqFlags, irqMask));
     533             :   }
     534             : 
     535           0 :   uint32_t symbolLength = ((uint32_t)(10 * 1000) << this->spreadingFactor) / (10 * this->bandwidthKhz);
     536           0 :   uint32_t sleepPeriod = symbolLength * sleepSymbols;
     537             :   RADIOLIB_DEBUG_BASIC_PRINTLN("Auto sleep period: %lu", (long unsigned int)sleepPeriod);
     538             : 
     539             :   // when the unit detects a preamble, it starts a timer that will timeout if it doesn't receive a header in time.
     540             :   // the duration is sleepPeriod + 2 * wakePeriod.
     541             :   // The sleepPeriod doesn't take into account shutdown and startup time for the unit (~1ms)
     542             :   // We need to ensure that the timeout is longer than senderPreambleLength.
     543             :   // So we must satisfy: wakePeriod > (preamblePeriod - (sleepPeriod - 1000)) / 2. (A)
     544             :   // we also need to ensure the unit is awake to see at least minSymbols. (B)
     545           0 :   uint32_t wakePeriod = RADIOLIB_MAX(
     546             :     (symbolLength * (senderPreambleLength + 1) - (sleepPeriod - 1000)) / 2, // (A)
     547             :     symbolLength * (minSymbols + 1)); //(B)
     548             :   RADIOLIB_DEBUG_BASIC_PRINTLN("Auto wake period: %lu", (long unsigned int)wakePeriod);
     549             : 
     550             :   // If our sleep period is shorter than our transition time, just use the standard startReceive
     551           0 :   if(sleepPeriod < this->tcxoDelay + 1016) {
     552           0 :     return(startReceive(RADIOLIB_SX126X_RX_TIMEOUT_INF, irqFlags, irqMask));
     553             :   }
     554             : 
     555           0 :   return(startReceiveDutyCycle(wakePeriod, sleepPeriod, irqFlags, irqMask));
     556             : }
     557             : 
     558           0 : int16_t SX126x::startReceiveCommon(uint32_t timeout, RadioLibIrqFlags_t irqFlags, RadioLibIrqFlags_t irqMask) {
     559             :   // ensure we are in standby
     560           0 :   int16_t state = standby();
     561           0 :   RADIOLIB_ASSERT(state);
     562             : 
     563             :   // set DIO mapping
     564           0 :   if(timeout != RADIOLIB_SX126X_RX_TIMEOUT_INF) {
     565           0 :     irqMask |= (1UL << RADIOLIB_IRQ_TIMEOUT);
     566             :   }
     567           0 :   state = setDioIrqParams(getIrqMapped(irqFlags), getIrqMapped(irqMask));
     568           0 :   RADIOLIB_ASSERT(state);
     569             : 
     570             :   // set buffer pointers
     571           0 :   state = setBufferBaseAddress();
     572           0 :   RADIOLIB_ASSERT(state);
     573             : 
     574             :   // clear interrupt flags
     575           0 :   state = clearIrqStatus();
     576           0 :   RADIOLIB_ASSERT(state);
     577             : 
     578             :   // restore original packet length
     579           0 :   uint8_t modem = getPacketType();
     580           0 :   if(modem == RADIOLIB_SX126X_PACKET_TYPE_LORA) {
     581           0 :     state = setPacketParams(this->preambleLengthLoRa, this->crcTypeLoRa, this->implicitLen, this->headerType, this->invertIQEnabled);
     582           0 :   } else if(modem == RADIOLIB_SX126X_PACKET_TYPE_GFSK) {
     583           0 :     state = setPacketParamsFSK(this->preambleLengthFSK, this->preambleDetLength, this->crcTypeFSK, this->syncWordLength, RADIOLIB_SX126X_GFSK_ADDRESS_FILT_OFF, this->whitening, this->packetType);
     584             :   } else {
     585           0 :     return(RADIOLIB_ERR_UNKNOWN);
     586             :   }
     587             : 
     588           0 :   return(state);
     589             : }
     590             : 
     591           0 : int16_t SX126x::readData(uint8_t* data, size_t len) {
     592             :   // this method may get called from receive() after Rx timeout
     593             :   // if that's the case, the first call will return "SPI command timeout error"
     594             :   // check the IRQ to be sure this really originated from timeout event
     595           0 :   int16_t state = this->mod->SPIcheckStream();
     596           0 :   uint16_t irq = getIrqFlags();
     597           0 :   if((state == RADIOLIB_ERR_SPI_CMD_TIMEOUT) && (irq & RADIOLIB_SX126X_IRQ_TIMEOUT)) {
     598             :     // this is definitely Rx timeout
     599           0 :     return(RADIOLIB_ERR_RX_TIMEOUT);
     600             :   }
     601           0 :   RADIOLIB_ASSERT(state);
     602             : 
     603             :   // check integrity CRC
     604           0 :   int16_t crcState = RADIOLIB_ERR_NONE;
     605             :   // Report CRC mismatch when there's a payload CRC error, or a header error and no valid header (to avoid false alarm from previous packet)
     606           0 :   if((irq & RADIOLIB_SX126X_IRQ_CRC_ERR) || ((irq & RADIOLIB_SX126X_IRQ_HEADER_ERR) && !(irq & RADIOLIB_SX126X_IRQ_HEADER_VALID))) {
     607           0 :     crcState = RADIOLIB_ERR_CRC_MISMATCH;
     608             :   }
     609             :   
     610             :   // get packet length and Rx buffer offset
     611           0 :   uint8_t offset = 0;
     612           0 :   size_t length = getPacketLength(true, &offset);
     613           0 :   if((len != 0) && (len < length)) {
     614             :     // user requested less data than we got, only return what was requested
     615           0 :     length = len;
     616             :   }
     617             : 
     618             :   // read packet data starting at offset
     619           0 :   state = readBuffer(data, length, offset);
     620           0 :   RADIOLIB_ASSERT(state);
     621             : 
     622             :   // clear interrupt flags
     623           0 :   state = clearIrqStatus();
     624             : 
     625             :   // check if CRC failed - this is done after reading data to give user the option to keep them
     626           0 :   RADIOLIB_ASSERT(crcState);
     627             : 
     628           0 :   return(state);
     629             : }
     630             : 
     631           0 : int16_t SX126x::startChannelScan() {
     632           0 :   ChannelScanConfig_t cfg = {
     633             :     .cad = {
     634             :       .symNum = RADIOLIB_SX126X_CAD_PARAM_DEFAULT,
     635             :       .detPeak = RADIOLIB_SX126X_CAD_PARAM_DEFAULT,
     636             :       .detMin = RADIOLIB_SX126X_CAD_PARAM_DEFAULT,
     637             :       .exitMode = RADIOLIB_SX126X_CAD_PARAM_DEFAULT,
     638             :       .timeout = 0,
     639             :       .irqFlags = RADIOLIB_IRQ_CAD_DEFAULT_FLAGS,
     640             :       .irqMask = RADIOLIB_IRQ_CAD_DEFAULT_MASK,
     641             :     },
     642             :   };
     643           0 :   return(this->startChannelScan(cfg));
     644             : }
     645             : 
     646           0 : int16_t SX126x::startChannelScan(const ChannelScanConfig_t &config) {
     647             :   // check active modem
     648           0 :   if(getPacketType() != RADIOLIB_SX126X_PACKET_TYPE_LORA) {
     649           0 :     return(RADIOLIB_ERR_WRONG_MODEM);
     650             :   }
     651             : 
     652             :   // set mode to standby
     653           0 :   int16_t state = standby();
     654           0 :   RADIOLIB_ASSERT(state);
     655             : 
     656             :   // set RF switch (if present)
     657           0 :   this->mod->setRfSwitchState(Module::MODE_RX);
     658             : 
     659             :   // set DIO pin mapping
     660           0 :   state = setDioIrqParams(getIrqMapped(config.cad.irqFlags), getIrqMapped(config.cad.irqMask));
     661           0 :   RADIOLIB_ASSERT(state);
     662             : 
     663             :   // clear interrupt flags
     664           0 :   state = clearIrqStatus();
     665           0 :   RADIOLIB_ASSERT(state);
     666             : 
     667             :   // set mode to CAD
     668           0 :   state = setCad(config.cad.symNum, config.cad.detPeak, config.cad.detMin, config.cad.exitMode, config.cad.timeout);
     669           0 :   return(state);
     670             : }
     671             : 
     672           0 : int16_t SX126x::getChannelScanResult() {
     673             :   // check active modem
     674           0 :   if(getPacketType() != RADIOLIB_SX126X_PACKET_TYPE_LORA) {
     675           0 :     return(RADIOLIB_ERR_WRONG_MODEM);
     676             :   }
     677             : 
     678             :   // check CAD result
     679           0 :   uint16_t cadResult = getIrqFlags();
     680           0 :   if(cadResult & RADIOLIB_SX126X_IRQ_CAD_DETECTED) {
     681             :     // detected some LoRa activity
     682           0 :     return(RADIOLIB_LORA_DETECTED);
     683           0 :   } else if(cadResult & RADIOLIB_SX126X_IRQ_CAD_DONE) {
     684             :     // channel is free
     685           0 :     return(RADIOLIB_CHANNEL_FREE);
     686             :   }
     687             : 
     688           0 :   return(RADIOLIB_ERR_UNKNOWN);
     689             : }
     690             : 
     691           0 : float SX126x::getDataRate() const {
     692           0 :   return(this->dataRateMeasured);
     693             : }
     694             : 
     695           0 : float SX126x::getRSSI() {
     696           0 :   return(this->getRSSI(true));
     697             : }
     698             : 
     699           0 : float SX126x::getRSSI(bool packet) {
     700           0 :   if(packet) { 
     701             :     // get last packet RSSI from packet status
     702           0 :     uint32_t packetStatus = getPacketStatus();
     703           0 :     uint8_t rssiPkt = packetStatus & 0xFF;
     704           0 :     return(-1.0 * rssiPkt/2.0);
     705             :   } else {
     706             :     // get instantaneous RSSI value
     707           0 :     uint8_t rssiRaw = 0;
     708           0 :     this->mod->SPIreadStream(RADIOLIB_SX126X_CMD_GET_RSSI_INST, &rssiRaw, 1);
     709           0 :     return((float)rssiRaw / (-2.0f));
     710             :   }
     711             : }
     712             : 
     713           0 : float SX126x::getSNR() {
     714             :   // check active modem
     715           0 :   if(getPacketType() != RADIOLIB_SX126X_PACKET_TYPE_LORA) {
     716           0 :     return(RADIOLIB_ERR_WRONG_MODEM);
     717             :   }
     718             : 
     719             :   // get last packet SNR from packet status
     720           0 :   uint32_t packetStatus = getPacketStatus();
     721           0 :   uint8_t snrPkt = (packetStatus >> 8) & 0xFF;
     722           0 :   if(snrPkt < 128) {
     723           0 :     return(snrPkt/4.0);
     724             :   } else {
     725           0 :     return((snrPkt - 256)/4.0);
     726             :   }
     727             : }
     728             : 
     729           0 : float SX126x::getFrequencyError() {
     730             :   // check active modem
     731           0 :   uint8_t modem = getPacketType();
     732           0 :   if(modem != RADIOLIB_SX126X_PACKET_TYPE_LORA) {
     733           0 :     return(0.0);
     734             :   }
     735             : 
     736             :   // read the raw frequency error register values
     737           0 :   uint8_t efeRaw[3] = {0};
     738           0 :   int16_t state = readRegister(RADIOLIB_SX126X_REG_FREQ_ERROR_RX_CRC, &efeRaw[0], 1);
     739           0 :   RADIOLIB_ASSERT(state);
     740           0 :   state = readRegister(RADIOLIB_SX126X_REG_FREQ_ERROR_RX_CRC + 1, &efeRaw[1], 1);
     741           0 :   RADIOLIB_ASSERT(state);
     742           0 :   state = readRegister(RADIOLIB_SX126X_REG_FREQ_ERROR_RX_CRC + 2, &efeRaw[2], 1);
     743           0 :   RADIOLIB_ASSERT(state);
     744           0 :   uint32_t efe = ((uint32_t) efeRaw[0] << 16) | ((uint32_t) efeRaw[1] << 8) | efeRaw[2];
     745           0 :   efe &= 0x0FFFFF;
     746             : 
     747           0 :   float error = 0;
     748             : 
     749             :   // check the first bit
     750           0 :   if (efe & 0x80000) {
     751             :     // frequency error is negative
     752           0 :     efe |= (uint32_t) 0xFFF00000;
     753           0 :     efe = ~efe + 1;
     754           0 :     error = 1.55f * (float) efe / (1600.0f / (float) this->bandwidthKhz) * -1.0f;
     755             :   } else {
     756           0 :     error = 1.55f * (float) efe / (1600.0f / (float) this->bandwidthKhz);
     757             :   }
     758             : 
     759           0 :   return(error);
     760             : }
     761             : 
     762           0 : size_t SX126x::getPacketLength(bool update) {
     763           0 :   return(this->getPacketLength(update, NULL));
     764             : }
     765             : 
     766           0 : size_t SX126x::getPacketLength(bool update, uint8_t* offset) {
     767             :   (void)update;
     768             : 
     769             :   // in implicit mode, return the cached value if the offset was not requested
     770           0 :   if((getPacketType() == RADIOLIB_SX126X_PACKET_TYPE_LORA) && (this->headerType == RADIOLIB_SX126X_LORA_HEADER_IMPLICIT) && (!offset)) {
     771           0 :     return(this->implicitLen);
     772             :   }
     773             : 
     774             :   // if offset was requested, or in explicit mode, we always have to perform the SPI transaction
     775           0 :   uint8_t rxBufStatus[2] = {0, 0};
     776           0 :   this->mod->SPIreadStream(RADIOLIB_SX126X_CMD_GET_RX_BUFFER_STATUS, rxBufStatus, 2);
     777             : 
     778           0 :   if(offset) { *offset = rxBufStatus[1]; }
     779             : 
     780           0 :   return((size_t)rxBufStatus[0]);
     781             : }
     782             : 
     783           0 : int16_t SX126x::getLoRaRxHeaderInfo(uint8_t* cr, bool* hasCRC) {
     784           0 :   int16_t state = RADIOLIB_ERR_NONE;
     785             : 
     786             :   // check if in explicit header mode
     787           0 :   if(this->headerType == RADIOLIB_SX126X_LORA_HEADER_IMPLICIT) {
     788           0 :     return(RADIOLIB_ERR_WRONG_MODEM);
     789             :   }
     790             : 
     791           0 :   if(cr) { *cr = this->mod->SPIgetRegValue(RADIOLIB_SX126X_REG_LORA_RX_CODING_RATE, 6, 4) >> 4; }
     792           0 :   if(hasCRC) { *hasCRC = (this->mod->SPIgetRegValue(RADIOLIB_SX126X_REG_FREQ_ERROR_RX_CRC, 4, 4) != 0); }
     793             : 
     794           0 :   return(state);
     795             : }
     796             : 
     797          15 : RadioLibTime_t SX126x::calculateTimeOnAir(ModemType_t modem, DataRate_t dr, PacketConfig_t pc, size_t len) {
     798             :   // everything is in microseconds to allow integer arithmetic
     799             :   // some constants have .25, these are multiplied by 4, and have _x4 postfix to indicate that fact
     800          15 :   switch (modem) {
     801           8 :     case RADIOLIB_MODEM_LORA: {
     802           8 :       uint32_t symbolLength_us = ((uint32_t)(1000 * 10) << dr.lora.spreadingFactor) / (dr.lora.bandwidth * 10) ;
     803           8 :       uint8_t sfCoeff1_x4 = 17; // (4.25 * 4)
     804           8 :       uint8_t sfCoeff2 = 8;
     805           8 :       if(dr.lora.spreadingFactor == 5 || dr.lora.spreadingFactor == 6) {
     806           0 :         sfCoeff1_x4 = 25; // 6.25 * 4
     807           0 :         sfCoeff2 = 0;
     808             :       }
     809           8 :       uint8_t sfDivisor = 4*dr.lora.spreadingFactor;
     810           8 :       if(pc.lora.ldrOptimize) {
     811           4 :         sfDivisor = 4*(dr.lora.spreadingFactor - 2);
     812             :       }
     813           8 :       const int8_t bitsPerCrc = 16;
     814           8 :       const int8_t N_symbol_header = pc.lora.implicitHeader ? 0 : 20;
     815             : 
     816             :       // numerator of equation in section 6.1.4 of SX1268 datasheet v1.1 (might not actually be bitcount, but it has len * 8)
     817           8 :       int16_t bitCount = (int16_t) 8 * len + pc.lora.crcEnabled * bitsPerCrc - 4 * dr.lora.spreadingFactor  + sfCoeff2 + N_symbol_header;
     818           8 :       if(bitCount < 0) {
     819           0 :         bitCount = 0;
     820             :       }
     821             :       // add (sfDivisor) - 1 to the numerator to give integer CEIL(...)
     822           8 :       uint16_t nPreCodedSymbols = (bitCount + (sfDivisor - 1)) / (sfDivisor);
     823             : 
     824             :       // preamble can be 65k, therefore nSymbol_x4 needs to be 32 bit
     825           8 :       uint32_t nSymbol_x4 = (pc.lora.preambleLength + 8) * 4 + sfCoeff1_x4 + nPreCodedSymbols * dr.lora.codingRate * 4;
     826             : 
     827           8 :       return((symbolLength_us * nSymbol_x4) / 4);
     828             :     }
     829           4 :     case RADIOLIB_MODEM_FSK: {
     830           4 :        return((((float)(pc.fsk.crcLength * 8) + pc.fsk.syncWordLength + pc.fsk.preambleLength + (uint32_t)len * 8) / (dr.fsk.bitRate / 1000.0f)));
     831             :     }
     832           3 :     case RADIOLIB_MODEM_LRFHSS: {
     833             :       // calculate the number of bits based on coding rate
     834             :       uint16_t N_bits;
     835           3 :       switch(dr.lrFhss.cr) {
     836           0 :         case RADIOLIB_SX126X_LR_FHSS_CR_5_6:
     837           0 :           N_bits = ((len * 6) + 4) / 5; // this is from the official LR11xx driver, but why the extra +4?
     838           0 :           break;
     839           3 :         case RADIOLIB_SX126X_LR_FHSS_CR_2_3:
     840           3 :           N_bits = (len * 3) / 2;
     841           3 :           break;
     842           0 :         case RADIOLIB_SX126X_LR_FHSS_CR_1_2:
     843           0 :           N_bits = len * 2;
     844           0 :           break;
     845           0 :         case RADIOLIB_SX126X_LR_FHSS_CR_1_3:
     846           0 :           N_bits = len * 3;
     847           0 :           break;
     848           0 :         default:
     849           0 :           return(RADIOLIB_ERR_INVALID_CODING_RATE);
     850             :       }
     851             : 
     852             :       // calculate number of bits when accounting for unaligned last block
     853           3 :       uint16_t N_payBits = (N_bits / RADIOLIB_SX126X_LR_FHSS_FRAG_BITS) * RADIOLIB_SX126X_LR_FHSS_BLOCK_BITS;
     854           3 :       uint16_t N_lastBlockBits = N_bits % RADIOLIB_SX126X_LR_FHSS_FRAG_BITS;
     855           3 :       if(N_lastBlockBits) {
     856           3 :         N_payBits += N_lastBlockBits + 2;
     857             :       }
     858             : 
     859             :       // add header bits
     860           3 :       uint16_t N_totalBits = (RADIOLIB_SX126X_LR_FHSS_HEADER_BITS * pc.lrFhss.hdrCount) + N_payBits;
     861           3 :       return(((uint32_t)N_totalBits * 8 * 1000000UL) / 488.28215f);
     862             :     }
     863           0 :     default:
     864           0 :       return(RADIOLIB_ERR_WRONG_MODEM);
     865             :   }
     866             : 
     867             :   return(RADIOLIB_ERR_UNKNOWN);
     868             : }
     869             : 
     870           0 : RadioLibTime_t SX126x::getTimeOnAir(size_t len) {
     871           0 :   uint8_t type = getPacketType();
     872           0 :   ModemType_t modem = RADIOLIB_MODEM_LORA;
     873           0 :   DataRate_t dataRate = {};
     874           0 :   PacketConfig_t packetConfig = {};
     875             : 
     876           0 :   if(type == RADIOLIB_SX126X_PACKET_TYPE_LORA) {
     877           0 :     uint8_t cr = this->codingRate;
     878             :     // We assume same calculation for short and long interleaving, so map CR values 0-4 and 5-7 to the same values
     879           0 :     if (cr < 5) {
     880           0 :       cr = cr + 4;
     881           0 :     } else if (cr == 7) {
     882           0 :       cr = cr + 1;
     883             :     }
     884             : 
     885           0 :     dataRate.lora.codingRate = cr;
     886           0 :     dataRate.lora.spreadingFactor = this->spreadingFactor;
     887           0 :     dataRate.lora.bandwidth = this->bandwidthKhz;
     888             : 
     889           0 :     packetConfig.lora.preambleLength = this->preambleLengthLoRa;
     890           0 :     packetConfig.lora.crcEnabled = (bool)this->crcTypeLoRa;
     891           0 :     packetConfig.lora.implicitHeader = this->headerType == RADIOLIB_SX126X_LORA_HEADER_IMPLICIT;
     892           0 :     packetConfig.lora.ldrOptimize = (bool)this->ldrOptimize;
     893           0 :   } else if(type == RADIOLIB_SX126X_PACKET_TYPE_GFSK) {
     894           0 :     modem = RADIOLIB_MODEM_FSK;
     895             : 
     896           0 :     dataRate.fsk.bitRate = RADIOLIB_SX126X_CRYSTAL_FREQ * 32.0f * 1000.0f / (float)this->bitRate;
     897           0 :     dataRate.fsk.freqDev = (float)this->frequencyDev;
     898             : 
     899           0 :     uint8_t crcLen = 0;
     900           0 :     if(this->crcTypeFSK == RADIOLIB_SX126X_GFSK_CRC_1_BYTE || this->crcTypeFSK == RADIOLIB_SX126X_GFSK_CRC_1_BYTE_INV) {
     901           0 :       crcLen = 1;
     902           0 :     } else if(this->crcTypeFSK == RADIOLIB_SX126X_GFSK_CRC_2_BYTE || this->crcTypeFSK == RADIOLIB_SX126X_GFSK_CRC_2_BYTE_INV) {
     903           0 :       crcLen = 2;
     904             :     }
     905             : 
     906           0 :     packetConfig.fsk.preambleLength = this->preambleLengthFSK;
     907           0 :     packetConfig.fsk.syncWordLength = this->syncWordLength;
     908           0 :     packetConfig.fsk.crcLength = crcLen;
     909           0 :   } else if(type == RADIOLIB_SX126X_PACKET_TYPE_LR_FHSS) {
     910           0 :     modem = RADIOLIB_MODEM_LRFHSS;
     911             : 
     912           0 :     dataRate.lrFhss.bw = this->lrFhssBw;
     913           0 :     dataRate.lrFhss.cr = this->lrFhssCr;
     914           0 :     dataRate.lrFhss.narrowGrid = this->lrFhssGridNonFcc;
     915             :     
     916           0 :     packetConfig.lrFhss.hdrCount = this->lrFhssHdrCount;
     917           0 :   } else if(type == RADIOLIB_SX126X_PACKET_TYPE_BPSK) {
     918             :     // BPSK is so experimental it does not have a specific data rate structure
     919             :     // so just reuse FSK
     920           0 :     modem = RADIOLIB_MODEM_FSK;
     921             : 
     922           0 :     dataRate.fsk.bitRate = RADIOLIB_SX126X_CRYSTAL_FREQ * 32.0f * 1000.0f / (float)this->bitRate;
     923           0 :     dataRate.fsk.freqDev = 0;
     924             : 
     925           0 :     packetConfig.fsk.preambleLength = 0;
     926           0 :     packetConfig.fsk.syncWordLength = 0;
     927           0 :     packetConfig.fsk.crcLength = 0;
     928             : 
     929             :   } else {
     930           0 :     return(RADIOLIB_ERR_WRONG_MODEM);
     931             :   
     932             :   }
     933             : 
     934           0 :   return(calculateTimeOnAir(modem, dataRate, packetConfig, len));
     935             : }
     936             : 
     937           0 : RadioLibTime_t SX126x::calculateRxTimeout(RadioLibTime_t timeoutUs) {
     938             :   // the timeout value is given in units of 15.625 microseconds
     939             :   // the calling function should provide some extra width, as this number of units is truncated to integer
     940           0 :   RadioLibTime_t timeout = timeoutUs / 15.625f;
     941           0 :   return(timeout);
     942             : }
     943             : 
     944           0 : uint32_t SX126x::getIrqFlags() {
     945           0 :   uint8_t data[] = { 0x00, 0x00 };
     946           0 :   this->mod->SPIreadStream(RADIOLIB_SX126X_CMD_GET_IRQ_STATUS, data, 2);
     947           0 :   return(((uint32_t)(data[0]) << 8) | data[1]);
     948             : }
     949             : 
     950           0 : int16_t SX126x::setIrqFlags(uint32_t irq) {
     951           0 :   return(this->setDioIrqParams(irq, irq));
     952             : }
     953             : 
     954           0 : int16_t SX126x::clearIrqFlags(uint32_t irq) {
     955           0 :   return(this->clearIrqStatus(irq));
     956             : }
     957             : 
     958           0 : uint8_t SX126x::randomByte() {
     959             :   // set some magic registers
     960           0 :   this->mod->SPIsetRegValue(RADIOLIB_SX126X_REG_ANA_LNA, RADIOLIB_SX126X_LNA_RNG_ENABLED, 0, 0);
     961           0 :   this->mod->SPIsetRegValue(RADIOLIB_SX126X_REG_ANA_MIXER, RADIOLIB_SX126X_MIXER_RNG_ENABLED, 0, 0);
     962             : 
     963             :   // set mode to Rx
     964           0 :   setRx(RADIOLIB_SX126X_RX_TIMEOUT_INF);
     965             : 
     966             :   // wait a bit for the RSSI reading to stabilise
     967           0 :   this->mod->hal->delay(10);
     968             : 
     969             :   // read RSSI value 8 times, always keep just the least significant bit
     970           0 :   uint8_t randByte = 0x00;
     971           0 :   for(uint8_t i = 0; i < 8; i++) {
     972           0 :     uint8_t val = 0x00;
     973           0 :     readRegister(RADIOLIB_SX126X_REG_RANDOM_NUMBER_0, &val, sizeof(uint8_t));
     974           0 :     randByte |= ((val & 0x01) << i);
     975             :   }
     976             : 
     977             :   // set mode to standby
     978           0 :   standby();
     979             : 
     980             :   // restore the magic registers
     981           0 :   this->mod->SPIsetRegValue(RADIOLIB_SX126X_REG_ANA_LNA, RADIOLIB_SX126X_LNA_RNG_DISABLED, 0, 0);
     982           0 :   this->mod->SPIsetRegValue(RADIOLIB_SX126X_REG_ANA_MIXER, RADIOLIB_SX126X_MIXER_RNG_DISABLED, 0, 0);
     983             : 
     984           0 :   return(randByte);
     985             : }
     986             : 
     987           0 : int16_t SX126x::getModem(ModemType_t* modem) {
     988           0 :   RADIOLIB_ASSERT_PTR(modem);
     989             : 
     990           0 :   uint8_t type = getPacketType();
     991           0 :   switch(type) {
     992           0 :     case(RADIOLIB_SX126X_PACKET_TYPE_LORA):
     993           0 :       *modem = ModemType_t::RADIOLIB_MODEM_LORA;
     994           0 :       return(RADIOLIB_ERR_NONE);
     995           0 :     case(RADIOLIB_SX126X_PACKET_TYPE_GFSK):
     996           0 :       *modem = ModemType_t::RADIOLIB_MODEM_FSK;
     997           0 :       return(RADIOLIB_ERR_NONE);
     998           0 :     case(RADIOLIB_SX126X_PACKET_TYPE_LR_FHSS):
     999           0 :       *modem = ModemType_t::RADIOLIB_MODEM_LRFHSS;
    1000           0 :       return(RADIOLIB_ERR_NONE);
    1001             :   }
    1002             :   
    1003           0 :   return(RADIOLIB_ERR_WRONG_MODEM);
    1004             : }
    1005             : 
    1006           0 : int16_t SX126x::stageMode(RadioModeType_t mode, RadioModeConfig_t* cfg) {
    1007             :   int16_t state;
    1008             : 
    1009           0 :   switch(mode) {
    1010           0 :     case(RADIOLIB_RADIO_MODE_RX): {
    1011             :       // in implicit header mode, use the provided length if it is nonzero
    1012             :       // otherwise we trust the user has previously set the payload length manually
    1013           0 :       if((this->headerType == RADIOLIB_SX126X_LORA_HEADER_IMPLICIT) && (cfg->receive.len != 0)) {
    1014           0 :         this->implicitLen = cfg->receive.len;
    1015             :       }
    1016             : 
    1017           0 :       state = startReceiveCommon(cfg->receive.timeout, cfg->receive.irqFlags, cfg->receive.irqMask);
    1018           0 :       RADIOLIB_ASSERT(state);
    1019             : 
    1020             :       // if max(uint32_t) is used, revert to RxContinuous
    1021           0 :       if(cfg->receive.timeout == 0xFFFFFFFF) {
    1022           0 :         cfg->receive.timeout = 0xFFFFFF;
    1023             :       }
    1024           0 :       this->rxTimeout = cfg->receive.timeout;
    1025           0 :     } break;
    1026             :   
    1027           0 :     case(RADIOLIB_RADIO_MODE_TX): {
    1028             :       // check packet length
    1029           0 :       if(cfg->transmit.len > RADIOLIB_SX126X_MAX_PACKET_LENGTH) {
    1030           0 :         return(RADIOLIB_ERR_PACKET_TOO_LONG);
    1031             :       }
    1032             : 
    1033             :       // maximum packet length is decreased by 1 when address filtering is active
    1034             :       if((RADIOLIB_SX126X_GFSK_ADDRESS_FILT_OFF != RADIOLIB_SX126X_GFSK_ADDRESS_FILT_OFF) && 
    1035             :         (cfg->transmit.len > RADIOLIB_SX126X_MAX_PACKET_LENGTH - 1)) {
    1036             :         return(RADIOLIB_ERR_PACKET_TOO_LONG);
    1037             :       }
    1038             : 
    1039             :       // set packet Length
    1040           0 :       state = RADIOLIB_ERR_NONE;
    1041           0 :       uint8_t modem = getPacketType();
    1042           0 :       if(modem == RADIOLIB_SX126X_PACKET_TYPE_LORA) {
    1043           0 :         state = setPacketParams(this->preambleLengthLoRa, this->crcTypeLoRa, cfg->transmit.len, this->headerType, this->invertIQEnabled);
    1044             :       
    1045           0 :       } else if(modem == RADIOLIB_SX126X_PACKET_TYPE_GFSK) {
    1046           0 :         state = setPacketParamsFSK(this->preambleLengthFSK, this->preambleDetLength, this->crcTypeFSK, this->syncWordLength, RADIOLIB_SX126X_GFSK_ADDRESS_FILT_OFF, this->whitening, this->packetType, cfg->transmit.len);
    1047             :       
    1048           0 :       } else if(modem == RADIOLIB_SX126X_PACKET_TYPE_BPSK) {
    1049           0 :         uint16_t rampUp = RADIOLIB_SX126X_BPSK_RAMP_UP_TIME_600_BPS;
    1050           0 :         uint16_t rampDown = RADIOLIB_SX126X_BPSK_RAMP_DOWN_TIME_600_BPS;
    1051           0 :         if(this->bitRate == 100) {
    1052           0 :           rampUp = RADIOLIB_SX126X_BPSK_RAMP_UP_TIME_100_BPS;
    1053           0 :           rampDown = RADIOLIB_SX126X_BPSK_RAMP_DOWN_TIME_100_BPS;
    1054             :         }
    1055           0 :         state = setPacketParamsBPSK(cfg->transmit.len, rampUp, rampDown, 8*cfg->transmit.len);
    1056             :       
    1057           0 :       } else if(modem != RADIOLIB_SX126X_PACKET_TYPE_LR_FHSS) {
    1058           0 :         return(RADIOLIB_ERR_UNKNOWN);
    1059             :       
    1060             :       }
    1061           0 :       RADIOLIB_ASSERT(state);
    1062             : 
    1063             :       // set DIO mapping
    1064           0 :       if(modem != RADIOLIB_SX126X_PACKET_TYPE_LR_FHSS) {
    1065           0 :         state = setDioIrqParams(RADIOLIB_SX126X_IRQ_TX_DONE | RADIOLIB_SX126X_IRQ_TIMEOUT, RADIOLIB_SX126X_IRQ_TX_DONE);
    1066             :       } else {
    1067           0 :         state = setDioIrqParams(RADIOLIB_SX126X_IRQ_TX_DONE | RADIOLIB_SX126X_IRQ_LR_FHSS_HOP, RADIOLIB_SX126X_IRQ_TX_DONE | RADIOLIB_SX126X_IRQ_LR_FHSS_HOP);
    1068             :       }
    1069           0 :       RADIOLIB_ASSERT(state);
    1070             : 
    1071             :       // set buffer pointers
    1072           0 :       state = setBufferBaseAddress();
    1073           0 :       RADIOLIB_ASSERT(state);
    1074             : 
    1075             :       // write packet to buffer
    1076           0 :       if(modem != RADIOLIB_SX126X_PACKET_TYPE_LR_FHSS) {
    1077           0 :         state = writeBuffer(cfg->transmit.data, cfg->transmit.len);
    1078             :       
    1079             :       } else {
    1080             :         // first, reset the LR-FHSS state machine
    1081           0 :         state = resetLRFHSS();
    1082           0 :         RADIOLIB_ASSERT(state);
    1083             : 
    1084             :         // skip hopping for the first 4 - lrFhssHdrCount blocks
    1085           0 :         for(int i = 0; i < 4 - this->lrFhssHdrCount; ++i ) {
    1086           0 :           stepLRFHSS();
    1087             :         }
    1088             : 
    1089             :         // in LR-FHSS mode, we need to build the entire packet manually
    1090           0 :         uint8_t frame[RADIOLIB_SX126X_MAX_PACKET_LENGTH] = { 0 };
    1091           0 :         size_t frameLen = 0;
    1092           0 :         this->lrFhssFrameBitsRem = 0;
    1093           0 :         this->lrFhssFrameHopsRem = 0;
    1094           0 :         this->lrFhssHopNum = 0;
    1095           0 :         state = buildLRFHSSPacket(cfg->transmit.data, cfg->transmit.len, frame, &frameLen, &this->lrFhssFrameBitsRem, &this->lrFhssFrameHopsRem);
    1096           0 :         RADIOLIB_ASSERT(state);
    1097             : 
    1098             :         // FIXME check max len for FHSS
    1099           0 :         state = writeBuffer(frame, frameLen);
    1100           0 :         RADIOLIB_ASSERT(state);
    1101             : 
    1102             :         // activate hopping
    1103           0 :         const uint8_t hopCfg[] = { RADIOLIB_SX126X_HOPPING_ENABLED, (uint8_t)frameLen, (uint8_t)this->lrFhssFrameHopsRem };
    1104           0 :         state = writeRegister(RADIOLIB_SX126X_REG_HOPPING_ENABLE, hopCfg, 3);
    1105           0 :         RADIOLIB_ASSERT(state);
    1106             : 
    1107             :         // write the initial hopping table
    1108           0 :         uint8_t initHops = this->lrFhssFrameHopsRem;
    1109           0 :         if(initHops > 16) {
    1110           0 :           initHops = 16;
    1111             :         };
    1112           0 :         for(size_t i = 0; i < initHops; i++) {
    1113             :           // set the hop frequency and symbols
    1114           0 :           state = this->setLRFHSSHop(i);
    1115           0 :           RADIOLIB_ASSERT(state);
    1116             :         }
    1117             :       
    1118             :       }
    1119           0 :       RADIOLIB_ASSERT(state);
    1120             : 
    1121             :       // clear interrupt flags
    1122           0 :       state = clearIrqStatus();
    1123           0 :       RADIOLIB_ASSERT(state);
    1124             : 
    1125             :       // fix sensitivity
    1126           0 :       state = fixSensitivity();
    1127           0 :       RADIOLIB_ASSERT(state);
    1128           0 :     } break;
    1129             :     
    1130           0 :     default:
    1131           0 :       return(RADIOLIB_ERR_UNSUPPORTED);
    1132             :   }
    1133             : 
    1134           0 :   this->stagedMode = mode;
    1135           0 :   return(state);
    1136             : }
    1137             : 
    1138           0 : int16_t SX126x::launchMode() {
    1139             :   int16_t state;
    1140           0 :   switch(this->stagedMode) {
    1141           0 :     case(RADIOLIB_RADIO_MODE_RX): {
    1142           0 :       this->mod->setRfSwitchState(Module::MODE_RX);
    1143           0 :       state = setRx(this->rxTimeout);
    1144           0 :     } break;
    1145             :   
    1146           0 :     case(RADIOLIB_RADIO_MODE_TX): {
    1147           0 :       this->mod->setRfSwitchState(this->txMode);
    1148           0 :       state = setTx(RADIOLIB_SX126X_TX_TIMEOUT_NONE);
    1149           0 :       RADIOLIB_ASSERT(state);
    1150             : 
    1151             :       // wait for BUSY to go low (= PA ramp up done)
    1152           0 :       while(this->mod->hal->digitalRead(this->mod->getGpio())) {
    1153           0 :         this->mod->hal->yield();
    1154             :       }
    1155           0 :     } break;
    1156             :     
    1157           0 :     default:
    1158           0 :       return(RADIOLIB_ERR_UNSUPPORTED);
    1159             :   }
    1160             : 
    1161           0 :   this->stagedMode = RADIOLIB_RADIO_MODE_NONE;
    1162           0 :   return(state);
    1163             : }
    1164             : 
    1165             : #if !RADIOLIB_EXCLUDE_DIRECT_RECEIVE
    1166           0 : void SX126x::setDirectAction(void (*func)(void)) {
    1167           0 :   setDio1Action(func);
    1168           0 : }
    1169             : 
    1170           0 : void SX126x::readBit(uint32_t pin) {
    1171           0 :   updateDirectBuffer((uint8_t)this->mod->hal->digitalRead(pin));
    1172           0 : }
    1173             : #endif
    1174             : 
    1175           0 : int16_t SX126x::uploadPatch(const uint32_t* patch, size_t len, bool nonvolatile) {
    1176             :   // set to standby RC mode
    1177           0 :   int16_t state = standby(RADIOLIB_SX126X_STANDBY_RC);
    1178           0 :   RADIOLIB_ASSERT(state);
    1179             : 
    1180             :   // check the version
    1181             :   #if RADIOLIB_DEBUG_BASIC
    1182             :   char ver_pre[16];
    1183             :   this->mod->SPIreadRegisterBurst(RADIOLIB_SX126X_REG_VERSION_STRING, 16, reinterpret_cast<uint8_t*>(ver_pre));
    1184             :   RADIOLIB_DEBUG_BASIC_PRINTLN("Pre-update version string: %s", ver_pre);
    1185             :   #endif
    1186             : 
    1187             :   // enable patch update
    1188           0 :   this->mod->SPIwriteRegister(RADIOLIB_SX126X_REG_PATCH_UPDATE_ENABLE, RADIOLIB_SX126X_PATCH_UPDATE_ENABLED);
    1189             :   
    1190             :   // upload the patch
    1191             :   uint8_t data[4];
    1192           0 :   for(uint32_t i = 0; i < len / sizeof(uint32_t); i++) {
    1193           0 :     uint32_t bin = 0;
    1194           0 :     if(nonvolatile) {
    1195           0 :       uint32_t* ptr = const_cast<uint32_t*>(patch) + i;
    1196           0 :       bin = RADIOLIB_NONVOLATILE_READ_DWORD(ptr);
    1197             :     } else {
    1198           0 :       bin = patch[i];
    1199             :     }
    1200           0 :     data[0] = (bin >> 24) & 0xFF;
    1201           0 :     data[1] = (bin >> 16) & 0xFF;
    1202           0 :     data[2] = (bin >> 8) & 0xFF;
    1203           0 :     data[3] = bin & 0xFF;
    1204           0 :     this->mod->SPIwriteRegisterBurst(RADIOLIB_SX126X_REG_PATCH_MEMORY_BASE + i*sizeof(uint32_t), data, sizeof(uint32_t));
    1205             :   }
    1206             : 
    1207             :   // disable patch update
    1208           0 :   this->mod->SPIwriteRegister(RADIOLIB_SX126X_REG_PATCH_UPDATE_ENABLE, RADIOLIB_SX126X_PATCH_UPDATE_DISABLED);
    1209             : 
    1210             :   // update
    1211           0 :   this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_PRAM_UPDATE, NULL, 0);
    1212             : 
    1213             :   // check the version again
    1214             :   #if RADIOLIB_DEBUG_BASIC
    1215             :   char ver_post[16];
    1216             :   this->mod->SPIreadRegisterBurst(RADIOLIB_SX126X_REG_VERSION_STRING, 16, reinterpret_cast<uint8_t*>(ver_post));
    1217             :   RADIOLIB_DEBUG_BASIC_PRINTLN("Post-update version string: %s", ver_post);
    1218             :   #endif
    1219             : 
    1220           0 :   return(state);
    1221             : }
    1222             : 
    1223           0 : int16_t SX126x::spectralScanStart(uint16_t numSamples, uint8_t window, uint8_t interval) {
    1224             :   // abort first - not sure if this is strictly needed, but the example code does this
    1225           0 :   spectralScanAbort();
    1226             : 
    1227             :   // set the RSSI window size
    1228           0 :   this->mod->SPIwriteRegister(RADIOLIB_SX126X_REG_RSSI_AVG_WINDOW, window);
    1229             : 
    1230             :   // start Rx with infinite timeout
    1231           0 :   int16_t state = setRx(RADIOLIB_SX126X_RX_TIMEOUT_INF);
    1232           0 :   RADIOLIB_ASSERT(state);
    1233             : 
    1234             :   // now set the actual spectral scan parameters
    1235           0 :   const uint8_t data[3] = { (uint8_t)((numSamples >> 8) & 0xFF), (uint8_t)(numSamples & 0xFF), interval };
    1236           0 :   return(this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_SPECTR_SCAN_PARAMS, data, 3));
    1237             : }
    1238             : 
    1239           0 : void SX126x::spectralScanAbort() {
    1240           0 :   this->mod->SPIwriteRegister(RADIOLIB_SX126X_REG_RSSI_AVG_WINDOW, 0x00);
    1241           0 : }
    1242             : 
    1243           0 : int16_t SX126x::spectralScanGetStatus() {
    1244           0 :   uint8_t status = this->mod->SPIreadRegister(RADIOLIB_SX126X_REG_SPECTRAL_SCAN_STATUS);
    1245           0 :   if(status == RADIOLIB_SX126X_SPECTRAL_SCAN_COMPLETED) {
    1246           0 :     return(RADIOLIB_ERR_NONE);
    1247             :   }
    1248           0 :   return(RADIOLIB_ERR_RANGING_TIMEOUT);
    1249             : }
    1250             : 
    1251           0 : int16_t SX126x::spectralScanGetResult(uint16_t* results) {
    1252             :   // read the raw results
    1253             :   uint8_t data[2*RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE];
    1254           0 :   this->mod->SPIreadRegisterBurst(RADIOLIB_SX126X_REG_SPECTRAL_SCAN_RESULT, 2*RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE, data);
    1255             : 
    1256             :   // convert it
    1257           0 :   for(uint8_t i = 0; i < RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE; i++) {
    1258           0 :     results[i] = ((uint16_t)data[i*2] << 8) | ((uint16_t)data[i*2 + 1]);
    1259             :   }
    1260           0 :   return(RADIOLIB_ERR_NONE);
    1261             : }
    1262             : 
    1263           0 : int16_t SX126x::calibrateImage(float freq) {
    1264           0 :   uint8_t data[2] = { 0, 0 };
    1265             : 
    1266             :   // try to match the frequency ranges
    1267           0 :   int freqBand = (int)freq;
    1268           0 :   if((freqBand >= 902) && (freqBand <= 928)) {
    1269           0 :     data[0] = RADIOLIB_SX126X_CAL_IMG_902_MHZ_1;
    1270           0 :     data[1] = RADIOLIB_SX126X_CAL_IMG_902_MHZ_2;
    1271           0 :   } else if((freqBand >= 863) && (freqBand <= 870)) {
    1272           0 :     data[0] = RADIOLIB_SX126X_CAL_IMG_863_MHZ_1;
    1273           0 :     data[1] = RADIOLIB_SX126X_CAL_IMG_863_MHZ_2;
    1274           0 :   } else if((freqBand >= 779) && (freqBand <= 787)) {
    1275           0 :     data[0] = RADIOLIB_SX126X_CAL_IMG_779_MHZ_1;
    1276           0 :     data[1] = RADIOLIB_SX126X_CAL_IMG_779_MHZ_2;
    1277           0 :   } else if((freqBand >= 470) && (freqBand <= 510)) {
    1278           0 :     data[0] = RADIOLIB_SX126X_CAL_IMG_470_MHZ_1;
    1279           0 :     data[1] = RADIOLIB_SX126X_CAL_IMG_470_MHZ_2;
    1280           0 :   } else if((freqBand >= 430) && (freqBand <= 440)) {
    1281           0 :     data[0] = RADIOLIB_SX126X_CAL_IMG_430_MHZ_1;
    1282           0 :     data[1] = RADIOLIB_SX126X_CAL_IMG_430_MHZ_2;
    1283             :   }
    1284             : 
    1285             :   int16_t state;
    1286           0 :   if(data[0]) {
    1287             :     // matched with predefined ranges, do the calibration
    1288           0 :     state = SX126x::calibrateImage(data);
    1289             :   
    1290             :   } else {
    1291             :     // if nothing matched, try custom calibration - the may or may not work
    1292             :     RADIOLIB_DEBUG_BASIC_PRINTLN("Failed to match predefined frequency range, trying custom");
    1293           0 :     state = SX126x::calibrateImageRejection(freq - 4.0f, freq + 4.0f);
    1294             :   
    1295             :   }
    1296             :   
    1297           0 :   return(state);
    1298             : }
    1299             : 
    1300           0 : int16_t SX126x::calibrateImageRejection(float freqMin, float freqMax) {
    1301             :   // calculate the calibration coefficients and calibrate image
    1302           0 :   uint8_t data[] = { (uint8_t)floor((freqMin - 1.0f) / 4.0f), (uint8_t)ceil((freqMax + 1.0f) / 4.0f) };
    1303           0 :   data[0] = (data[0] % 2) ? data[0] : data[0] - 1;
    1304           0 :   data[1] = (data[1] % 2) ? data[1] : data[1] + 1;
    1305           0 :   return(this->calibrateImage(data));
    1306             : }
    1307             : 
    1308           0 : int16_t SX126x::fixSensitivity() {
    1309             :   // fix receiver sensitivity for 500 kHz LoRa
    1310             :   // see SX1262/SX1268 datasheet, chapter 15 Known Limitations, section 15.1 for details
    1311             : 
    1312             :   // read current sensitivity configuration
    1313           0 :   uint8_t sensitivityConfig = 0;
    1314           0 :   int16_t state = readRegister(RADIOLIB_SX126X_REG_SENSITIVITY_CONFIG, &sensitivityConfig, 1);
    1315           0 :   RADIOLIB_ASSERT(state);
    1316             : 
    1317             :   // fix the value for LoRa with 500 kHz bandwidth
    1318           0 :   if((getPacketType() == RADIOLIB_SX126X_PACKET_TYPE_LORA) && (fabsf(this->bandwidthKhz - 500.0f) <= 0.001f)) {
    1319           0 :     sensitivityConfig &= 0xFB;
    1320             :   } else {
    1321           0 :     sensitivityConfig |= 0x04;
    1322             :   }
    1323           0 :   return(writeRegister(RADIOLIB_SX126X_REG_SENSITIVITY_CONFIG, &sensitivityConfig, 1));
    1324             : }
    1325             : 
    1326           0 : int16_t SX126x::fixPaClamping(bool enable) {
    1327             :   // fixes overly eager PA clamping
    1328             :   // see SX1262/SX1268 datasheet, chapter 15 Known Limitations, section 15.2 for details
    1329             : 
    1330             :   // read current clamping configuration
    1331           0 :   uint8_t clampConfig = 0;
    1332           0 :   int16_t state = readRegister(RADIOLIB_SX126X_REG_TX_CLAMP_CONFIG, &clampConfig, 1);
    1333           0 :   RADIOLIB_ASSERT(state);
    1334             : 
    1335             :   // apply or undo workaround
    1336           0 :   if (enable)
    1337           0 :     clampConfig |= 0x1E;
    1338             :   else
    1339           0 :     clampConfig = (clampConfig & ~0x1E) | 0x08;
    1340             : 
    1341           0 :   return(writeRegister(RADIOLIB_SX126X_REG_TX_CLAMP_CONFIG, &clampConfig, 1));
    1342             : }
    1343             : 
    1344           0 : int16_t SX126x::fixImplicitTimeout() {
    1345             :   // fixes timeout in implicit header mode
    1346             :   // see SX1262/SX1268 datasheet, chapter 15 Known Limitations, section 15.3 for details
    1347             : 
    1348             :   //check if we're in implicit LoRa mode
    1349           0 :   if(!((this->headerType == RADIOLIB_SX126X_LORA_HEADER_IMPLICIT) && (getPacketType() == RADIOLIB_SX126X_PACKET_TYPE_LORA))) {
    1350             :     // not in the correct mode, nothing to do here
    1351           0 :     return(RADIOLIB_ERR_NONE);
    1352             :   }
    1353             : 
    1354             :   // stop RTC counter
    1355           0 :   uint8_t rtcStop = 0x00;
    1356           0 :   int16_t state = writeRegister(RADIOLIB_SX126X_REG_RTC_CTRL, &rtcStop, 1);
    1357           0 :   RADIOLIB_ASSERT(state);
    1358             : 
    1359             :   // read currently active event
    1360           0 :   uint8_t rtcEvent = 0;
    1361           0 :   state = readRegister(RADIOLIB_SX126X_REG_EVENT_MASK, &rtcEvent, 1);
    1362           0 :   RADIOLIB_ASSERT(state);
    1363             : 
    1364             :   // clear events
    1365           0 :   rtcEvent |= 0x02;
    1366           0 :   return(writeRegister(RADIOLIB_SX126X_REG_EVENT_MASK, &rtcEvent, 1));
    1367             : }
    1368             : 
    1369           0 : int16_t SX126x::fixInvertedIQ(uint8_t iqConfig) {
    1370             :   // fixes IQ configuration for inverted IQ
    1371             :   // see SX1262/SX1268 datasheet, chapter 15 Known Limitations, section 15.4 for details
    1372             : 
    1373             :   // read current IQ configuration
    1374           0 :   uint8_t iqConfigCurrent = 0;
    1375           0 :   int16_t state = readRegister(RADIOLIB_SX126X_REG_IQ_CONFIG, &iqConfigCurrent, 1);
    1376           0 :   RADIOLIB_ASSERT(state);
    1377             : 
    1378             :   // set correct IQ configuration
    1379           0 :   if(iqConfig == RADIOLIB_SX126X_LORA_IQ_INVERTED) {
    1380           0 :     iqConfigCurrent &= 0xFB;
    1381             :   } else {
    1382           0 :     iqConfigCurrent |= 0x04;
    1383             :   }
    1384             : 
    1385             :   // update with the new value
    1386           0 :   return(writeRegister(RADIOLIB_SX126X_REG_IQ_CONFIG, &iqConfigCurrent, 1));
    1387             : }
    1388             : 
    1389           0 : int16_t SX126x::fixGFSK() {
    1390             :   // method that applies some magic workaround for specific bitrate, frequency deviation,
    1391             :   // receiver bandwidth and carrier frequencies for GFSK (and resets it in all other cases)
    1392             :   // this is not documented in the datasheet, only in Semtech repositories for SX126x and LR11xx
    1393             : 
    1394             :   // first, check we are using GFSK modem
    1395           0 :   if(getPacketType() != RADIOLIB_SX126X_PACKET_TYPE_GFSK) {
    1396             :     // not in GFSK, nothing to do here
    1397           0 :     return(RADIOLIB_ERR_NONE);
    1398             :   }
    1399             : 
    1400             :   // next, decide what to change based on modulation properties
    1401           0 :   int16_t state = RADIOLIB_ERR_UNKNOWN;
    1402           0 :   if(this->bitRate == 1200) {
    1403             :     // workaround for 1.2 kbps
    1404           0 :     state = this->mod->SPIsetRegValue(RADIOLIB_SX126X_REG_GFSK_FIX_3, 0x00, 4, 4);
    1405             : 
    1406           0 :   } else if(this->bitRate == 600)  {
    1407             :     // workaround for 0.6 kbps
    1408           0 :     state = this->mod->SPIsetRegValue(RADIOLIB_SX126X_REG_GFSK_FIX_1, 0x18, 4, 3);
    1409           0 :     RADIOLIB_ASSERT(state);
    1410           0 :     state = this->mod->SPIsetRegValue(RADIOLIB_SX126X_REG_RSSI_AVG_WINDOW, 0x04, 4, 2);
    1411           0 :     RADIOLIB_ASSERT(state);
    1412           0 :     state = this->mod->SPIsetRegValue(RADIOLIB_SX126X_REG_GFSK_FIX_3, 0x00, 4, 4);
    1413           0 :     RADIOLIB_ASSERT(state);
    1414           0 :     state = this->mod->SPIsetRegValue(RADIOLIB_SX126X_REG_GFSK_FIX_4, 0x50, 6, 4);
    1415             :   
    1416             :   } else {
    1417             :     // reset
    1418           0 :     state = this->mod->SPIsetRegValue(RADIOLIB_SX126X_REG_GFSK_FIX_1, 0x08, 4, 3);
    1419           0 :     RADIOLIB_ASSERT(state);
    1420           0 :     state = this->mod->SPIsetRegValue(RADIOLIB_SX126X_REG_RSSI_AVG_WINDOW, 0x00, 4, 2);
    1421           0 :     RADIOLIB_ASSERT(state);
    1422           0 :     state = this->mod->SPIsetRegValue(RADIOLIB_SX126X_REG_GFSK_FIX_3, 0x10, 4, 4);
    1423           0 :     RADIOLIB_ASSERT(state);
    1424           0 :     state = this->mod->SPIsetRegValue(RADIOLIB_SX126X_REG_GFSK_FIX_4, 0x00, 6, 4);
    1425             :   
    1426             :   }
    1427             : 
    1428           0 :   return(state);
    1429             : }
    1430             : 
    1431           0 : Module* SX126x::getMod() {
    1432           0 :   return(this->mod);
    1433             : }
    1434             : 
    1435           0 : int16_t SX126x::modSetup(float tcxoVoltage, bool useRegulatorLDO, uint8_t modem) {
    1436             :   // set module properties
    1437           0 :   this->mod->init();
    1438           0 :   this->mod->hal->pinMode(this->mod->getIrq(), this->mod->hal->GpioModeInput);
    1439           0 :   this->mod->hal->pinMode(this->mod->getGpio(), this->mod->hal->GpioModeInput);
    1440           0 :   this->mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_ADDR] = Module::BITS_16;
    1441           0 :   this->mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_CMD] = Module::BITS_8;
    1442           0 :   this->mod->spiConfig.statusPos = 1;
    1443           0 :   this->mod->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_READ] = RADIOLIB_SX126X_CMD_READ_REGISTER;
    1444           0 :   this->mod->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_WRITE] = RADIOLIB_SX126X_CMD_WRITE_REGISTER;
    1445           0 :   this->mod->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_NOP] = RADIOLIB_SX126X_CMD_NOP;
    1446           0 :   this->mod->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_STATUS] = RADIOLIB_SX126X_CMD_GET_STATUS;
    1447           0 :   this->mod->spiConfig.stream = true;
    1448           0 :   this->mod->spiConfig.parseStatusCb = SPIparseStatus;
    1449             : 
    1450             :   // find the SX126x chip - this will also reset the module and verify the module
    1451           0 :   if(!SX126x::findChip(this->chipType)) {
    1452             :     RADIOLIB_DEBUG_BASIC_PRINTLN("No SX126x found!");
    1453           0 :     this->mod->term();
    1454           0 :     return(RADIOLIB_ERR_CHIP_NOT_FOUND);
    1455             :   }
    1456             :   RADIOLIB_DEBUG_BASIC_PRINTLN("M\tSX126x");
    1457             : 
    1458           0 :   int16_t state = RADIOLIB_ERR_NONE;
    1459             : 
    1460             :   // set TCXO control, if requested
    1461           0 :   if(!this->XTAL && tcxoVoltage > 0.0f) {
    1462           0 :     state = setTCXO(tcxoVoltage);
    1463           0 :     RADIOLIB_ASSERT(state);
    1464             :   }
    1465             : 
    1466             :   // configure settings not accessible by API
    1467           0 :   state = config(modem);
    1468           0 :   RADIOLIB_ASSERT(state);
    1469             : 
    1470           0 :   if (useRegulatorLDO) {
    1471           0 :     state = setRegulatorLDO();
    1472             :   } else {
    1473           0 :     state = setRegulatorDCDC();
    1474             :   }
    1475           0 :   return(state);
    1476             : }
    1477             : 
    1478           0 : int16_t SX126x::SPIparseStatus(uint8_t in) {
    1479           0 :   if((in & 0b00001110) == RADIOLIB_SX126X_STATUS_CMD_TIMEOUT) {
    1480           0 :     return(RADIOLIB_ERR_SPI_CMD_TIMEOUT);
    1481           0 :   } else if((in & 0b00001110) == RADIOLIB_SX126X_STATUS_CMD_INVALID) {
    1482           0 :     return(RADIOLIB_ERR_SPI_CMD_INVALID);
    1483           0 :   } else if((in & 0b00001110) == RADIOLIB_SX126X_STATUS_CMD_FAILED) {
    1484           0 :     return(RADIOLIB_ERR_SPI_CMD_FAILED);
    1485           0 :   } else if((in == 0x00) || (in == 0xFF)) {
    1486           0 :     return(RADIOLIB_ERR_CHIP_NOT_FOUND);
    1487             :   }
    1488           0 :   return(RADIOLIB_ERR_NONE);
    1489             : }
    1490             : 
    1491           0 : bool SX126x::findChip(const char* verStr) {
    1492           0 :   uint8_t i = 0;
    1493           0 :   bool flagFound = false;
    1494           0 :   while((i < 10) && !flagFound) {
    1495             :     // reset the module
    1496           0 :     reset(true);
    1497             : 
    1498             :     // read the version string
    1499           0 :     char version[16] = { 0 };
    1500           0 :     this->mod->SPIreadRegisterBurst(RADIOLIB_SX126X_REG_VERSION_STRING, 16, reinterpret_cast<uint8_t*>(version));
    1501             : 
    1502             :     // check version register
    1503           0 :     if(strncmp(verStr, version, 6) == 0) {
    1504             :       RADIOLIB_DEBUG_BASIC_PRINTLN("Found SX126x: RADIOLIB_SX126X_REG_VERSION_STRING:");
    1505             :       RADIOLIB_DEBUG_BASIC_HEXDUMP(reinterpret_cast<uint8_t*>(version), 16, RADIOLIB_SX126X_REG_VERSION_STRING);
    1506             :       RADIOLIB_DEBUG_BASIC_PRINTLN();
    1507           0 :       flagFound = true;
    1508             :     } else {
    1509             :       #if RADIOLIB_DEBUG_BASIC
    1510             :         RADIOLIB_DEBUG_BASIC_PRINTLN("SX126x not found! (%d of 10 tries) RADIOLIB_SX126X_REG_VERSION_STRING:", i + 1);
    1511             :         RADIOLIB_DEBUG_BASIC_HEXDUMP(reinterpret_cast<uint8_t*>(version), 16, RADIOLIB_SX126X_REG_VERSION_STRING);
    1512             :         RADIOLIB_DEBUG_BASIC_PRINTLN("Expected string: %s", verStr);
    1513             :       #endif
    1514           0 :       this->mod->hal->delay(10);
    1515           0 :       i++;
    1516             :     }
    1517             :   }
    1518             : 
    1519           0 :   return(flagFound);
    1520             : }
    1521             : 
    1522             : #endif

Generated by: LCOV version 1.14