LCOV - code coverage report
Current view: top level - src/modules/LR11x0 - LR_common.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 62 190 32.6 %
Date: 2026-11-08 19:43:08 Functions: 9 13 69.2 %

          Line data    Source code
       1             : #include "LR_common.h"
       2             : 
       3             : #include <string.h>
       4             : 
       5          18 : LRxxxx::LRxxxx(Module* mod) : PhysicalLayer() {
       6          18 :   this->mod = mod;
       7          18 :   this->mod->spiConfig.stream = true;
       8          18 :   this->mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_CMD] = Module::BITS_16;
       9          18 :   this->mod->spiConfig.statusPos = 0;
      10          18 :   this->mod->spiConfig.parseStatusCb = SPIparseStatus;
      11          18 :   this->mod->spiConfig.checkStatusCb = SPIcheckStatus;
      12          18 : }
      13             : 
      14           5 : uint32_t LRxxxx::getIrqStatus() {
      15             :   // there is no dedicated "get IRQ" command, the IRQ bits are sent after the status bytes
      16           5 :   uint8_t buff[6] = { 0 };
      17           5 :   Module::BitWidth_t statusWidth = mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_STATUS];
      18           5 :   this->mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_STATUS] = Module::BITS_0;
      19           5 :   mod->SPItransferStream(NULL, 0, false, NULL, buff, sizeof(buff), true);
      20           5 :   this->mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_STATUS] = statusWidth;
      21           5 :   uint32_t irq = ((uint32_t)(buff[2]) << 24) | ((uint32_t)(buff[3]) << 16) | ((uint32_t)(buff[4]) << 8) | (uint32_t)buff[5];
      22           5 :   return(irq);
      23             : }
      24             : 
      25           3 : RadioLibTime_t LRxxxx::getToA(size_t len, ModemType_t modem) {
      26           3 :   DataRate_t dr = {};
      27           3 :   PacketConfig_t pc = {};
      28           3 :   size_t packetLen = len;
      29           3 :   switch(modem) {
      30           0 :     case ModemType_t::RADIOLIB_MODEM_LORA: {
      31           0 :       uint8_t cr = this->codingRate;
      32             :       // We assume same calculation for short and long interleaving, so map CR values 0-4 and 5-7 to the same values
      33           0 :       if (cr < 5) {
      34           0 :         cr = cr + 4;
      35           0 :       } else if (cr == 7) {
      36           0 :         cr = cr + 1;
      37             :       }
      38             : 
      39           0 :       dr.lora.spreadingFactor = this->spreadingFactor;
      40           0 :       dr.lora.bandwidth = this->bandwidthHz;
      41           0 :       dr.lora.codingRate = cr;
      42             : 
      43           0 :       pc.lora.preambleLength = this->preambleLengthLoRa;
      44           0 :       pc.lora.implicitHeader = (this->headerType == RADIOLIB_LRXXXX_LORA_HEADER_IMPLICIT);
      45           0 :       pc.lora.crcEnabled = (this->crcTypeLoRa == RADIOLIB_LRXXXX_LORA_CRC_ENABLED);
      46           0 :       pc.lora.ldrOptimize = (bool)this->ldrOptimize;
      47           0 :       break;
      48             :     }
      49             : 
      50           0 :     case ModemType_t::RADIOLIB_MODEM_FSK: {
      51           0 :       dr.fsk.bitRate = this->bitRate;
      52           0 :       dr.fsk.freqDev = this->frequencyDev;
      53           0 :       pc.fsk.preambleLength = this->preambleLengthGFSK;
      54           0 :       pc.fsk.syncWordLength = this->syncWordLength; 
      55           0 :       pc.fsk.crcLength = this->crcLenGFSK;
      56           0 :       if(this->packetType != 0) {
      57             :         //! \todo [LRxxxx] Implement ToA calculation for extended GFSK modes (9/15-bit packet lengths)
      58           0 :         packetLen++;
      59             :       }
      60           0 :       break;
      61             :     }
      62             : 
      63           0 :     case ModemType_t::RADIOLIB_MODEM_LRFHSS: {
      64           0 :       dr.lrFhss.bw = this->lrFhssBw;
      65           0 :       dr.lrFhss.cr = this->lrFhssCr;
      66           0 :       dr.lrFhss.narrowGrid = (this->lrFhssGrid == RADIOLIB_LRXXXX_LR_FHSS_GRID_STEP_NON_FCC) ? true : false;
      67             : 
      68           0 :       pc.lrFhss.hdrCount = this->lrFhssHdrCount;
      69           0 :       break;
      70             :     }
      71             : 
      72           3 :     default:
      73           3 :       return(RADIOLIB_ERR_WRONG_MODEM);
      74             :   }
      75             : 
      76           0 :   return(this->calculateTimeOnAir(modem, dr, pc, packetLen));
      77             : }
      78             : 
      79           5 : RadioLibTime_t LRxxxx::calculateRxTimeout(RadioLibTime_t timeoutUs) {
      80             :   // the timeout value is given in units of 30.52 microseconds
      81             :   // the calling function should provide some extra width, as this number of units is truncated to integer
      82           5 :   RadioLibTime_t timeout = (RadioLibTime_t) ((float) timeoutUs * (1.0f / 30.52f));
      83             :   // or can be: RadioLibTime_t timeout = (RadioLibTime_t) (((uint64_t) timeoutUs * 34360) >> 20);
      84           5 :   return(timeout);
      85             : }
      86             : 
      87           0 : int16_t LRxxxx::reset() {
      88             :   // run the reset sequence
      89           0 :   this->mod->hal->pinMode(this->mod->getRst(), this->mod->hal->GpioModeOutput);
      90           0 :   this->mod->hal->digitalWrite(this->mod->getRst(), this->mod->hal->GpioLevelLow);
      91           0 :   this->mod->hal->delay(10);
      92           0 :   this->mod->hal->digitalWrite(this->mod->getRst(), this->mod->hal->GpioLevelHigh);
      93             : 
      94             :   // the typical transition duration should be 273 ms
      95           0 :   this->mod->hal->delay(300);
      96             :   
      97             :   // wait for BUSY to go low
      98           0 :   RadioLibTime_t start = this->mod->hal->millis();
      99           0 :   while(this->mod->hal->digitalRead(this->mod->getGpio())) {
     100           0 :     this->mod->hal->yield();
     101           0 :     if(this->mod->hal->millis() - start >= 3000) {
     102             :       RADIOLIB_DEBUG_BASIC_PRINTLN("BUSY pin timeout after reset!");
     103           0 :       return(RADIOLIB_ERR_SPI_CMD_TIMEOUT);
     104             :     }
     105             :   }
     106             : 
     107           0 :   return(RADIOLIB_ERR_NONE);
     108             : }
     109             : 
     110           5 : int16_t LRxxxx::getStatus(uint8_t* stat1, uint8_t* stat2, uint32_t* irq) {
     111           5 :   uint8_t buff[6] = { 0 };
     112             : 
     113             :   // the status check command doesn't return status in the same place as other read commands
     114             :   // but only as the first byte (as with any other command), hence LRxxxx::SPIcommand can't be used
     115             :   // it also seems to ignore the actual command, and just sending in bunch of NOPs will work 
     116             :   // the status bytes are the thing being read here, so they must not be skipped as a reply prefix
     117           5 :   Module::BitWidth_t statusWidth = this->mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_STATUS];
     118           5 :   this->mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_STATUS] = Module::BITS_0;
     119           5 :   int16_t state = this->mod->SPItransferStream(NULL, 0, false, NULL, buff, sizeof(buff), true);
     120           5 :   this->mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_STATUS] = statusWidth;
     121             : 
     122             :   // pass the replies
     123           5 :   if(stat1) { *stat1 = buff[0]; }
     124           5 :   if(stat2) { *stat2 = buff[1]; }
     125           5 :   if(irq)   { *irq = ((uint32_t)(buff[2]) << 24) | ((uint32_t)(buff[3]) << 16) | ((uint32_t)(buff[4]) << 8) | (uint32_t)buff[5]; }
     126             : 
     127           5 :   return(state);
     128             : }
     129             : 
     130           0 : int16_t LRxxxx::lrFhssBuildFrame(uint16_t cmd, uint8_t hdrCount, uint8_t cr, uint8_t grid, uint8_t hop, uint8_t bw, uint16_t hopSeq, int8_t devOffset, const uint8_t* payload, size_t len) {
     131             :   // check maximum size
     132           0 :   const uint8_t maxLen[4][4] = {
     133             :     { 189, 178, 167, 155, },
     134             :     { 151, 142, 133, 123, },
     135             :     { 112, 105,  99,  92, },
     136             :     {  74,  69,  65,  60, },
     137             :   };
     138           0 :   if((cr > RADIOLIB_LR_FHSS_CR_1_3) || ((hdrCount - 1) > (int)sizeof(maxLen[0])) || (len > maxLen[cr][hdrCount - 1])) {
     139           0 :     return(RADIOLIB_ERR_SPI_CMD_INVALID);
     140             :   }
     141             : 
     142             :   // build buffers
     143           0 :   size_t buffLen = 9 + len;
     144             :   #if RADIOLIB_STATIC_ONLY
     145             :     uint8_t dataBuff[9 + 190];
     146             :   #else
     147           0 :     uint8_t* dataBuff = new uint8_t[buffLen];
     148             :   #endif
     149             : 
     150             :   // set properties of the packet
     151           0 :   dataBuff[0] = hdrCount;
     152           0 :   dataBuff[1] = cr;
     153           0 :   dataBuff[2] = RADIOLIB_LRXXXX_LR_FHSS_MOD_TYPE_GMSK;
     154           0 :   dataBuff[3] = grid;
     155           0 :   dataBuff[4] = hop;
     156           0 :   dataBuff[5] = bw;
     157           0 :   dataBuff[6] = (uint8_t)((hopSeq >> 8) & 0x01);
     158           0 :   dataBuff[7] = (uint8_t)(hopSeq & 0xFF);
     159           0 :   dataBuff[8] = devOffset;
     160           0 :   memcpy(&dataBuff[9], payload, len);
     161             : 
     162           0 :   int16_t state = this->SPIcommand(cmd, true, dataBuff, buffLen);
     163             :   #if !RADIOLIB_STATIC_ONLY
     164           0 :     delete[] dataBuff;
     165             :   #endif
     166           0 :   return(state);
     167             : }
     168             : 
     169           3 : uint8_t LRxxxx::roundRampTime(uint32_t rampTimeUs) {
     170             :   uint8_t regVal;
     171             : 
     172             :   // Round up the ramp time to nearest discrete register value
     173           3 :   if(rampTimeUs <= 2) {
     174           0 :     regVal = RADIOLIB_LRXXXX_PA_RAMP_2U;
     175           3 :   } else if(rampTimeUs <= 4) {
     176           0 :     regVal = RADIOLIB_LRXXXX_PA_RAMP_4U;
     177           3 :   } else if(rampTimeUs <= 8) {
     178           0 :     regVal = RADIOLIB_LRXXXX_PA_RAMP_8U;
     179           3 :   } else if(rampTimeUs <= 16) {
     180           0 :     regVal = RADIOLIB_LRXXXX_PA_RAMP_16U;
     181           3 :   } else if(rampTimeUs <= 32) {
     182           0 :     regVal = RADIOLIB_LRXXXX_PA_RAMP_32U;
     183           3 :   } else if(rampTimeUs <= 48) {
     184           3 :     regVal = RADIOLIB_LRXXXX_PA_RAMP_48U;
     185           0 :   } else if(rampTimeUs <= 64) {
     186           0 :     regVal = RADIOLIB_LRXXXX_PA_RAMP_64U;
     187           0 :   } else if(rampTimeUs <= 80) {
     188           0 :     regVal = RADIOLIB_LRXXXX_PA_RAMP_80U;
     189           0 :   } else if(rampTimeUs <= 96) {
     190           0 :     regVal = RADIOLIB_LRXXXX_PA_RAMP_96U;
     191           0 :   } else if(rampTimeUs <= 112) {
     192           0 :     regVal = RADIOLIB_LRXXXX_PA_RAMP_112U;
     193           0 :   } else if(rampTimeUs <= 128) {
     194           0 :     regVal = RADIOLIB_LRXXXX_PA_RAMP_128U;
     195           0 :   } else if(rampTimeUs <= 144) {
     196           0 :     regVal = RADIOLIB_LRXXXX_PA_RAMP_144U;
     197           0 :   } else if(rampTimeUs <= 160) {
     198           0 :     regVal = RADIOLIB_LRXXXX_PA_RAMP_160U;
     199           0 :   } else if(rampTimeUs <= 176) {
     200           0 :     regVal = RADIOLIB_LRXXXX_PA_RAMP_176U;
     201           0 :   } else if(rampTimeUs <= 192) {
     202           0 :     regVal = RADIOLIB_LRXXXX_PA_RAMP_192U;
     203           0 :   } else if(rampTimeUs <= 208) {
     204           0 :     regVal = RADIOLIB_LRXXXX_PA_RAMP_208U;
     205           0 :   } else if(rampTimeUs <= 240) {
     206           0 :     regVal = RADIOLIB_LRXXXX_PA_RAMP_240U;
     207           0 :   } else if(rampTimeUs <= 272) {
     208           0 :     regVal = RADIOLIB_LRXXXX_PA_RAMP_272U;
     209             :   } else {  // 304
     210           0 :     regVal = RADIOLIB_LRXXXX_PA_RAMP_304U;
     211             :   }
     212             : 
     213           3 :   return regVal;
     214             : }
     215             : 
     216          10 : int16_t LRxxxx::setU32(uint16_t cmd, uint32_t u32) {
     217             :   uint8_t buff[] = { 
     218          10 :     (uint8_t)((u32 >> 24) & 0xFF), (uint8_t)((u32 >> 16) & 0xFF),
     219          10 :     (uint8_t)((u32 >> 8) & 0xFF), (uint8_t)(u32 & 0xFF),
     220          10 :   };
     221          20 :   return(this->SPIcommand(cmd, true, buff, sizeof(buff)));
     222             : }
     223             : 
     224        3270 : int16_t LRxxxx::SPIparseStatus(uint8_t in) {
     225        3270 :   if((in & 0b00001110) == RADIOLIB_LRXXXX_STAT_1_CMD_PERR) {
     226           0 :     return(RADIOLIB_ERR_SPI_CMD_INVALID);
     227        3270 :   } else if((in & 0b00001110) == RADIOLIB_LRXXXX_STAT_1_CMD_FAIL) {
     228           0 :     return(RADIOLIB_ERR_SPI_CMD_FAILED);
     229        3270 :   } else if((in == 0x00) || (in == 0xFF)) {
     230        3270 :     return(RADIOLIB_ERR_CHIP_NOT_FOUND);
     231             :   }
     232           0 :   return(RADIOLIB_ERR_NONE);
     233             : }
     234             : 
     235           0 : int16_t LRxxxx::SPIcheckStatus(Module* mod) {
     236             :   // the status check command doesn't return status in the same place as other read commands,
     237             :   // but only as the first byte (as with any other command), hence LR11x0::SPIcommand can't be used
     238             :   // it also seems to ignore the actual command, and just sending in bunch of NOPs will work 
     239           0 :   uint8_t buff[6] = { 0 };
     240           0 :   Module::BitWidth_t statusWidth = mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_STATUS];
     241           0 :   mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_STATUS] = Module::BITS_0;
     242           0 :   int16_t state = mod->SPItransferStream(NULL, 0, false, NULL, buff, sizeof(buff), true);
     243           0 :   mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_STATUS] = statusWidth;
     244           0 :   RADIOLIB_ASSERT(state);
     245           0 :   return(LRxxxx::SPIparseStatus(buff[0]));
     246             : }
     247             : 
     248           0 : int16_t LRxxxx::writeCommon(uint16_t cmd, uint32_t addrOffset, const uint32_t* data, size_t len, bool nonvolatile) {
     249             :   // build buffers - later we need to ensure endians are correct, 
     250             :   // so there is probably no way to do this without copying buffers and iterating
     251             :   // LR2021 has 24-bit address, whereas LR11x0 has 32-bit
     252           0 :   const size_t addrLen = (this->mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_ADDR] >= Module::BITS_32) ? 4 : 3;
     253           0 :   size_t buffLen = addrLen + len*sizeof(uint32_t);
     254             :   #if RADIOLIB_STATIC_ONLY
     255             :     uint8_t dataBuff[sizeof(uint32_t) + RADIOLIB_LRXXXX_SPI_MAX_READ_WRITE_LEN];
     256             :   #else
     257           0 :     uint8_t* dataBuff = new uint8_t[buffLen];
     258             :   #endif
     259             : 
     260             :   // set the address or offset
     261           0 :   uint8_t* dataBuffPtr = reinterpret_cast<uint8_t*>(dataBuff);
     262           0 :   if(addrLen == 4) {
     263           0 :     *(dataBuffPtr++) = (uint8_t)((addrOffset >> 24) & 0xFF);
     264             :   }
     265             :   
     266           0 :   *(dataBuffPtr++) = (uint8_t)((addrOffset >> 16) & 0xFF);
     267           0 :   *(dataBuffPtr++) = (uint8_t)((addrOffset >> 8) & 0xFF);
     268           0 :   *(dataBuffPtr++) = (uint8_t)(addrOffset & 0xFF);
     269             : 
     270             :   // convert endians
     271           0 :   for(size_t i = 0; i < len; i++) {
     272           0 :     uint32_t bin = 0;
     273           0 :     if(nonvolatile) {
     274           0 :       uint32_t* ptr = const_cast<uint32_t*>(data) + i;
     275           0 :       bin = RADIOLIB_NONVOLATILE_READ_DWORD(ptr);
     276             :     } else {
     277           0 :       bin = data[i];
     278             :     }
     279           0 :     *(dataBuffPtr++) = (uint8_t)((bin >> 24) & 0xFF);
     280           0 :     *(dataBuffPtr++) = (uint8_t)((bin >> 16) & 0xFF);
     281           0 :     *(dataBuffPtr++) = (uint8_t)((bin >> 8) & 0xFF);
     282           0 :     *(dataBuffPtr++) = (uint8_t)(bin & 0xFF);
     283             :   }
     284             : 
     285           0 :   int16_t state = this->mod->SPIwriteStream(cmd, dataBuff, buffLen, true, false);
     286             :   #if !RADIOLIB_STATIC_ONLY
     287           0 :     delete[] dataBuff;
     288             :   #endif
     289           0 :   return(state);
     290             : }
     291             : 
     292         204 : int16_t LRxxxx::SPIcommand(uint16_t cmd, bool write, uint8_t* data, size_t len, const uint8_t* out, size_t outLen) {
     293         204 :   int16_t state = RADIOLIB_ERR_UNKNOWN;
     294         204 :   if(!write) {
     295             :     // the SPI interface of LR11x0 requires two separate transactions for reading
     296             :     // send the 16-bit command
     297         142 :     state = this->mod->SPIwriteStream(cmd, out, outLen, true, false);
     298         142 :     RADIOLIB_ASSERT(state);
     299             : 
     300             :     // read the result without command
     301           0 :     this->mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_CMD] = Module::BITS_0;
     302           0 :     state = this->mod->SPIreadStream(RADIOLIB_LRXXXX_CMD_NOP, data, len, true, false);
     303           0 :     this->mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_CMD] = Module::BITS_16;
     304             : 
     305             :   } else {
     306             :     // write is just a single transaction
     307          62 :     state = this->mod->SPIwriteStream(cmd, data, len, true, true);
     308             :   
     309             :   }
     310             :   
     311          62 :   return(state);
     312             : }

Generated by: LCOV version 1.14