From b6b3eafa182ecb60d3ba9506a8acf099019e5993 Mon Sep 17 00:00:00 2001 From: GUVWAF Date: Sat, 13 Sep 2025 14:22:21 +0200 Subject: [PATCH] Add `calculateTimeOnAir()` method to relevant modules --- src/modules/LR11x0/LR11x0.cpp | 117 ++++++++++++++++++++++++++++++++++ src/modules/LR11x0/LR11x0.h | 14 +++- src/modules/SX126x/SX126x.cpp | 106 +++++++++++++++++++++++++++++- src/modules/SX126x/SX126x.h | 14 +++- src/modules/SX127x/SX1272.cpp | 3 + src/modules/SX127x/SX1272.h | 6 -- src/modules/SX127x/SX1278.cpp | 3 + src/modules/SX127x/SX1278.h | 6 -- src/modules/SX127x/SX127x.cpp | 97 +++++++++++++++++----------- src/modules/SX127x/SX127x.h | 18 +++++- src/modules/SX128x/SX128x.cpp | 96 ++++++++++++++++++++++++++++ src/modules/SX128x/SX128x.h | 16 ++++- 12 files changed, 441 insertions(+), 55 deletions(-) diff --git a/src/modules/LR11x0/LR11x0.cpp b/src/modules/LR11x0/LR11x0.cpp index 573cd855..eb7ed40f 100644 --- a/src/modules/LR11x0/LR11x0.cpp +++ b/src/modules/LR11x0/LR11x0.cpp @@ -1203,7 +1203,124 @@ size_t LR11x0::getPacketLength(bool update, uint8_t* offset) { return((size_t)len); } +RadioLibTime_t LR11x0::calculateTimeOnAir(ModemType_t modem, DataRate_t dr, PacketConfig_t pc, size_t len) { + // check active modem + if (modem == ModemType_t::RADIOLIB_MODEM_LORA) { + uint32_t symbolLength_us = ((uint32_t)(1000 * 10) << dr.lora.spreadingFactor) / (dr.lora.bandwidth * 10) ; + uint8_t sfCoeff1_x4 = 17; // (4.25 * 4) + uint8_t sfCoeff2 = 8; + if(dr.lora.spreadingFactor == 5 || dr.lora.spreadingFactor == 6) { + sfCoeff1_x4 = 25; // 6.25 * 4 + sfCoeff2 = 0; + } + uint8_t sfDivisor = 4*dr.lora.spreadingFactor; + if(pc.lora.ldrOptimize) { + sfDivisor = 4*(dr.lora.spreadingFactor - 2); + } + const int8_t bitsPerCrc = 16; + const int8_t N_symbol_header = pc.lora.implicitHeader ? 0 : 20; + + // numerator of equation in section 6.1.4 of SX1268 datasheet v1.1 (might not actually be bitcount, but it has len * 8) + int16_t bitCount = (int16_t) 8 * len + pc.lora.crcEnabled * bitsPerCrc - 4 * dr.lora.spreadingFactor + sfCoeff2 + N_symbol_header; + if(bitCount < 0) { + bitCount = 0; + } + // add (sfDivisor) - 1 to the numerator to give integer CEIL(...) + uint16_t nPreCodedSymbols = (bitCount + (sfDivisor - 1)) / (sfDivisor); + + // preamble can be 65k, therefore nSymbol_x4 needs to be 32 bit + uint32_t nSymbol_x4 = (pc.lora.preambleLength + 8) * 4 + sfCoeff1_x4 + nPreCodedSymbols * dr.lora.codingRate * 4; + + // get time-on-air in us + return((symbolLength_us * nSymbol_x4) / 4); + + } else if(modem == ModemType_t::RADIOLIB_MODEM_FSK) { + return((((float)(pc.fsk.crcLength * 8) + pc.fsk.syncWordLength + pc.fsk.preambleLength + (uint32_t)len * 8) / (dr.fsk.bitRate / 1000.0f))); + + } else if(modem == ModemType_t::RADIOLIB_MODEM_LRFHSS) { + // calculate the number of bits based on coding rate + uint16_t N_bits; + switch(dr.lrFhss.cr) { + case RADIOLIB_LR11X0_LR_FHSS_CR_5_6: + N_bits = ((len * 6) + 4) / 5; // this is from the official LR11xx driver, but why the extra +4? + break; + case RADIOLIB_LR11X0_LR_FHSS_CR_2_3: + N_bits = (len * 3) / 2; + break; + case RADIOLIB_LR11X0_LR_FHSS_CR_1_2: + N_bits = len * 2; + break; + case RADIOLIB_LR11X0_LR_FHSS_CR_1_3: + N_bits = len * 3; + break; + default: + return(RADIOLIB_ERR_INVALID_CODING_RATE); + } + + // calculate number of bits when accounting for unaligned last block + uint16_t N_payBits = (N_bits / RADIOLIB_LR11X0_LR_FHSS_FRAG_BITS) * RADIOLIB_LR11X0_LR_FHSS_BLOCK_BITS; + uint16_t N_lastBlockBits = N_bits % RADIOLIB_LR11X0_LR_FHSS_FRAG_BITS; + if(N_lastBlockBits) { + N_payBits += N_lastBlockBits + 2; + } + + // add header bits + uint16_t N_totalBits = (RADIOLIB_LR11X0_LR_FHSS_HEADER_BITS * pc.lrFhss.hdrCount) + N_payBits; + return(((uint32_t)N_totalBits * 8 * 1000000UL) / RADIOLIB_LR11X0_LR_FHSS_BIT_RATE); + + } else { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + return(0); +} + RadioLibTime_t LR11x0::getTimeOnAir(size_t len) { + ModemType_t modem; + int32_t state = this->getModem(&modem); + RADIOLIB_ASSERT(state); + + DataRate_t dr = {}; + PacketConfig_t pc = {}; + switch(modem) { + case ModemType_t::RADIOLIB_MODEM_LORA: { + uint8_t cr = this->codingRate; + // We assume same calculation for short and long interleaving, so map CR values 0-4 and 5-7 to the same values + if (cr < 5) { + cr = cr + 4; + } else if (cr == 7) { + cr = cr + 1; + } + dr = {.lora = { .spreadingFactor = this->spreadingFactor, .bandwidth = this->bandwidthKhz, .codingRate = cr } }; + pc = {.lora = { .preambleLength = this->preambleLengthLoRa, .implicitHeader = (this->headerType == RADIOLIB_LR11X0_LORA_HEADER_IMPLICIT) ? true : false, \ + .crcEnabled = (this->crcTypeLoRa == RADIOLIB_LR11X0_LORA_CRC_ENABLED) ? true : false, \ + .ldrOptimize = (bool)this->ldrOptimize } }; + break; + } + case ModemType_t::RADIOLIB_MODEM_FSK: { + dr = {.fsk = { .bitRate = (float)this->bitRate / 1000.0f, .freqDev = (float)this->frequencyDev } }; + uint8_t crcLen = 0; + if(this->crcTypeGFSK == RADIOLIB_LR11X0_GFSK_CRC_1_BYTE || this->crcTypeGFSK == RADIOLIB_LR11X0_GFSK_CRC_1_BYTE_INV) { + crcLen = 1; + } else if(this->crcTypeGFSK == RADIOLIB_LR11X0_GFSK_CRC_2_BYTE || this->crcTypeGFSK == RADIOLIB_LR11X0_GFSK_CRC_2_BYTE_INV) { + crcLen = 2; + } + pc = {.fsk = { .preambleLength = this->preambleLengthGFSK, .syncWordLength = this->syncWordLength, .crcLength = crcLen } }; + break; + } + case ModemType_t::RADIOLIB_MODEM_LRFHSS: { + dr = {.lrFhss = { .bw = this->lrFhssBw, .cr = this->lrFhssCr, .narrowGrid = (this->lrFhssGrid == RADIOLIB_LR11X0_LR_FHSS_GRID_STEP_NON_FCC) ? true : false } }; + pc = {.lrFhss = { .hdrCount = this->lrFhssHdrCount } }; + break; + } + default: + return(RADIOLIB_ERR_WRONG_MODEM); + } + + return(this->calculateTimeOnAir(modem, dr, pc, len)); +} + +RadioLibTime_t LR11x0::getTimeOnAir_old(size_t len) { // check active modem uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; (void)getPacketType(&type); diff --git a/src/modules/LR11x0/LR11x0.h b/src/modules/LR11x0/LR11x0.h index 4a9f30e6..32b72032 100644 --- a/src/modules/LR11x0/LR11x0.h +++ b/src/modules/LR11x0/LR11x0.h @@ -1372,6 +1372,16 @@ class LR11x0: public PhysicalLayer { */ int16_t getLoRaRxHeaderInfo(uint8_t* cr, bool* hasCRC); + /*! + \brief Calculate the expected time-on-air for a given modem, data rate, packet configuration and payload size. + \param modem Modem type. + \param dr Data rate. + \param pc Packet config. + \param len Payload length in bytes. + \returns Expected time-on-air in microseconds. + */ + RadioLibTime_t calculateTimeOnAir(ModemType_t modem, DataRate_t dr, PacketConfig_t pc, size_t len); + /*! \brief Get expected time-on-air for a given size of payload \param len Payload length in bytes. @@ -1379,6 +1389,8 @@ class LR11x0: public PhysicalLayer { */ RadioLibTime_t getTimeOnAir(size_t len) override; + RadioLibTime_t getTimeOnAir_old(size_t len); + /*! \brief Calculate the timeout value for this specific module / series (in number of symbols or units of time) \param timeoutUs Timeout in microseconds to listen for @@ -1688,7 +1700,7 @@ class LR11x0: public PhysicalLayer { int16_t resetStats(void); int16_t getStats(uint16_t* nbPktReceived, uint16_t* nbPktCrcError, uint16_t* data1, uint16_t* data2); - int16_t getPacketType(uint8_t* type); + virtual int16_t getPacketType(uint8_t* type); int16_t getRxBufferStatus(uint8_t* len, uint8_t* startOffset); int16_t getPacketStatusLoRa(float* rssiPkt, float* snrPkt, float* signalRssiPkt); int16_t getPacketStatusGFSK(float* rssiSync, float* rssiAvg, uint8_t* rxLen, uint8_t* stat); diff --git a/src/modules/SX126x/SX126x.cpp b/src/modules/SX126x/SX126x.cpp index 1eec8e2f..a2273beb 100644 --- a/src/modules/SX126x/SX126x.cpp +++ b/src/modules/SX126x/SX126x.cpp @@ -1348,7 +1348,7 @@ int16_t SX126x::variablePacketLengthMode(uint8_t maxLen) { return(setPacketMode(RADIOLIB_SX126X_GFSK_PACKET_VARIABLE, maxLen)); } -RadioLibTime_t SX126x::getTimeOnAir(size_t len) { +RadioLibTime_t SX126x::getTimeOnAir_old(size_t len) { // everything is in microseconds to allow integer arithmetic // some constants have .25, these are multiplied by 4, and have _x4 postfix to indicate that fact uint8_t modem = getPacketType(); @@ -1419,6 +1419,110 @@ RadioLibTime_t SX126x::getTimeOnAir(size_t len) { return(RADIOLIB_ERR_UNKNOWN); } +RadioLibTime_t SX126x::calculateTimeOnAir(ModemType_t modem, DataRate_t dr, PacketConfig_t pc, size_t len) { + // everything is in microseconds to allow integer arithmetic + // some constants have .25, these are multiplied by 4, and have _x4 postfix to indicate that fact + switch (modem) { + case RADIOLIB_MODEM_LORA: { + uint32_t symbolLength_us = ((uint32_t)(1000 * 10) << dr.lora.spreadingFactor) / (dr.lora.bandwidth * 10) ; + uint8_t sfCoeff1_x4 = 17; // (4.25 * 4) + uint8_t sfCoeff2 = 8; + if(dr.lora.spreadingFactor == 5 || dr.lora.spreadingFactor == 6) { + sfCoeff1_x4 = 25; // 6.25 * 4 + sfCoeff2 = 0; + } + uint8_t sfDivisor = 4*dr.lora.spreadingFactor; + if(pc.lora.ldrOptimize) { + sfDivisor = 4*(dr.lora.spreadingFactor - 2); + } + const int8_t bitsPerCrc = 16; + const int8_t N_symbol_header = pc.lora.implicitHeader ? 0 : 20; + + // numerator of equation in section 6.1.4 of SX1268 datasheet v1.1 (might not actually be bitcount, but it has len * 8) + int16_t bitCount = (int16_t) 8 * len + pc.lora.crcEnabled * bitsPerCrc - 4 * dr.lora.spreadingFactor + sfCoeff2 + N_symbol_header; + if(bitCount < 0) { + bitCount = 0; + } + // add (sfDivisor) - 1 to the numerator to give integer CEIL(...) + uint16_t nPreCodedSymbols = (bitCount + (sfDivisor - 1)) / (sfDivisor); + + // preamble can be 65k, therefore nSymbol_x4 needs to be 32 bit + uint32_t nSymbol_x4 = (pc.lora.preambleLength + 8) * 4 + sfCoeff1_x4 + nPreCodedSymbols * dr.lora.codingRate * 4; + + return((symbolLength_us * nSymbol_x4) / 4); + } + case RADIOLIB_MODEM_FSK: { + return((((float)(pc.fsk.crcLength * 8) + pc.fsk.syncWordLength + pc.fsk.preambleLength + (uint32_t)len * 8) / (dr.fsk.bitRate / 1000.0f))); + } + case RADIOLIB_MODEM_LRFHSS: { + // calculate the number of bits based on coding rate + uint16_t N_bits; + switch(dr.lrFhss.cr) { + case RADIOLIB_SX126X_LR_FHSS_CR_5_6: + N_bits = ((len * 6) + 4) / 5; // this is from the official LR11xx driver, but why the extra +4? + break; + case RADIOLIB_SX126X_LR_FHSS_CR_2_3: + N_bits = (len * 3) / 2; + break; + case RADIOLIB_SX126X_LR_FHSS_CR_1_2: + N_bits = len * 2; + break; + case RADIOLIB_SX126X_LR_FHSS_CR_1_3: + N_bits = len * 3; + break; + default: + return(RADIOLIB_ERR_INVALID_CODING_RATE); + } + + // calculate number of bits when accounting for unaligned last block + uint16_t N_payBits = (N_bits / RADIOLIB_SX126X_LR_FHSS_FRAG_BITS) * RADIOLIB_SX126X_LR_FHSS_BLOCK_BITS; + uint16_t N_lastBlockBits = N_bits % RADIOLIB_SX126X_LR_FHSS_FRAG_BITS; + if(N_lastBlockBits) { + N_payBits += N_lastBlockBits + 2; + } + + // add header bits + uint16_t N_totalBits = (RADIOLIB_SX126X_LR_FHSS_HEADER_BITS * pc.lrFhss.hdrCount) + N_payBits; + return(((uint32_t)N_totalBits * 8 * 1000000UL) / 488.28215f); + } + default: + return(RADIOLIB_ERR_WRONG_MODEM); + } + + return(RADIOLIB_ERR_UNKNOWN); +} + +RadioLibTime_t SX126x::getTimeOnAir(size_t len) { + uint8_t type = getPacketType(); + ModemType_t modem = RADIOLIB_MODEM_LORA; + DataRate_t dataRate = {}; + PacketConfig_t packetConfig = {}; + + if(type == RADIOLIB_SX126X_PACKET_TYPE_LORA) { + dataRate = {.lora = {.spreadingFactor = this->spreadingFactor, .bandwidth = this->bandwidthKhz, .codingRate = (uint8_t)(this->codingRate + 4) } }; + packetConfig = {.lora = {.preambleLength = this->preambleLengthLoRa, .implicitHeader = this->headerType == RADIOLIB_SX126X_LORA_HEADER_IMPLICIT, .crcEnabled = (bool)this->crcTypeLoRa, .ldrOptimize = (bool)this->ldrOptimize}}; + } else if(type == RADIOLIB_SX126X_PACKET_TYPE_GFSK) { + modem = RADIOLIB_MODEM_FSK; + float bitRate = RADIOLIB_SX126X_CRYSTAL_FREQ * 32.0f * 1000.0f / (float)this->bitRate; + dataRate = {.fsk = {.bitRate = bitRate, .freqDev = (float)this->frequencyDev }}; + uint8_t crcLen = 0; + if(this->crcTypeFSK == RADIOLIB_SX126X_GFSK_CRC_1_BYTE || this->crcTypeFSK == RADIOLIB_SX126X_GFSK_CRC_1_BYTE_INV) { + crcLen = 1; + } else if(this->crcTypeFSK == RADIOLIB_SX126X_GFSK_CRC_2_BYTE || this->crcTypeFSK == RADIOLIB_SX126X_GFSK_CRC_2_BYTE_INV) { + crcLen = 2; + } + packetConfig = {.fsk = {.preambleLength = this->preambleLengthFSK, .syncWordLength = this->syncWordLength, .crcLength = crcLen}}; + } else if(type == RADIOLIB_SX126X_PACKET_TYPE_LR_FHSS) { + modem = RADIOLIB_MODEM_LRFHSS; + dataRate = {.lrFhss = {.bw = this->lrFhssBw, .cr = this->lrFhssCr, .narrowGrid = this->lrFhssGridNonFcc }}; + packetConfig = {.lrFhss = {.hdrCount = this->lrFhssHdrCount}}; + } else { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + return(calculateTimeOnAir(modem, dataRate, packetConfig, len)); +} + RadioLibTime_t SX126x::calculateRxTimeout(RadioLibTime_t timeoutUs) { // the timeout value is given in units of 15.625 microseconds // the calling function should provide some extra width, as this number of units is truncated to integer diff --git a/src/modules/SX126x/SX126x.h b/src/modules/SX126x/SX126x.h index 28173994..02e410c0 100644 --- a/src/modules/SX126x/SX126x.h +++ b/src/modules/SX126x/SX126x.h @@ -1008,6 +1008,16 @@ class SX126x: public PhysicalLayer { */ int16_t variablePacketLengthMode(uint8_t maxLen = RADIOLIB_SX126X_MAX_PACKET_LENGTH); + /*! + \brief Calculate the expected time-on-air for a given modem, data rate, packet configuration and payload size. + \param modem Modem type. + \param dr Data rate. + \param pc Packet config. + \param len Payload length in bytes. + \returns Expected time-on-air in microseconds. + */ + RadioLibTime_t calculateTimeOnAir(ModemType_t modem, DataRate_t dr, PacketConfig_t pc, size_t len); + /*! \brief Get expected time-on-air for a given size of payload \param len Payload length in bytes. @@ -1015,6 +1025,8 @@ class SX126x: public PhysicalLayer { */ RadioLibTime_t getTimeOnAir(size_t len) override; + RadioLibTime_t getTimeOnAir_old(size_t len); + /*! \brief Calculate the timeout value for this specific module / series (in number of symbols or units of time) \param timeoutUs Timeout in microseconds to listen for @@ -1231,7 +1243,7 @@ class SX126x: public PhysicalLayer { virtual int16_t clearIrqStatus(uint16_t clearIrqParams = RADIOLIB_SX126X_IRQ_ALL); int16_t setRfFrequency(uint32_t frf); int16_t calibrateImage(const uint8_t* data); - uint8_t getPacketType(); + virtual uint8_t getPacketType(); int16_t setTxParams(uint8_t power, uint8_t rampTime); int16_t setModulationParams(uint8_t sf, uint8_t bw, uint8_t cr, uint8_t ldro); int16_t setModulationParamsFSK(uint32_t br, uint8_t sh, uint8_t rxBw, uint32_t freqDev); diff --git a/src/modules/SX127x/SX1272.cpp b/src/modules/SX127x/SX1272.cpp index 313cbff0..ce4682be 100644 --- a/src/modules/SX127x/SX1272.cpp +++ b/src/modules/SX127x/SX1272.cpp @@ -122,8 +122,10 @@ int16_t SX1272::setBandwidth(float bw) { float symbolLength = (float)(uint32_t(1) << SX127x::spreadingFactor) / (float)SX127x::bandwidth; Module* mod = this->getMod(); if(symbolLength >= 16.0f) { + this->ldroEnabled = true; state = mod->SPIsetRegValue(RADIOLIB_SX127X_REG_MODEM_CONFIG_1, RADIOLIB_SX1272_LOW_DATA_RATE_OPT_ON, 0, 0); } else { + this->ldroEnabled = false; state = mod->SPIsetRegValue(RADIOLIB_SX127X_REG_MODEM_CONFIG_1, RADIOLIB_SX1272_LOW_DATA_RATE_OPT_OFF, 0, 0); } } @@ -483,6 +485,7 @@ int16_t SX1272::forceLDRO(bool enable) { } this->ldroAuto = false; + this->ldroEnabled = enable; Module* mod = this->getMod(); if(enable) { return(mod->SPIsetRegValue(RADIOLIB_SX127X_REG_MODEM_CONFIG_1, RADIOLIB_SX1272_LOW_DATA_RATE_OPT_ON, 0, 0)); diff --git a/src/modules/SX127x/SX1272.h b/src/modules/SX127x/SX1272.h index 92760148..5a269276 100644 --- a/src/modules/SX127x/SX1272.h +++ b/src/modules/SX127x/SX1272.h @@ -324,12 +324,6 @@ class SX1272: public SX127x { int16_t configFSK() override; void errataFix(bool rx) override; -#if !RADIOLIB_GODMODE - private: -#endif - bool ldroAuto = true; - bool ldroEnabled = false; - }; #endif diff --git a/src/modules/SX127x/SX1278.cpp b/src/modules/SX127x/SX1278.cpp index dab91a5a..14335fbc 100644 --- a/src/modules/SX127x/SX1278.cpp +++ b/src/modules/SX127x/SX1278.cpp @@ -136,8 +136,10 @@ int16_t SX1278::setBandwidth(float bw) { float symbolLength = (float)(uint32_t(1) << SX127x::spreadingFactor) / (float)SX127x::bandwidth; Module* mod = this->getMod(); if(symbolLength >= 16.0f) { + this->ldroEnabled = true; state = mod->SPIsetRegValue(RADIOLIB_SX1278_REG_MODEM_CONFIG_3, RADIOLIB_SX1278_LOW_DATA_RATE_OPT_ON, 3, 3); } else { + this->ldroEnabled = false; state = mod->SPIsetRegValue(RADIOLIB_SX1278_REG_MODEM_CONFIG_3, RADIOLIB_SX1278_LOW_DATA_RATE_OPT_OFF, 3, 3); } } @@ -522,6 +524,7 @@ int16_t SX1278::forceLDRO(bool enable) { Module* mod = this->getMod(); this->ldroAuto = false; + this->ldroEnabled = enable; if(enable) { return(mod->SPIsetRegValue(RADIOLIB_SX1278_REG_MODEM_CONFIG_3, RADIOLIB_SX1278_LOW_DATA_RATE_OPT_ON, 3, 3)); } else { diff --git a/src/modules/SX127x/SX1278.h b/src/modules/SX127x/SX1278.h index 9155db9d..3cfcb19c 100644 --- a/src/modules/SX127x/SX1278.h +++ b/src/modules/SX127x/SX1278.h @@ -336,12 +336,6 @@ class SX1278: public SX127x { int16_t configFSK() override; void errataFix(bool rx) override; -#if !RADIOLIB_GODMODE - private: -#endif - bool ldroAuto = true; - bool ldroEnabled = false; - }; /*! diff --git a/src/modules/SX127x/SX127x.cpp b/src/modules/SX127x/SX127x.cpp index d13064b6..c4565515 100644 --- a/src/modules/SX127x/SX127x.cpp +++ b/src/modules/SX127x/SX127x.cpp @@ -833,6 +833,8 @@ int16_t SX127x::setFrequencyDeviation(float freqDev) { return(RADIOLIB_ERR_INVALID_FREQUENCY_DEVIATION); } + this->frequencyDev = newFreqDev; + // set mode to STANDBY int16_t state = setMode(RADIOLIB_SX127X_STANDBY); RADIOLIB_ASSERT(state); @@ -1127,68 +1129,91 @@ int16_t SX127x::variablePacketLengthMode(uint8_t maxLen) { return(SX127x::setPacketMode(RADIOLIB_SX127X_PACKET_VARIABLE, maxLen)); } -float SX127x::getNumSymbols(size_t len) { - // get symbol length in us - float symbolLength = (float) (uint32_t(1) << this->spreadingFactor) / (float) this->bandwidth; - +float SX127x::getNumSymbols(size_t len, DataRate_t dr, PacketConfig_t pc) { // get Low Data Rate optimization flag - float de = 0; - if (symbolLength >= 16.0f) { - de = 1; - } + float de = pc.lora.ldrOptimize ? 1.0f : 0.0f; // get explicit/implicit header enabled flag - float ih = (float) this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_MODEM_CONFIG_1, 0, 0); - + float ih = (float) pc.lora.implicitHeader; + // get CRC enabled flag - float crc = (float) (this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_MODEM_CONFIG_2, 2, 2) >> 2); + float crc = (float) pc.lora.crcEnabled; // get number of preamble symbols - float n_pre = (float) ((this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_PREAMBLE_MSB) << 8) | this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_PREAMBLE_LSB)); + float n_pre = (float) pc.lora.preambleLength; // get number of payload symbols - float n_pay = 8.0f + RADIOLIB_MAX(ceilf((8.0f * (float) len - 4.0f * (float) this->spreadingFactor + 28.0f + 16.0f * crc - 20.0f * ih) / (4.0f * (float) this->spreadingFactor - 8.0f * de)) * (float) this->codingRate, 0.0f); + float n_pay = 8.0f + RADIOLIB_MAX(ceilf((8.0f * (float) len - 4.0f * (float) dr.lora.spreadingFactor + 28.0f + 16.0f * crc - 20.0f * ih) / (4.0f * (float) dr.lora.spreadingFactor - 8.0f * de)) * (float) dr.lora.codingRate, 0.0f); // add 4.25 symbols for the sync return(n_pre + n_pay + 4.25f); } -RadioLibTime_t SX127x::getTimeOnAir(size_t len) { - // check active modem - uint8_t modem = getActiveModem(); - if (modem == RADIOLIB_SX127X_LORA) { +RadioLibTime_t SX127x::calculateTimeOnAir(ModemType_t modem, DataRate_t dr, PacketConfig_t pc, size_t len) { + if (modem == RADIOLIB_MODEM_LORA) { // get symbol length in us - float symbolLength = (float) (uint32_t(1) << this->spreadingFactor) / (float) this->bandwidth; + float symbolLength = (float) (uint32_t(1) << dr.lora.spreadingFactor) / (float) dr.lora.bandwidth; // get number of symbols - float n_sym = getNumSymbols(len); + float n_sym = getNumSymbols(len, dr, pc); // get time-on-air in us return ceil((double)symbolLength * (double)n_sym) * 1000; - } else if(modem == RADIOLIB_SX127X_FSK_OOK) { - // get number of bits preamble - float n_pre = (float) ((this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_PREAMBLE_MSB_FSK) << 8) | this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_PREAMBLE_LSB_FSK)) * 8; - // get the number of bits of the sync word - float n_syncWord = (float) (this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_SYNC_CONFIG, 2, 0) + 1) * 8; - // get CRC bits - float crc = (this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_PACKET_CONFIG_1, 4, 4) == RADIOLIB_SX127X_CRC_ON) * 16; - - if (this->packetLengthConfig == RADIOLIB_SX127X_PACKET_FIXED) { - // if packet size fixed -> len = fixed packet length - len = this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_PAYLOAD_LENGTH_FSK); - } else { - // if packet variable -> Add 1 extra byte for payload length - len += 1; - } - + } else if(modem == RADIOLIB_MODEM_FSK) { + // calculate time-on-air in us {[(length in bytes) * (8 bits / 1 byte)] / [(Bit Rate in kbps) * (1000 bps / 1 kbps)]} * (1000000 us in 1 sec) - return((uint32_t) (((crc + n_syncWord + n_pre + (float) (len * 8)) / (this->bitRate * 1000.0f)) * 1000000.0f)); + return((uint32_t) ((((float)(pc.fsk.crcLength * 8) + pc.fsk.syncWordLength + pc.fsk.preambleLength + (float) (len * 8)) / (dr.fsk.bitRate * 1000.0f)) * 1000000.0f)); + } else { + return(RADIOLIB_ERR_WRONG_MODEM); } return(RADIOLIB_ERR_UNKNOWN); } +RadioLibTime_t SX127x::getTimeOnAir(size_t len) { + uint8_t modem = getActiveModem(); + DataRate_t dataRate = {}; + PacketConfig_t packetConfig = {}; + + switch (modem) { + case(RADIOLIB_SX127X_LORA): { + // Get number of preamble symbols + uint16_t n_pre = ((this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_PREAMBLE_MSB) << 8) | this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_PREAMBLE_LSB)); + + dataRate = { .lora = { .spreadingFactor = this->spreadingFactor, .bandwidth = this->bandwidth, .codingRate = this->codingRate } }; + + packetConfig = { .lora = { .preambleLength = n_pre, .implicitHeader = this->implicitHdr, .crcEnabled = this->crcEnabled, .ldrOptimize = this->ldroEnabled } }; + + return(calculateTimeOnAir((ModemType_t)RADIOLIB_MODEM_LORA, dataRate, packetConfig, len)); + } + case(RADIOLIB_SX127X_FSK_OOK): { + dataRate = { .fsk = { .bitRate = this->bitRate, .freqDev = this->frequencyDev } }; + + // get number of bits preamble + uint16_t n_pre = (uint16_t) ((this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_PREAMBLE_MSB_FSK) << 8) | this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_PREAMBLE_LSB_FSK)) * 8; + // get the number of bits of the sync word + uint8_t n_syncWord = (uint8_t) ((this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_SYNC_CONFIG, 2, 0) + 1) * 8); + // get CRC enabled status + bool crcEnabled = (this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_PACKET_CONFIG_1, 4, 4) == RADIOLIB_SX127X_CRC_ON); + + if (this->packetLengthConfig == RADIOLIB_SX127X_PACKET_FIXED) { + // if packet size fixed -> len = fixed packet length + len = this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_PAYLOAD_LENGTH_FSK); + } else { + // if packet variable -> Add 1 extra byte for payload length + len += 1; + } + + packetConfig = { .fsk = { .preambleLength = n_pre, .syncWordLength = n_syncWord, .crcLength = (uint8_t)(crcEnabled * 2) } }; + + return(calculateTimeOnAir((ModemType_t)RADIOLIB_MODEM_FSK, dataRate, packetConfig, len)); + } + default: + return(RADIOLIB_ERR_WRONG_MODEM); + } +} + RadioLibTime_t SX127x::calculateRxTimeout(RadioLibTime_t timeoutUs) { RadioLibTime_t timeout = 0; if(getActiveModem() == RADIOLIB_SX127X_LORA) { diff --git a/src/modules/SX127x/SX127x.h b/src/modules/SX127x/SX127x.h index db0d8e80..7db70591 100644 --- a/src/modules/SX127x/SX127x.h +++ b/src/modules/SX127x/SX127x.h @@ -1040,9 +1040,11 @@ class SX127x: public PhysicalLayer { /*! \brief Convert from bytes to LoRa symbols. \param len Payload length in bytes. + \param dr Data rate. + \param pc Packet configuration. \returns The total number of LoRa symbols, including preamble, sync and possible header. */ - float getNumSymbols(size_t len); + float getNumSymbols(size_t len, DataRate_t dr, PacketConfig_t pc); /*! \brief Get expected time-on-air for a given size of payload. @@ -1051,6 +1053,16 @@ class SX127x: public PhysicalLayer { */ RadioLibTime_t getTimeOnAir(size_t len) override; + /*! + \brief Calculate the expected time-on-air for a given modem, data rate, packet configuration and payload size. + \param modem Modem type. + \param dr Data rate. + \param pc Packet config. + \param len Payload length in bytes. + \returns Expected time-on-air in microseconds. + */ + RadioLibTime_t calculateTimeOnAir(ModemType_t modem, DataRate_t dr, PacketConfig_t pc, size_t len); + /*! \brief Calculate the timeout value for this specific module / series (in number of symbols or units of time) \param timeoutUs Timeout in microseconds to listen for @@ -1251,6 +1263,8 @@ class SX127x: public PhysicalLayer { bool crcEnabled = false; bool ookEnabled = false; bool implicitHdr = false; + bool ldroAuto = false; + bool ldroEnabled = false; virtual int16_t configFSK(); int16_t getActiveModem(); @@ -1264,7 +1278,7 @@ class SX127x: public PhysicalLayer { #endif Module* mod; - float bitRate = 0; + float bitRate = 0, frequencyDev = 0; bool crcOn = true; // default value used in FSK mode float dataRate = 0; bool packetLengthQueried = false; // FSK packet length is the first byte in FIFO, length can only be queried once diff --git a/src/modules/SX128x/SX128x.cpp b/src/modules/SX128x/SX128x.cpp index 89e16332..323eb9d6 100644 --- a/src/modules/SX128x/SX128x.cpp +++ b/src/modules/SX128x/SX128x.cpp @@ -969,6 +969,7 @@ int16_t SX128x::setFrequencyDeviation(float freqDev) { } // update modulation parameters + this->frequencyDev = newFreqDev; this->modIndex = modInd; return(setModulationParams(this->bitRate, this->modIndex, this->shaping)); } @@ -1334,7 +1335,102 @@ int16_t SX128x::variablePacketLengthMode(uint8_t maxLen) { return(setPacketMode(RADIOLIB_SX128X_GFSK_FLRC_PACKET_VARIABLE, maxLen)); } +RadioLibTime_t SX128x::calculateTimeOnAir(ModemType_t modem, DataRate_t dr, PacketConfig_t pc, size_t len) { + switch(modem) { + case (ModemType_t::RADIOLIB_MODEM_LORA): { + // calculate number of symbols + float N_symbol = 0; + uint8_t sf = dr.lora.spreadingFactor; + float cr = (float)dr.lora.codingRate; + + // get SF coefficients + float coeff1 = 0; + int16_t coeff2 = 0; + int16_t coeff3 = 0; + if(sf < 7) { + // SF5, SF6 + coeff1 = 6.25; + coeff2 = 4*sf; + coeff3 = 4*sf; + } else if(sf < 11) { + // SF7. SF8, SF9, SF10 + coeff1 = 4.25; + coeff2 = 4*sf + 8; + coeff3 = 4*sf; + } else { + // SF11, SF12 + coeff1 = 4.25; + coeff2 = 4*sf + 8; + coeff3 = 4*(sf - 2); + } + + // get CRC length + int16_t N_bitCRC = 16; + if(!pc.lora.crcEnabled) { + N_bitCRC = 0; + } + + // get header length + int16_t N_symbolHeader = 20; + if(pc.lora.implicitHeader) { + N_symbolHeader = 0; + } + + // calculate number of LoRa preamble symbols + uint32_t N_symbolPreamble = pc.lora.preambleLength; + + // calculate the number of symbols + N_symbol = (float)N_symbolPreamble + coeff1 + 8.0f + ceilf((float)RADIOLIB_MAX((int16_t)(8 * len + N_bitCRC - coeff2 + N_symbolHeader), (int16_t)0) / (float)coeff3) * cr; + + // get time-on-air in us + return(((uint32_t(1) << sf) / dr.lora.bandwidth) * N_symbol * 1000.0f); + } + case (ModemType_t::RADIOLIB_MODEM_FSK): + return((((float)(pc.fsk.crcLength * 8) + pc.fsk.syncWordLength + pc.fsk.preambleLength + (uint32_t)len * 8) / (dr.fsk.bitRate / 1000.0f))); + + default: + return(RADIOLIB_ERR_WRONG_MODEM); + } + +} + RadioLibTime_t SX128x::getTimeOnAir(size_t len) { + // check active modem + uint8_t modem = getPacketType(); + DataRate_t dr; + PacketConfig_t pc; + + if(modem == RADIOLIB_SX128X_PACKET_TYPE_LORA) { + uint8_t sf = this->spreadingFactor >> 4; + uint8_t cr = this->codingRateLoRa; + // We assume same calculation for short and long interleaving, so map CR values 0-4 and 5-7 to the same values + if (cr < 5) { + cr = cr + 4; + } else if (cr == 7) { + cr = cr + 1; + } + dr = {.lora = {.spreadingFactor = sf, .bandwidth = this->bandwidthKhz, .codingRate = cr}}; + + uint16_t preambleLength = (this->preambleLengthLoRa & 0x0F) * (uint32_t(1) << ((this->preambleLengthLoRa & 0xF0) >> 4)); + pc = {.lora = { .preambleLength = preambleLength, .implicitHeader = this->headerType == RADIOLIB_SX128X_LORA_HEADER_IMPLICIT, .crcEnabled = this->crcLoRa == RADIOLIB_SX128X_LORA_CRC_ON, .ldrOptimize = false }}; + + return(calculateTimeOnAir(ModemType_t::RADIOLIB_MODEM_LORA, dr, pc, len)); + } else if (modem == RADIOLIB_SX128X_PACKET_TYPE_GFSK) { + dr = {.fsk = {.bitRate = (float)this->bitRateKbps, .freqDev = this->frequencyDev}}; + + uint8_t crcLength = this->crcGFSK >> 4; + uint16_t preambleLength = (this->preambleLengthGFSK >> 2) + 4; + uint8_t syncWordLen = ((this->syncWordLen >> 1) + 1) * 8; + pc = {.fsk = {.preambleLength = preambleLength, .syncWordLength = syncWordLen, .crcLength = crcLength}}; + + return(calculateTimeOnAir(ModemType_t::RADIOLIB_MODEM_FSK, dr, pc, len)); + } else { + return(RADIOLIB_ERR_WRONG_MODEM); + } + +} + +RadioLibTime_t SX128x::getTimeOnAir_old(size_t len) { // check active modem uint8_t modem = getPacketType(); if(modem == RADIOLIB_SX128X_PACKET_TYPE_LORA) { diff --git a/src/modules/SX128x/SX128x.h b/src/modules/SX128x/SX128x.h index e4d85359..db3a5a42 100644 --- a/src/modules/SX128x/SX128x.h +++ b/src/modules/SX128x/SX128x.h @@ -828,6 +828,16 @@ class SX128x: public PhysicalLayer { */ int16_t variablePacketLengthMode(uint8_t maxLen = RADIOLIB_SX128X_MAX_PACKET_LENGTH); + /*! + \brief Calculate the expected time-on-air for a given modem, data rate, packet configuration and payload size. + \param modem Modem type. + \param dr Data rate. + \param pc Packet config. + \param len Payload length in bytes. + \returns Expected time-on-air in microseconds. + */ + RadioLibTime_t calculateTimeOnAir(ModemType_t modem, DataRate_t dr, PacketConfig_t pc, size_t len); + /*! \brief Get expected time-on-air for a given size of payload. \param len Payload length in bytes. @@ -835,6 +845,8 @@ class SX128x: public PhysicalLayer { */ RadioLibTime_t getTimeOnAir(size_t len) override; + RadioLibTime_t getTimeOnAir_old(size_t len); + /*! \brief Set implicit header mode for future reception/transmission. \returns \ref status_codes @@ -913,7 +925,7 @@ class SX128x: public PhysicalLayer { int16_t setTx(uint16_t periodBaseCount = RADIOLIB_SX128X_TX_TIMEOUT_NONE, uint8_t periodBase = RADIOLIB_SX128X_PERIOD_BASE_15_625_US); int16_t setRx(uint16_t periodBaseCount, uint8_t periodBase = RADIOLIB_SX128X_PERIOD_BASE_15_625_US); int16_t setCad(uint8_t symbolNum); - uint8_t getPacketType(); + virtual uint8_t getPacketType(); int16_t setRfFrequency(uint32_t frf); int16_t setTxParams(uint8_t pwr, uint8_t rampTime = RADIOLIB_SX128X_PA_RAMP_10_US); int16_t setBufferBaseAddress(uint8_t txBaseAddress = 0x00, uint8_t rxBaseAddress = 0x00); @@ -942,7 +954,7 @@ class SX128x: public PhysicalLayer { uint8_t invertIQEnabled = RADIOLIB_SX128X_LORA_IQ_STANDARD; // cached GFSK parameters - float modIndexReal = 0; + float modIndexReal = 0, frequencyDev = 0; uint16_t bitRateKbps = 0; uint8_t bitRate = 0, modIndex = 0, shaping = 0; uint8_t preambleLengthGFSK = 0, syncWordLen = 0, syncWordMatch = 0, crcGFSK = 0, whitening = 0;