GUVWAF 2025-09-13 13:45:37 +00:00 zatwierdzone przez GitHub
commit 1cc26f089e
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
16 zmienionych plików z 499 dodań i 191 usunięć

Wyświetl plik

@ -9,6 +9,7 @@ add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../../../../RadioLib" "${CMAKE_CUR
file(GLOB_RECURSE TEST_SOURCES
"tests/main.cpp"
"tests/TestModule.cpp"
"tests/TestCalculateTimeOnAir.cpp"
)
# create the executable

Wyświetl plik

@ -0,0 +1,75 @@
#include <boost/test/unit_test.hpp>
#include "modules/SX126x/SX126x.h"
#include "modules/SX127x/SX127x.h"
#include "modules/SX128x/SX128x.h"
#include "modules/LR11x0/LR11x0.h"
// Testable wrappers
class SX127xTestable : public SX127x {
public:
explicit SX127xTestable(Module* mod) : SX127x(mod) {}
void reset () {}
void errataFix(bool rx) {(void)rx;}
};
// --- Config structure ---
struct RadioConfig {
std::string name; // Radio name
ModemType_t modem;
DataRate_t dr;
PacketConfig_t pc;
std::vector<size_t> payload_len;
std::vector<RadioLibTime_t> expected_toa; // Expected time on air in microseconds from Semtech calculators
};
// --- Test configurations with golden values ---
std::vector<RadioConfig> allConfigs = {
{ "SX126x", RADIOLIB_MODEM_LORA, {.lora={7,125,5}}, {.lora={8,false,true,true}}, {1,10,50,255}, {30976,46336,128256,548096} }, // 30.97, 46.33, 128.25, 548.09
{ "SX126x", RADIOLIB_MODEM_LORA, {.lora={11,250,8}}, {.lora={16,true,false,false}}, {5,15,100,200}, {296960,362496,1411072,2590720} }, // 296.96, 362.49, 1410, 2590
{ "SX126x", RADIOLIB_MODEM_FSK, {.fsk={100,10}}, {.fsk={16,16,2}}, {1,16,64,200}, {560,1760,5600,16480} },
{ "SX126x", RADIOLIB_MODEM_LRFHSS,{.lrFhss={RADIOLIB_LR11X0_LR_FHSS_BW_386_72,RADIOLIB_SX126X_LR_FHSS_CR_2_3,false}}, {.lrFhss={2}}, {1,20,100}, {3784697,4259832,6324212} },
{ "SX127x", RADIOLIB_MODEM_LORA, {.lora={6,125,6}}, {.lora={8,false,true,false}}, {7,23,98,156}, {23000,39000,115000,174000} }, // 20.61, 39.04, 115.84, 174.21
{ "SX127x", RADIOLIB_MODEM_LORA, {.lora={8,250,8}}, {.lora={32,true,true,false}}, {10,20,80,160}, {70000,87000,210000,373000} }, // 69.89, 86.27, 209.15, 372.99
{ "SX127x", RADIOLIB_MODEM_FSK, {.fsk={100,5}}, {.fsk={16,16,3}}, {1,16,32,61}, {640,1840,3120,5440} },
{ "SX128x", RADIOLIB_MODEM_LORA, {.lora={5,400,5}}, {.lora={8,false,true,false}}, {1,50,200}, {2580,10179,34180} }, // 2.54, 10.02, 33.65
{ "SX128x", RADIOLIB_MODEM_LORA, {.lora={12,800,7}}, {.lora={16,false,true,true}}, {10,100,250}, {216319,861440,1936640} }, // 212.99, 848.19, 1910
{ "SX128x", RADIOLIB_MODEM_FSK, {.fsk={250,100}}, {.fsk={16,16,2}}, {1,32,64,128}, {224,1216,2240,4288} },
{ "LR11x0", RADIOLIB_MODEM_LORA, {.lora={10,250,5}}, {.lora={8,false,true,true}}, {1,20,100}, {103424,205824,615424} }, // 103.42, 205.82, 615.42
{ "LR11x0", RADIOLIB_MODEM_LORA, {.lora={11,500,6}}, {.lora={32,true,false,false}}, {10,25,200}, {205824,279552,1065984} }, // 205.82, 279.55, 1070
{ "LR11x0", RADIOLIB_MODEM_FSK, {.fsk={200,50}}, {.fsk={16,32,2}}, {1,32,64,200}, {360,1600,2880,8320} },
{ "LR11x0", RADIOLIB_MODEM_LRFHSS,{.lrFhss={RADIOLIB_LR11X0_LR_FHSS_BW_136_72,RADIOLIB_LR11X0_LR_FHSS_CR_1_3,true}}, {.lrFhss={1}}, {1,10,50}, {1949692,2392059,4456440} },
};
BOOST_AUTO_TEST_SUITE(suite_TimeOnAir)
BOOST_AUTO_TEST_CASE(TimeOnAir_AllRadios) {
for (const auto& cfg : allConfigs) {
BOOST_TEST_MESSAGE("--- Test calculateTimeOnAir " << cfg.name << ", modem=" << cfg.modem << " ---");
for (size_t i = 0; i < cfg.payload_len.size(); i++) {
auto len = cfg.payload_len[i];
RadioLibTime_t toa = 0;
if (cfg.name == "SX126x") {
SX126x dummy(nullptr);
toa = dummy.calculateTimeOnAir(cfg.modem, cfg.dr, cfg.pc, len);
} else if (cfg.name == "SX127x") {
SX127xTestable dummy(nullptr);
toa = dummy.calculateTimeOnAir(cfg.modem, cfg.dr, cfg.pc, len);
} else if (cfg.name == "SX128x") {
SX128x dummy(nullptr);
toa = dummy.calculateTimeOnAir(cfg.modem, cfg.dr, cfg.pc, len);
} else if (cfg.name == "LR11x0") {
LR11x0 dummy(nullptr);
toa = dummy.calculateTimeOnAir(cfg.modem, cfg.dr, cfg.pc, len);
}
BOOST_CHECK_EQUAL(toa, cfg.expected_toa[i]);
}
}
}
BOOST_AUTO_TEST_SUITE_END()

Wyświetl plik

@ -230,6 +230,7 @@ beginFSK4 KEYWORD2
setTCXO KEYWORD2
setDio2AsRfSwitch KEYWORD2
getTimeOnAir KEYWORD2
calculateTimeOnAir KEYWORD2
implicitHeader KEYWORD2
explicitHeader KEYWORD2
setSyncBits KEYWORD2

Wyświetl plik

@ -1203,71 +1203,44 @@ size_t LR11x0::getPacketLength(bool update, uint8_t* offset) {
return((size_t)len);
}
RadioLibTime_t LR11x0::getTimeOnAir(size_t len) {
RadioLibTime_t LR11x0::calculateTimeOnAir(ModemType_t modem, DataRate_t dr, PacketConfig_t pc, size_t len) {
// check active modem
uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE;
(void)getPacketType(&type);
if(type == RADIOLIB_LR11X0_PACKET_TYPE_LORA) {
// calculate number of symbols
float N_symbol = 0;
if(this->codingRate <= RADIOLIB_LR11X0_LORA_CR_4_8_SHORT) {
// legacy coding rate - nice and simple
// get SF coefficients
float coeff1 = 0;
int16_t coeff2 = 0;
int16_t coeff3 = 0;
if(this->spreadingFactor < 7) {
// SF5, SF6
coeff1 = 6.25;
coeff2 = 4*this->spreadingFactor;
coeff3 = 4*this->spreadingFactor;
} else if(this->spreadingFactor < 11) {
// SF7. SF8, SF9, SF10
coeff1 = 4.25;
coeff2 = 4*this->spreadingFactor + 8;
coeff3 = 4*this->spreadingFactor;
} else {
// SF11, SF12
coeff1 = 4.25;
coeff2 = 4*this->spreadingFactor + 8;
coeff3 = 4*(this->spreadingFactor - 2);
}
// get CRC length
int16_t N_bitCRC = 16;
if(this->crcTypeLoRa == RADIOLIB_LR11X0_LORA_CRC_DISABLED) {
N_bitCRC = 0;
}
// get header length
int16_t N_symbolHeader = 20;
if(this->headerType == RADIOLIB_LR11X0_LORA_HEADER_IMPLICIT) {
N_symbolHeader = 0;
}
// calculate number of LoRa preamble symbols
uint32_t N_symbolPreamble = (this->preambleLengthLoRa & 0x0F) * (uint32_t(1) << ((this->preambleLengthLoRa & 0xF0) >> 4));
// 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) * (float)(this->codingRate + 4);
} else {
// long interleaving - abandon hope all ye who enter here
/// \todo implement this mess - SX1280 datasheet v3.0 section 7.4.4.2
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(((uint32_t(1) << this->spreadingFactor) / this->bandwidthKhz) * N_symbol * 1000.0f);
return((symbolLength_us * nSymbol_x4) / 4);
} else if(type == RADIOLIB_LR11X0_PACKET_TYPE_GFSK) {
return(((uint32_t)len * 8 * 1000000UL) / this->bitRate);
} 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(type == RADIOLIB_LR11X0_PACKET_TYPE_LR_FHSS) {
} else if(modem == ModemType_t::RADIOLIB_MODEM_LRFHSS) {
// calculate the number of bits based on coding rate
uint16_t N_bits;
switch(this->lrFhssCr) {
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;
@ -1292,14 +1265,74 @@ RadioLibTime_t LR11x0::getTimeOnAir(size_t len) {
}
// add header bits
uint16_t N_totalBits = (RADIOLIB_LR11X0_LR_FHSS_HEADER_BITS * this->lrFhssHdrCount) + N_payBits;
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;
dr.lora.bandwidth = this->bandwidthKhz;
dr.lora.codingRate = cr;
pc.lora.preambleLength = this->preambleLengthLoRa;
pc.lora.implicitHeader = (this->headerType == RADIOLIB_LR11X0_LORA_HEADER_IMPLICIT) ? true : false;
pc.lora.crcEnabled = (this->crcTypeLoRa == RADIOLIB_LR11X0_LORA_CRC_ENABLED) ? true : false;
pc.lora.ldrOptimize = (bool)this->ldrOptimize;
break;
}
case ModemType_t::RADIOLIB_MODEM_FSK: {
dr.fsk.bitRate = (float)this->bitRate / 1000.0f;
dr.fsk.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;
pc.fsk.syncWordLength = this->syncWordLength;
pc.fsk.crcLength = crcLen;
break;
}
case ModemType_t::RADIOLIB_MODEM_LRFHSS: {
dr.lrFhss.bw = this->lrFhssBw;
dr.lrFhss.cr = this->lrFhssCr;
dr.lrFhss.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::calculateRxTimeout(RadioLibTime_t timeoutUs) {
// the timeout value is given in units of 30.52 microseconds
// the calling function should provide some extra width, as this number of units is truncated to integer

Wyświetl plik

@ -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.

Wyświetl plik

@ -1348,77 +1348,125 @@ 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::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
uint8_t modem = getPacketType();
if(modem == RADIOLIB_SX126X_PACKET_TYPE_LORA) {
uint32_t symbolLength_us = ((uint32_t)(1000 * 10) << this->spreadingFactor) / (this->bandwidthKhz * 10) ;
uint8_t sfCoeff1_x4 = 17; // (4.25 * 4)
uint8_t sfCoeff2 = 8;
if(this->spreadingFactor == 5 || this->spreadingFactor == 6) {
sfCoeff1_x4 = 25; // 6.25 * 4
sfCoeff2 = 0;
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);
}
uint8_t sfDivisor = 4*this->spreadingFactor;
if(symbolLength_us >= 16000) {
sfDivisor = 4*(this->spreadingFactor - 2);
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)));
}
const int8_t bitsPerCrc = 16;
const int8_t N_symbol_header = this->headerType == RADIOLIB_SX126X_LORA_HEADER_EXPLICIT ? 20 : 0;
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);
}
// 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 + this->crcTypeLoRa * bitsPerCrc - 4 * this->spreadingFactor + sfCoeff2 + N_symbol_header;
if(bitCount < 0) {
bitCount = 0;
// 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);
}
// 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 = (this->preambleLengthLoRa + 8) * 4 + sfCoeff1_x4 + nPreCodedSymbols * (this->codingRate + 4) * 4;
return((symbolLength_us * nSymbol_x4) / 4);
} else if(modem == RADIOLIB_SX126X_PACKET_TYPE_GFSK) {
return(((uint32_t)len * 8 * this->bitRate) / (RADIOLIB_SX126X_CRYSTAL_FREQ * 32));
} else if(modem == RADIOLIB_SX126X_PACKET_TYPE_LR_FHSS) {
// calculate the number of bits based on coding rate
uint16_t N_bits;
switch(this->lrFhssCr) {
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 * this->lrFhssHdrCount) + 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;
dataRate.lora.bandwidth = this->bandwidthKhz;
dataRate.lora.codingRate = (uint8_t)(this->codingRate + 4);
packetConfig.lora.preambleLength = this->preambleLengthLoRa;
packetConfig.lora.crcEnabled = (bool)this->crcTypeLoRa;
packetConfig.lora.implicitHeader = this->headerType == RADIOLIB_SX126X_LORA_HEADER_IMPLICIT;
packetConfig.lora.ldrOptimize = (bool)this->ldrOptimize;
} else if(type == RADIOLIB_SX126X_PACKET_TYPE_GFSK) {
modem = RADIOLIB_MODEM_FSK;
dataRate.fsk.bitRate = RADIOLIB_SX126X_CRYSTAL_FREQ * 32.0f * 1000.0f / (float)this->bitRate;
dataRate.fsk.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;
packetConfig.fsk.syncWordLength = this->syncWordLength;
packetConfig.fsk.crcLength = crcLen;
} else if(type == RADIOLIB_SX126X_PACKET_TYPE_LR_FHSS) {
modem = RADIOLIB_MODEM_LRFHSS;
dataRate.lrFhss.bw = this->lrFhssBw;
dataRate.lrFhss.cr = this->lrFhssCr;
dataRate.lrFhss.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

Wyświetl plik

@ -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.

Wyświetl plik

@ -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));

Wyświetl plik

@ -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

Wyświetl plik

@ -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 {

Wyświetl plik

@ -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;
};
/*!

Wyświetl plik

@ -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,99 @@ 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 dr = {};
PacketConfig_t pc = {};
switch (modem) {
case(RADIOLIB_SX127X_LORA): {
dr.lora.spreadingFactor = this->spreadingFactor;
dr.lora.bandwidth = this->bandwidth;
dr.lora.codingRate = this->codingRate;
// 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));
pc.lora.preambleLength = n_pre;
pc.lora.implicitHeader = this->implicitHdr;
pc.lora.crcEnabled = this->crcEnabled;
pc.lora.ldrOptimize = this->ldroEnabled;
return(calculateTimeOnAir((ModemType_t)RADIOLIB_MODEM_LORA, dr, pc, len));
}
case(RADIOLIB_SX127X_FSK_OOK): {
dr.fsk.bitRate = this->bitRate;
dr.fsk.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 crcEn = (this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_PACKET_CONFIG_1, 4, 4) == RADIOLIB_SX127X_CRC_ON);
pc.fsk.preambleLength = n_pre;
pc.fsk.syncWordLength = n_syncWord;
pc.fsk.crcLength = (uint8_t)(crcEn * 2);
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;
}
return(calculateTimeOnAir((ModemType_t)RADIOLIB_MODEM_FSK, dr, pc, len));
}
default:
return(RADIOLIB_ERR_WRONG_MODEM);
}
}
RadioLibTime_t SX127x::calculateRxTimeout(RadioLibTime_t timeoutUs) {
RadioLibTime_t timeout = 0;
if(getActiveModem() == RADIOLIB_SX127X_LORA) {

Wyświetl plik

@ -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

Wyświetl plik

@ -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,15 +1335,13 @@ int16_t SX128x::variablePacketLengthMode(uint8_t maxLen) {
return(setPacketMode(RADIOLIB_SX128X_GFSK_FLRC_PACKET_VARIABLE, maxLen));
}
RadioLibTime_t SX128x::getTimeOnAir(size_t len) {
// check active modem
uint8_t modem = getPacketType();
if(modem == RADIOLIB_SX128X_PACKET_TYPE_LORA) {
// calculate number of symbols
float N_symbol = 0;
uint8_t sf = this->spreadingFactor >> 4;
if(this->codingRateLoRa <= RADIOLIB_SX128X_LORA_CR_4_8) {
// legacy coding rate - nice and simple
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;
@ -1367,33 +1366,73 @@ RadioLibTime_t SX128x::getTimeOnAir(size_t len) {
// get CRC length
int16_t N_bitCRC = 16;
if(this->crcLoRa == RADIOLIB_SX128X_LORA_CRC_OFF) {
if(!pc.lora.crcEnabled) {
N_bitCRC = 0;
}
// get header length
int16_t N_symbolHeader = 20;
if(this->headerType == RADIOLIB_SX128X_LORA_HEADER_IMPLICIT) {
if(pc.lora.implicitHeader) {
N_symbolHeader = 0;
}
// calculate number of LoRa preamble symbols
uint32_t N_symbolPreamble = (this->preambleLengthLoRa & 0x0F) * (uint32_t(1) << ((this->preambleLengthLoRa & 0xF0) >> 4));
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) * (float)(this->codingRateLoRa + 4);
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;
} else {
// long interleaving - abandon hope all ye who enter here
/// \todo implement this mess - SX1280 datasheet v3.0 section 7.4.4.2
// 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;
}
// get time-on-air in us
return(((uint32_t(1) << sf) / this->bandwidthKhz) * N_symbol * 1000.0f);
dr.lora.spreadingFactor = sf;
dr.lora.codingRate = cr;
dr.lora.bandwidth = this->bandwidthKhz;
uint16_t preambleLength = (this->preambleLengthLoRa & 0x0F) * (uint32_t(1) << ((this->preambleLengthLoRa & 0xF0) >> 4));
pc.lora.preambleLength = preambleLength;
pc.lora.implicitHeader = this->headerType == RADIOLIB_SX128X_LORA_HEADER_IMPLICIT;
pc.lora.crcEnabled = this->crcLoRa == RADIOLIB_SX128X_LORA_CRC_ON;
pc.lora.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;
dr.fsk.freqDev = this->frequencyDev;
pc.fsk.preambleLength = ((uint16_t)this->preambleLengthGFSK >> 2) + 4;
pc.fsk.syncWordLength = ((this->syncWordLen >> 1) + 1) * 8;
pc.fsk.crcLength = this->crcGFSK >> 4;
return(calculateTimeOnAir(ModemType_t::RADIOLIB_MODEM_FSK, dr, pc, len));
} else {
return(((uint32_t)len * 8 * 1000) / this->bitRateKbps);
return(RADIOLIB_ERR_WRONG_MODEM);
}
}

Wyświetl plik

@ -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.
@ -942,7 +952,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;

Wyświetl plik

@ -36,7 +36,7 @@ struct LoRaRate_t {
/*! \brief LoRa bandwidth in kHz */
float bandwidth;
/*! \brief LoRa coding rate */
/*! \brief LoRa coding rate denominator */
uint8_t codingRate;
};
@ -82,6 +82,46 @@ union DataRate_t {
LrFhssRate_t lrFhss;
};
struct LoRaPacketConfig_t {
/*! \brief LoRa preamble length */
uint16_t preambleLength;
/*! \brief LoRa implicit header mode */
bool implicitHeader;
/*! \brief LoRa CRC mode */
bool crcEnabled;
/*! \brief LoRa low data rate optimization */
bool ldrOptimize;
};
struct FSKPacketConfig_t {
/*! \brief FSK preamble length in bits */
uint16_t preambleLength;
/*! \brief Length of the sync word in bits */
uint8_t syncWordLength;
/*! \brief FSK CRC length in bytes */
uint8_t crcLength;
};
struct LrFhssPacketConfig_t {
/*! \brief LR-FHSS header count (1 - 4) */
uint8_t hdrCount;
};
/*!
\union PacketConfig_t
\brief Common packet configuration structure
*/
union PacketConfig_t {
LoRaPacketConfig_t lora;
FSKPacketConfig_t fsk;
LrFhssPacketConfig_t lrFhss;
};
/*!
\struct CADScanConfig_t
\brief Channel scan configuration interpretation in case LoRa CAD is used