[SX126x] LR-FHSS support (#1228)
* [FEC] Added Vitervi encoder * [SX126x] Added initial LR-FHSS transmission support * [CI] Use RPi build for CodeQL * [SX126x] Fix signed comparison warning * [FEC] Make input to Viterbi encoder const * [CI] Drop SX126x examples from Arduino Uno builds * [CI] Build SX123x for CodeQL scan * [FEC] Fix comparison type * [SX126x] Added configurable grid step * [SX126x] Rename convolutional coding class * [SX126x] Fix payload CRC * [SX126x] ADded LR-FHSS example * [SX126x] Make argument const
This commit is contained in:
parent
1f11cd1dd5
commit
dc77e6e662
12 changed files with 933 additions and 11 deletions
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
|
@ -54,7 +54,7 @@ jobs:
|
|||
|
||||
- name: Build example
|
||||
run:
|
||||
arduino-cli compile --libraries /home/runner/work/RadioLib --fqbn arduino:avr:uno $PWD/examples/SX126x/SX126x_Transmit_Blocking/SX126x_Transmit_Blocking.ino --warnings=all
|
||||
arduino-cli compile --libraries /home/runner/work/RadioLib --fqbn arduino:avr:uno $PWD/examples/SX123x/SX123x_Transmit_Blocking/SX123x_Transmit_Blocking.ino --warnings=all
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
|
|
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
|
@ -46,7 +46,7 @@ jobs:
|
|||
# platform-dependent settings - extra board options, board index URLs, skip patterns etc.
|
||||
include:
|
||||
- id: arduino:avr:uno
|
||||
run: echo "skip-pattern=(STM32WL|SSTV|LoRaWAN|LR11x0_Firmware_Update|Pager|APRS|Morse)" >> $GITHUB_OUTPUT
|
||||
run: echo "skip-pattern=(STM32WL|SSTV|LoRaWAN|LR11x0_Firmware_Update|Pager|APRS|Morse|SX126x)" >> $GITHUB_OUTPUT
|
||||
- id: arduino:avr:mega
|
||||
run: |
|
||||
echo "options=':cpu=atmega2560'" >> $GITHUB_OUTPUT
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
RadioLib SX126x LR-FHSS Modem Example
|
||||
|
||||
This example shows how to use LR-FHSS modem in SX126x chips.
|
||||
This modem can only transmit data, and is not able to receive.
|
||||
|
||||
NOTE: The sketch below is just a guide on how to use
|
||||
LR-FHSS modem, so this code should not be run directly!
|
||||
Instead, modify the other examples to use LR-FHSS
|
||||
modem and use the appropriate configuration
|
||||
methods.
|
||||
|
||||
For default module settings, see the wiki page
|
||||
https://github.com/jgromes/RadioLib/wiki/Default-configuration#sx126x---lr-fhss-modem
|
||||
|
||||
For full API reference, see the GitHub Pages
|
||||
https://jgromes.github.io/RadioLib/
|
||||
*/
|
||||
|
||||
// include the library
|
||||
#include <RadioLib.h>
|
||||
|
||||
// SX1262 has the following connections:
|
||||
// NSS pin: 10
|
||||
// IRQ pin: 2
|
||||
// NRST pin: 3
|
||||
// BUSY pin: 9
|
||||
SX1262 radio = new Module(10, 2, 3, 9);
|
||||
|
||||
// or using RadioShield
|
||||
// https://github.com/jgromes/RadioShield
|
||||
//SX1262 radio = RadioShield.ModuleA;
|
||||
|
||||
// or using CubeCell
|
||||
//SX1262 radio = new Module(RADIOLIB_BUILTIN_MODULE);
|
||||
|
||||
void setup() {
|
||||
Serial.begin(9600);
|
||||
|
||||
// initialize SX1262 with default settings
|
||||
Serial.print(F("[SX1262] Initializing ... "));
|
||||
int state = radio.beginLRFHSS();
|
||||
if (state == RADIOLIB_ERR_NONE) {
|
||||
Serial.println(F("success!"));
|
||||
} else {
|
||||
Serial.print(F("failed, code "));
|
||||
Serial.println(state);
|
||||
while (true) { delay(10); }
|
||||
}
|
||||
|
||||
// if needed, you can switch between any of the modems
|
||||
//
|
||||
// radio.begin() start LoRa modem (and disable LR-FHSS)
|
||||
// radio.beginLRFHSS() start LR-FHSS modem (and disable LoRa)
|
||||
|
||||
// the following settings can also
|
||||
// be modified at run-time
|
||||
state = radio.setFrequency(433.5);
|
||||
state = radio.setLrFhssConfig(RADIOLIB_SX126X_LR_FHSS_BW_1523_4, // bandwidth
|
||||
RADIOLIB_SX126X_LR_FHSS_CR_1_2, // coding rate
|
||||
3, // header count
|
||||
0x13A); // hopping sequence seed
|
||||
state = radio.setOutputPower(10.0);
|
||||
state = radio.setSyncWord(0x12345678);
|
||||
if (state != RADIOLIB_ERR_NONE) {
|
||||
Serial.print(F("Unable to set configuration, code "));
|
||||
Serial.println(state);
|
||||
while (true) { delay(10); }
|
||||
}
|
||||
|
||||
#warning "This sketch is just an API guide! Read the note at line 6."
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// LR-FHSS modem can only transmit!
|
||||
// transmit LR-FHSS packet
|
||||
int state = radio.transmit("Hello World!");
|
||||
/*
|
||||
byte byteArr[] = {0x01, 0x23, 0x45, 0x67,
|
||||
0x89, 0xAB, 0xCD, 0xEF};
|
||||
int state = radio.transmit(byteArr, 8);
|
||||
*/
|
||||
if (state == RADIOLIB_ERR_NONE) {
|
||||
Serial.println(F("[SX1262] Packet transmitted successfully!"));
|
||||
} else if (state == RADIOLIB_ERR_PACKET_TOO_LONG) {
|
||||
Serial.println(F("[SX1262] Packet too long!"));
|
||||
} else if (state == RADIOLIB_ERR_TX_TIMEOUT) {
|
||||
Serial.println(F("[SX1262] Timed out while transmitting!"));
|
||||
} else {
|
||||
Serial.println(F("[SX1262] Failed to transmit packet, code "));
|
||||
Serial.println(state);
|
||||
}
|
||||
|
||||
}
|
|
@ -47,6 +47,24 @@ int16_t SX1262::beginFSK(float freq, float br, float freqDev, float rxBw, int8_t
|
|||
return(state);
|
||||
}
|
||||
|
||||
int16_t SX1262::beginLRFHSS(float freq, uint8_t bw, uint8_t cr, bool narrowGrid, int8_t power, float tcxoVoltage, bool useRegulatorLDO) {
|
||||
// execute common part
|
||||
int16_t state = SX126x::beginLRFHSS(bw, cr, narrowGrid, tcxoVoltage, useRegulatorLDO);
|
||||
RADIOLIB_ASSERT(state);
|
||||
|
||||
// configure publicly accessible settings
|
||||
state = setFrequency(freq);
|
||||
RADIOLIB_ASSERT(state);
|
||||
|
||||
state = SX126x::fixPaClamping();
|
||||
RADIOLIB_ASSERT(state);
|
||||
|
||||
state = setOutputPower(power);
|
||||
RADIOLIB_ASSERT(state);
|
||||
|
||||
return(state);
|
||||
}
|
||||
|
||||
int16_t SX1262::setFrequency(float freq) {
|
||||
return(setFrequency(freq, true));
|
||||
}
|
||||
|
|
|
@ -62,6 +62,21 @@ class SX1262: public SX126x {
|
|||
*/
|
||||
int16_t beginFSK(float freq = 434.0, float br = 4.8, float freqDev = 5.0, float rxBw = 156.2, int8_t power = 10, uint16_t preambleLength = 16, float tcxoVoltage = 1.6, bool useRegulatorLDO = false);
|
||||
|
||||
/*!
|
||||
\brief Initialization method for LR-FHSS modem. This modem only supports transmission!
|
||||
\param freq Carrier frequency in MHz. Defaults to 434.0 MHz.
|
||||
\param bw LR-FHSS bandwidth, one of RADIOLIB_SX126X_LR_FHSS_BW_* values. Defaults to 722.66 kHz.
|
||||
\param cr LR-FHSS coding rate, one of RADIOLIB_SX126X_LR_FHSS_CR_* values. Defaults to 2/3 coding rate.
|
||||
\param narrowGrid Whether to use narrow (3.9 kHz) or wide (25.39 kHz) grid spacing. Defaults to true (narrow/non-FCC) grid.
|
||||
\param power Output power in dBm. Defaults to 10 dBm.
|
||||
\param tcxoVoltage TCXO reference voltage to be set. Defaults to 1.6 V.
|
||||
If you are seeing -706/-707 error codes, it likely means you are using non-0 value for module with XTAL.
|
||||
To use XTAL, either set this value to 0, or set SX126x::XTAL to true.
|
||||
\param useRegulatorLDO Whether to use only LDO regulator (true) or DC-DC regulator (false). Defaults to false.
|
||||
\returns \ref status_codes
|
||||
*/
|
||||
int16_t beginLRFHSS(float freq = 434.0, uint8_t bw = RADIOLIB_SX126X_LR_FHSS_BW_722_66, uint8_t cr = RADIOLIB_SX126X_LR_FHSS_CR_2_3, bool narrowGrid = true, int8_t power = 10, float tcxoVoltage = 1.6, bool useRegulatorLDO = false);
|
||||
|
||||
// configuration methods
|
||||
|
||||
/*!
|
||||
|
|
|
@ -47,6 +47,24 @@ int16_t SX1268::beginFSK(float freq, float br, float freqDev, float rxBw, int8_t
|
|||
return(state);
|
||||
}
|
||||
|
||||
int16_t SX1268::beginLRFHSS(float freq, uint8_t bw, uint8_t cr, bool narrowGrid, int8_t power, float tcxoVoltage, bool useRegulatorLDO) {
|
||||
// execute common part
|
||||
int16_t state = SX126x::beginLRFHSS(bw, cr, narrowGrid, tcxoVoltage, useRegulatorLDO);
|
||||
RADIOLIB_ASSERT(state);
|
||||
|
||||
// configure publicly accessible settings
|
||||
state = setFrequency(freq);
|
||||
RADIOLIB_ASSERT(state);
|
||||
|
||||
state = SX126x::fixPaClamping();
|
||||
RADIOLIB_ASSERT(state);
|
||||
|
||||
state = setOutputPower(power);
|
||||
RADIOLIB_ASSERT(state);
|
||||
|
||||
return(state);
|
||||
}
|
||||
|
||||
int16_t SX1268::setFrequency(float freq) {
|
||||
return(setFrequency(freq, true));
|
||||
}
|
||||
|
|
|
@ -61,6 +61,21 @@ class SX1268: public SX126x {
|
|||
*/
|
||||
int16_t beginFSK(float freq = 434.0, float br = 4.8, float freqDev = 5.0, float rxBw = 156.2, int8_t power = 10, uint16_t preambleLength = 16, float tcxoVoltage = 1.6, bool useRegulatorLDO = false);
|
||||
|
||||
/*!
|
||||
\brief Initialization method for LR-FHSS modem. This modem only supports transmission!
|
||||
\param freq Carrier frequency in MHz. Defaults to 434.0 MHz.
|
||||
\param bw LR-FHSS bandwidth, one of RADIOLIB_SX126X_LR_FHSS_BW_* values. Defaults to 722.66 kHz.
|
||||
\param cr LR-FHSS coding rate, one of RADIOLIB_SX126X_LR_FHSS_CR_* values. Defaults to 2/3 coding rate.
|
||||
\param narrowGrid Whether to use narrow (3.9 kHz) or wide (25.39 kHz) grid spacing. Defaults to true (narrow/non-FCC) grid.
|
||||
\param power Output power in dBm. Defaults to 10 dBm.
|
||||
\param tcxoVoltage TCXO reference voltage to be set. Defaults to 1.6 V.
|
||||
If you are seeing -706/-707 error codes, it likely means you are using non-0 value for module with XTAL.
|
||||
To use XTAL, either set this value to 0, or set SX126x::XTAL to true.
|
||||
\param useRegulatorLDO Whether to use only LDO regulator (true) or DC-DC regulator (false). Defaults to false.
|
||||
\returns \ref status_codes
|
||||
*/
|
||||
int16_t beginLRFHSS(float freq = 434.0, uint8_t bw = RADIOLIB_SX126X_LR_FHSS_BW_722_66, uint8_t cr = RADIOLIB_SX126X_LR_FHSS_CR_2_3, bool narrowGrid = true, int8_t power = 10, float tcxoVoltage = 1.6, bool useRegulatorLDO = false);
|
||||
|
||||
// configuration methods
|
||||
|
||||
/*!
|
||||
|
|
|
@ -119,6 +119,47 @@ int16_t SX126x::beginFSK(float br, float freqDev, float rxBw, uint16_t preambleL
|
|||
return(state);
|
||||
}
|
||||
|
||||
int16_t SX126x::beginLRFHSS(uint8_t bw, uint8_t cr, bool narrowGrid, float tcxoVoltage, bool useRegulatorLDO) {
|
||||
this->lrFhssGridNonFcc = narrowGrid;
|
||||
|
||||
// set module properties and perform initial setup
|
||||
int16_t state = this->modSetup(tcxoVoltage, useRegulatorLDO, RADIOLIB_SX126X_PACKET_TYPE_LR_FHSS);
|
||||
RADIOLIB_ASSERT(state);
|
||||
|
||||
// set publicly accessible settings that are not a part of begin method
|
||||
state = setCurrentLimit(60.0);
|
||||
RADIOLIB_ASSERT(state);
|
||||
|
||||
state = setDio2AsRfSwitch(true);
|
||||
RADIOLIB_ASSERT(state);
|
||||
|
||||
// set all packet params to 0 (packet engine is disabled in LR-FHSS mode)
|
||||
state = setPacketParamsFSK(0, 0, 0, 0, 0, 0, 0, 0);
|
||||
RADIOLIB_ASSERT(state);
|
||||
|
||||
// set bit rate
|
||||
this->rxBandwidth = 0;
|
||||
this->frequencyDev = 0;
|
||||
this->pulseShape = RADIOLIB_SX126X_GFSK_FILTER_GAUSS_1;
|
||||
state = setBitRate(0.48828125f);
|
||||
RADIOLIB_ASSERT(state);
|
||||
|
||||
return(setLrFhssConfig(bw, cr));
|
||||
}
|
||||
|
||||
int16_t SX126x::setLrFhssConfig(uint8_t bw, uint8_t cr, uint8_t hdrCount, uint16_t hopSeqId) {
|
||||
// check and cache all parameters
|
||||
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);
|
||||
this->lrFhssCr = cr;
|
||||
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);
|
||||
this->lrFhssBw = bw;
|
||||
RADIOLIB_CHECK_RANGE(hdrCount, 1, 4, RADIOLIB_ERR_INVALID_BIT_RANGE);
|
||||
this->lrFhssHdrCount = hdrCount;
|
||||
RADIOLIB_CHECK_RANGE((int16_t)hopSeqId, (int16_t)0x000, (int16_t)0x1FF, RADIOLIB_ERR_INVALID_DATA_SHAPING);
|
||||
this->lrFhssHopSeqId = hopSeqId;
|
||||
return(RADIOLIB_ERR_NONE);
|
||||
}
|
||||
|
||||
int16_t SX126x::reset(bool verify) {
|
||||
// run the reset sequence
|
||||
this->mod->hal->pinMode(this->mod->getRst(), this->mod->hal->GpioModeOutput);
|
||||
|
@ -171,13 +212,34 @@ int16_t SX126x::transmit(const uint8_t* data, size_t len, uint8_t addr) {
|
|||
RADIOLIB_ASSERT(state);
|
||||
|
||||
// wait for packet transmission or timeout
|
||||
uint8_t modem = getPacketType();
|
||||
RadioLibTime_t start = this->mod->hal->millis();
|
||||
while(!this->mod->hal->digitalRead(this->mod->getIrq())) {
|
||||
while(true) {
|
||||
// yield for multi-threaded platforms
|
||||
this->mod->hal->yield();
|
||||
|
||||
// check timeout
|
||||
if(this->mod->hal->millis() - start > timeout) {
|
||||
finishTransmit();
|
||||
return(RADIOLIB_ERR_TX_TIMEOUT);
|
||||
}
|
||||
|
||||
// poll the interrupt pin
|
||||
if(this->mod->hal->digitalRead(this->mod->getIrq())) {
|
||||
// in LoRa or GFSK, only Tx done interrupt is enabled
|
||||
if(modem != RADIOLIB_SX126X_PACKET_TYPE_LR_FHSS) {
|
||||
break;
|
||||
}
|
||||
|
||||
// in LR-FHSS, IRQ signals both Tx done as frequency hop request
|
||||
if(this->getIrqFlags() & RADIOLIB_SX126X_IRQ_TX_DONE) {
|
||||
break;
|
||||
} else {
|
||||
// handle frequency hop
|
||||
this->setLRFHSSHop(this->lrFhssHopNum % 16);
|
||||
clearIrqStatus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update data rate
|
||||
|
@ -467,13 +529,17 @@ int16_t SX126x::startTransmit(const uint8_t* data, size_t len, uint8_t addr) {
|
|||
state = setPacketParams(this->preambleLengthLoRa, this->crcTypeLoRa, len, this->headerType, this->invertIQEnabled);
|
||||
} else if(modem == RADIOLIB_SX126X_PACKET_TYPE_GFSK) {
|
||||
state = setPacketParamsFSK(this->preambleLengthFSK, this->crcTypeFSK, this->syncWordLength, this->addrComp, this->whitening, this->packetType, len);
|
||||
} else {
|
||||
} else if(modem != RADIOLIB_SX126X_PACKET_TYPE_LR_FHSS) {
|
||||
return(RADIOLIB_ERR_UNKNOWN);
|
||||
}
|
||||
RADIOLIB_ASSERT(state);
|
||||
|
||||
// set DIO mapping
|
||||
state = setDioIrqParams(RADIOLIB_SX126X_IRQ_TX_DONE | RADIOLIB_SX126X_IRQ_TIMEOUT, RADIOLIB_SX126X_IRQ_TX_DONE);
|
||||
if(modem != RADIOLIB_SX126X_PACKET_TYPE_LR_FHSS) {
|
||||
state = setDioIrqParams(RADIOLIB_SX126X_IRQ_TX_DONE | RADIOLIB_SX126X_IRQ_TIMEOUT, RADIOLIB_SX126X_IRQ_TX_DONE);
|
||||
} else {
|
||||
state = setDioIrqParams(RADIOLIB_SX126X_IRQ_TX_DONE | RADIOLIB_SX126X_IRQ_LR_FHSS_HOP, RADIOLIB_SX126X_IRQ_TX_DONE | RADIOLIB_SX126X_IRQ_LR_FHSS_HOP);
|
||||
}
|
||||
RADIOLIB_ASSERT(state);
|
||||
|
||||
// set buffer pointers
|
||||
|
@ -481,7 +547,49 @@ int16_t SX126x::startTransmit(const uint8_t* data, size_t len, uint8_t addr) {
|
|||
RADIOLIB_ASSERT(state);
|
||||
|
||||
// write packet to buffer
|
||||
if(modem != RADIOLIB_SX126X_PACKET_TYPE_LR_FHSS) {
|
||||
state = writeBuffer(const_cast<uint8_t*>(data), len);
|
||||
|
||||
} else {
|
||||
// first, reset the LR-FHSS state machine
|
||||
state = resetLRFHSS();
|
||||
RADIOLIB_ASSERT(state);
|
||||
|
||||
// skip hopping for the first 4 - lrFhssHdrCount blocks
|
||||
for(int i = 0; i < 4 - this->lrFhssHdrCount; ++i ) {
|
||||
stepLRFHSS();
|
||||
}
|
||||
|
||||
// in LR-FHSS mode, we need to build the entire packet manually
|
||||
uint8_t frame[RADIOLIB_SX126X_MAX_PACKET_LENGTH] = { 0 };
|
||||
size_t frameLen = 0;
|
||||
this->lrFhssFrameBitsRem = 0;
|
||||
this->lrFhssFrameHopsRem = 0;
|
||||
this->lrFhssHopNum = 0;
|
||||
state = buildLRFHSSPacket(const_cast<uint8_t*>(data), len, frame, &frameLen, &this->lrFhssFrameBitsRem, &this->lrFhssFrameHopsRem);
|
||||
RADIOLIB_ASSERT(state);
|
||||
|
||||
// FIXME check max len for FHSS
|
||||
state = writeBuffer(frame, frameLen);
|
||||
RADIOLIB_ASSERT(state);
|
||||
|
||||
// activate hopping
|
||||
uint8_t hopCfg[] = { RADIOLIB_SX126X_HOPPING_ENABLED, (uint8_t)frameLen, (uint8_t)this->lrFhssFrameHopsRem };
|
||||
state = writeRegister(RADIOLIB_SX126X_REG_HOPPING_ENABLE, hopCfg, 3);
|
||||
RADIOLIB_ASSERT(state);
|
||||
|
||||
// write the initial hopping table
|
||||
uint8_t initHops = this->lrFhssFrameHopsRem;
|
||||
if(initHops > 16) {
|
||||
initHops = 16;
|
||||
};
|
||||
for(size_t i = 0; i < initHops; i++) {
|
||||
// set the hop frequency and symbols
|
||||
state = this->setLRFHSSHop(i);
|
||||
RADIOLIB_ASSERT(state);
|
||||
}
|
||||
|
||||
}
|
||||
RADIOLIB_ASSERT(state);
|
||||
|
||||
// clear interrupt flags
|
||||
|
@ -1025,7 +1133,8 @@ int16_t SX126x::setRxBoostedGainMode(bool rxbgm, bool persist) {
|
|||
|
||||
int16_t SX126x::setDataShaping(uint8_t sh) {
|
||||
// check active modem
|
||||
if(getPacketType() != RADIOLIB_SX126X_PACKET_TYPE_GFSK) {
|
||||
uint8_t modem = getPacketType();
|
||||
if((modem != RADIOLIB_SX126X_PACKET_TYPE_GFSK) && (modem != RADIOLIB_SX126X_PACKET_TYPE_LR_FHSS)) {
|
||||
return(RADIOLIB_ERR_WRONG_MODEM);
|
||||
}
|
||||
|
||||
|
@ -1079,6 +1188,14 @@ int16_t SX126x::setSyncWord(uint8_t* syncWord, size_t len) {
|
|||
return(RADIOLIB_ERR_INVALID_SYNC_WORD);
|
||||
}
|
||||
return(setSyncWord(syncWord[0]));
|
||||
|
||||
} else if(modem == RADIOLIB_SX126X_PACKET_TYPE_LR_FHSS) {
|
||||
// with length set to 4 and LR-FHSS modem active, assume it is the LR-FHSS sync word
|
||||
if(len != sizeof(uint32_t)) {
|
||||
return(RADIOLIB_ERR_INVALID_SYNC_WORD);
|
||||
}
|
||||
memcpy(this->lrFhssSyncWord, syncWord, sizeof(uint32_t));
|
||||
|
||||
}
|
||||
|
||||
return(RADIOLIB_ERR_WRONG_MODEM);
|
||||
|
@ -1346,7 +1463,8 @@ int16_t SX126x::variablePacketLengthMode(uint8_t maxLen) {
|
|||
RadioLibTime_t SX126x::getTimeOnAir(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
|
||||
if(getPacketType() == RADIOLIB_SX126X_PACKET_TYPE_LORA) {
|
||||
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;
|
||||
|
@ -1373,9 +1491,44 @@ RadioLibTime_t SX126x::getTimeOnAir(size_t len) {
|
|||
uint32_t nSymbol_x4 = (this->preambleLengthLoRa + 8) * 4 + sfCoeff1_x4 + nPreCodedSymbols * (this->codingRate + 4) * 4;
|
||||
|
||||
return((symbolLength_us * nSymbol_x4) / 4);
|
||||
} else {
|
||||
|
||||
} 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);
|
||||
|
||||
}
|
||||
|
||||
return(RADIOLIB_ERR_UNKNOWN);
|
||||
}
|
||||
|
||||
RadioLibTime_t SX126x::calculateRxTimeout(RadioLibTime_t timeoutUs) {
|
||||
|
@ -1908,8 +2061,8 @@ int16_t SX126x::clearDeviceErrors() {
|
|||
|
||||
int16_t SX126x::setFrequencyRaw(float freq) {
|
||||
// calculate raw value
|
||||
uint32_t frf = (freq * (uint32_t(1) << RADIOLIB_SX126X_DIV_EXPONENT)) / RADIOLIB_SX126X_CRYSTAL_FREQ;
|
||||
return(setRfFrequency(frf));
|
||||
this->frf = (freq * (uint32_t(1) << RADIOLIB_SX126X_DIV_EXPONENT)) / RADIOLIB_SX126X_CRYSTAL_FREQ;
|
||||
return(setRfFrequency(this->frf));
|
||||
}
|
||||
|
||||
int16_t SX126x::fixSensitivity() {
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
#include "../../Module.h"
|
||||
|
||||
#include "../../protocols/PhysicalLayer/PhysicalLayer.h"
|
||||
#include "../../utils/FEC.h"
|
||||
#include "../../utils/CRC.h"
|
||||
|
||||
// SX126X physical layer properties
|
||||
#define RADIOLIB_SX126X_FREQUENCY_STEP_SIZE 0.9536743164
|
||||
|
@ -434,6 +436,36 @@
|
|||
// size of the spectral scan result
|
||||
#define RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE (33)
|
||||
|
||||
// LR-FHSS configuration
|
||||
#define RADIOLIB_SX126X_LR_FHSS_CR_5_6 (0x00UL << 0) // 7 0 LR FHSS coding rate: 5/6
|
||||
#define RADIOLIB_SX126X_LR_FHSS_CR_2_3 (0x01UL << 0) // 7 0 2/3
|
||||
#define RADIOLIB_SX126X_LR_FHSS_CR_1_2 (0x02UL << 0) // 7 0 1/2
|
||||
#define RADIOLIB_SX126X_LR_FHSS_CR_1_3 (0x03UL << 0) // 7 0 1/3
|
||||
#define RADIOLIB_SX126X_LR_FHSS_MOD_TYPE_GMSK (0x00UL << 0) // 7 0 LR FHSS modulation: GMSK
|
||||
#define RADIOLIB_SX126X_LR_FHSS_GRID_STEP_FCC (0x00UL << 0) // 7 0 LR FHSS step size: 25.390625 kHz (FCC)
|
||||
#define RADIOLIB_SX126X_LR_FHSS_GRID_STEP_NON_FCC (0x01UL << 0) // 7 0 3.90625 kHz (non-FCC)
|
||||
#define RADIOLIB_SX126X_LR_FHSS_HOPPING_DISABLED (0x00UL << 0) // 7 0 LR FHSS hopping: disabled
|
||||
#define RADIOLIB_SX126X_LR_FHSS_HOPPING_ENABLED (0x01UL << 0) // 7 0 enabled
|
||||
#define RADIOLIB_SX126X_LR_FHSS_BW_39_06 (0x00UL << 0) // 7 0 LR FHSS bandwidth: 39.06 kHz
|
||||
#define RADIOLIB_SX126X_LR_FHSS_BW_85_94 (0x01UL << 0) // 7 0 85.94 kHz
|
||||
#define RADIOLIB_SX126X_LR_FHSS_BW_136_72 (0x02UL << 0) // 7 0 136.72 kHz
|
||||
#define RADIOLIB_SX126X_LR_FHSS_BW_183_59 (0x03UL << 0) // 7 0 183.59 kHz
|
||||
#define RADIOLIB_SX126X_LR_FHSS_BW_335_94 (0x04UL << 0) // 7 0 335.94 kHz
|
||||
#define RADIOLIB_SX126X_LR_FHSS_BW_386_72 (0x05UL << 0) // 7 0 386.72 kHz
|
||||
#define RADIOLIB_SX126X_LR_FHSS_BW_722_66 (0x06UL << 0) // 7 0 722.66 kHz
|
||||
#define RADIOLIB_SX126X_LR_FHSS_BW_773_44 (0x07UL << 0) // 7 0 773.44 kHz
|
||||
#define RADIOLIB_SX126X_LR_FHSS_BW_1523_4 (0x08UL << 0) // 7 0 1523.4 kHz
|
||||
#define RADIOLIB_SX126X_LR_FHSS_BW_1574_2 (0x09UL << 0) // 7 0 1574.2 kHz
|
||||
|
||||
// LR-FHSS packet lengths
|
||||
#define RADIOLIB_SX126X_LR_FHSS_MAX_ENC_SIZE (608)
|
||||
#define RADIOLIB_SX126X_LR_FHSS_HEADER_BITS (114)
|
||||
#define RADIOLIB_SX126X_LR_FHSS_HDR_BYTES (10)
|
||||
#define RADIOLIB_SX126X_LR_FHSS_SYNC_WORD_BYTES (4)
|
||||
#define RADIOLIB_SX126X_LR_FHSS_FRAG_BITS (48)
|
||||
#define RADIOLIB_SX126X_LR_FHSS_BLOCK_PREAMBLE_BITS (2)
|
||||
#define RADIOLIB_SX126X_LR_FHSS_BLOCK_BITS (RADIOLIB_SX126X_LR_FHSS_FRAG_BITS + RADIOLIB_SX126X_LR_FHSS_BLOCK_PREAMBLE_BITS)
|
||||
|
||||
/*!
|
||||
\class SX126x
|
||||
\brief Base class for %SX126x series. All derived classes for %SX126x (e.g. SX1262 or SX1268) inherit from this base class.
|
||||
|
@ -489,6 +521,27 @@ class SX126x: public PhysicalLayer {
|
|||
*/
|
||||
int16_t beginFSK(float br, float freqDev, float rxBw, uint16_t preambleLength, float tcxoVoltage, bool useRegulatorLDO = false);
|
||||
|
||||
/*!
|
||||
\brief Initialization method for LR-FHSS modem. This modem only supports transmission!
|
||||
\param bw LR-FHSS bandwidth, one of RADIOLIB_SX126X_LR_FHSS_BW_* values.
|
||||
\param cr LR-FHSS coding rate, one of RADIOLIB_SX126X_LR_FHSS_CR_* values.
|
||||
\param narrowGrid Whether to use narrow (3.9 kHz) or wide (25.39 kHz) grid spacing.
|
||||
\param tcxoVoltage TCXO reference voltage to be set on DIO3. Defaults to 1.6 V, set to 0 to skip.
|
||||
\param useRegulatorLDO Whether to use only LDO regulator (true) or DC-DC regulator (false). Defaults to false.
|
||||
\returns \ref status_codes
|
||||
*/
|
||||
int16_t beginLRFHSS(uint8_t bw, uint8_t cr, bool narrowGrid, float tcxoVoltage, bool useRegulatorLDO = false);
|
||||
|
||||
/*!
|
||||
\brief Sets LR-FHSS configuration.
|
||||
\param bw LR-FHSS bandwidth, one of RADIOLIB_SX126X_LR_FHSS_BW_* values.
|
||||
\param cr LR-FHSS coding rate, one of RADIOLIB_SX126X_LR_FHSS_CR_* values.
|
||||
\param hdrCount Header packet count, 1 - 4. Defaults to 3.
|
||||
\param hopSeqId 9-bit seed number for PRNG generation of the hopping sequence. Defaults to 0x13A.
|
||||
\returns \ref status_codes
|
||||
*/
|
||||
int16_t setLrFhssConfig(uint8_t bw, uint8_t cr, uint8_t hdrCount = 3, uint16_t hopSeqId = 0x100);
|
||||
|
||||
/*!
|
||||
\brief Reset method. Will reset the chip to the default state using RST pin.
|
||||
\param verify Whether correct module startup should be verified. When set to true, RadioLib will attempt to verify the module has started correctly
|
||||
|
@ -830,6 +883,7 @@ class SX126x: public PhysicalLayer {
|
|||
|
||||
/*!
|
||||
\brief Sets FSK sync word in the form of array of up to 8 bytes.
|
||||
Can also set LR-FHSS sync word, but its length must be 4 bytes.
|
||||
\param syncWord FSK sync word to be set.
|
||||
\param len FSK sync word length in bytes.
|
||||
\returns \ref status_codes
|
||||
|
@ -1214,10 +1268,26 @@ class SX126x: public PhysicalLayer {
|
|||
|
||||
uint32_t tcxoDelay = 0;
|
||||
uint8_t pwr = 0;
|
||||
uint32_t frf = 0;
|
||||
|
||||
size_t implicitLen = 0;
|
||||
uint8_t invertIQEnabled = RADIOLIB_SX126X_LORA_IQ_STANDARD;
|
||||
|
||||
// LR-FHSS stuff - there's a lot of it because all the encoding happens in software
|
||||
uint8_t lrFhssCr = RADIOLIB_SX126X_LR_FHSS_CR_2_3;
|
||||
uint8_t lrFhssBw = RADIOLIB_SX126X_LR_FHSS_BW_722_66;
|
||||
uint8_t lrFhssHdrCount = 3;
|
||||
uint8_t lrFhssSyncWord[RADIOLIB_SX126X_LR_FHSS_SYNC_WORD_BYTES] = { 0x12, 0xAD, 0x10, 0x1B };
|
||||
bool lrFhssGridNonFcc = false;
|
||||
uint16_t lrFhssNgrid = 0;
|
||||
uint16_t lrFhssLfsrState = 0;
|
||||
uint16_t lrFhssPoly = 0;
|
||||
uint16_t lrFhssSeed = 0;
|
||||
uint16_t lrFhssHopSeqId = 0;
|
||||
size_t lrFhssFrameBitsRem = 0;
|
||||
size_t lrFhssFrameHopsRem = 0;
|
||||
size_t lrFhssHopNum = 0;
|
||||
|
||||
int16_t modSetup(float tcxoVoltage, bool useRegulatorLDO, uint8_t modem);
|
||||
int16_t config(uint8_t modem);
|
||||
bool findChip(const char* verStr);
|
||||
|
@ -1232,6 +1302,11 @@ class SX126x: public PhysicalLayer {
|
|||
int16_t fixImplicitTimeout();
|
||||
int16_t fixInvertedIQ(uint8_t iqConfig);
|
||||
|
||||
// LR-FHSS utilities
|
||||
int16_t buildLRFHSSPacket(const uint8_t* in, size_t in_len, uint8_t* out, size_t* out_len, size_t* out_bits, size_t* out_hops);
|
||||
int16_t resetLRFHSS();
|
||||
uint16_t stepLRFHSS();
|
||||
int16_t setLRFHSSHop(uint8_t index);
|
||||
|
||||
void regdump();
|
||||
void effectEvalPre(uint8_t* buff, uint32_t start);
|
||||
|
|
392
src/modules/SX126x/SX126x_LR_FHSS.cpp
Normal file
392
src/modules/SX126x/SX126x_LR_FHSS.cpp
Normal file
|
@ -0,0 +1,392 @@
|
|||
#include "SX126x.h"
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#if !RADIOLIB_EXCLUDE_SX126X
|
||||
|
||||
/*
|
||||
LR-FHSS implementation in this file is adapted from Setmech's LR-FHSS demo:
|
||||
https://github.com/Lora-net/SWDM001/tree/master/lib/sx126x_driver
|
||||
|
||||
Its SX126x driver is distributed under the Clear BSD License,
|
||||
and to comply with its terms, it is reproduced below.
|
||||
|
||||
The Clear BSD License
|
||||
Copyright Semtech Corporation 2021. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted (subject to the limitations in the disclaimer
|
||||
below) provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the Semtech corporation nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY
|
||||
THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
|
||||
NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SEMTECH CORPORATION BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
// header interleaver
|
||||
static const uint8_t LrFhssHeaderInterleaver[80] = {
|
||||
0, 18, 36, 54, 72, 4, 22, 40,
|
||||
58, 76, 8, 26, 44, 62, 12, 30,
|
||||
48, 66, 16, 34, 52, 70, 1, 19,
|
||||
37, 55, 73, 5, 23, 41, 59, 77,
|
||||
9, 27, 45, 63, 13, 31, 49, 67,
|
||||
17, 35, 53, 71, 2, 20, 38, 56,
|
||||
74, 6, 24, 42, 60, 78, 10, 28,
|
||||
46, 64, 14, 32, 50, 68, 3, 21,
|
||||
39, 57, 75, 7, 25, 43, 61, 79,
|
||||
11, 29, 47, 65, 15, 33, 51, 69,
|
||||
};
|
||||
|
||||
int16_t SX126x::buildLRFHSSPacket(const uint8_t* in, size_t in_len, uint8_t* out, size_t* out_len, size_t* out_bits, size_t* out_hops) {
|
||||
// perform payload whitening
|
||||
uint8_t lfsr = 0xFF;
|
||||
for(size_t i = 0; i < in_len; i++) {
|
||||
uint8_t u = in[i] ^ lfsr;
|
||||
|
||||
// we really shouldn't reuse the caller's memory in this way ...
|
||||
// but since this is a private method it should be at least controlled, if not safe
|
||||
out[i] = ((u & 0x0F) << 4 ) | ((u & 0xF0) >> 4);
|
||||
lfsr = (lfsr << 1) | (((lfsr & 0x80) >> 7) ^ (((lfsr & 0x20) >> 5) ^ (((lfsr & 0x10) >> 4) ^ ((lfsr & 0x08) >> 3))));
|
||||
}
|
||||
|
||||
// calculate the CRC-16 over the whitened data, looks like something custom
|
||||
RadioLibCRCInstance.size = 16;
|
||||
RadioLibCRCInstance.poly = 0x755B;
|
||||
RadioLibCRCInstance.init = 0xFFFF;
|
||||
RadioLibCRCInstance.out = 0x0000;
|
||||
uint16_t crc16 = RadioLibCRCInstance.checksum(out, in_len);
|
||||
|
||||
// add payload CRC
|
||||
out[in_len] = (crc16 >> 8) & 0xFF;
|
||||
out[in_len + 1] = crc16 & 0xFF;
|
||||
out[in_len + 2] = 0;
|
||||
|
||||
// encode the payload with CRC using convolutional coding with 1/3 rate into a temporary buffer
|
||||
uint8_t tmp[RADIOLIB_SX126X_LR_FHSS_MAX_ENC_SIZE] = { 0 };
|
||||
size_t nb_bits = 0;
|
||||
RadioLibConvCodeInstance.begin(3);
|
||||
RadioLibConvCodeInstance.encode(out, 8 * (in_len + 2) + 6, tmp, &nb_bits);
|
||||
memset(out, 0, RADIOLIB_SX126X_MAX_PACKET_LENGTH);
|
||||
|
||||
// for rates other than the 1/3 base, puncture the code
|
||||
if(this->lrFhssCr != RADIOLIB_SX126X_LR_FHSS_CR_1_3) {
|
||||
uint32_t matrix_index = 0;
|
||||
uint8_t matrix[15] = { 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0 };
|
||||
uint8_t matrix_len = 0;
|
||||
switch(this->lrFhssCr) {
|
||||
case RADIOLIB_SX126X_LR_FHSS_CR_5_6:
|
||||
matrix_len = 15;
|
||||
break;
|
||||
case RADIOLIB_SX126X_LR_FHSS_CR_2_3:
|
||||
matrix_len = 6;
|
||||
break;
|
||||
case RADIOLIB_SX126X_LR_FHSS_CR_1_2:
|
||||
matrix_len = 3;
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t j = 0;
|
||||
for(uint32_t i = 0; i < nb_bits; i++) {
|
||||
if(matrix[matrix_index]) {
|
||||
if(TEST_BIT_IN_ARRAY_LSB(tmp, i)) {
|
||||
SET_BIT_IN_ARRAY_LSB(out, j);
|
||||
} else {
|
||||
CLEAR_BIT_IN_ARRAY_LSB(out, j);
|
||||
}
|
||||
j++;
|
||||
}
|
||||
|
||||
if(++matrix_index == matrix_len) {
|
||||
matrix_index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
nb_bits = j;
|
||||
memcpy(tmp, out, (nb_bits + 7) / 8);
|
||||
}
|
||||
|
||||
// interleave the payload into output buffer
|
||||
uint16_t step = 0;
|
||||
while(step * step < nb_bits) {
|
||||
// probably the silliest sqrt() I ever saw
|
||||
step++;
|
||||
}
|
||||
|
||||
const uint16_t step_v = step >> 1;
|
||||
step <<= 1;
|
||||
|
||||
uint16_t pos = 0;
|
||||
uint16_t st_idx = 0;
|
||||
uint16_t st_idx_init = 0;
|
||||
int16_t bits_left = nb_bits;
|
||||
uint16_t out_row_index = RADIOLIB_SX126X_LR_FHSS_HEADER_BITS * this->lrFhssHdrCount;
|
||||
|
||||
while(bits_left > 0) {
|
||||
int16_t in_row_width = bits_left;
|
||||
if(in_row_width > RADIOLIB_SX126X_LR_FHSS_FRAG_BITS) {
|
||||
in_row_width = RADIOLIB_SX126X_LR_FHSS_FRAG_BITS;
|
||||
}
|
||||
|
||||
// guard bits
|
||||
CLEAR_BIT_IN_ARRAY_LSB(out, out_row_index);
|
||||
CLEAR_BIT_IN_ARRAY_LSB(out, out_row_index + 1);
|
||||
|
||||
for(int16_t j = 0; j < in_row_width; j++) {
|
||||
// guard bit
|
||||
if(TEST_BIT_IN_ARRAY_LSB(tmp, pos)) {
|
||||
SET_BIT_IN_ARRAY_LSB(out, j + 2 + out_row_index);
|
||||
} else {
|
||||
CLEAR_BIT_IN_ARRAY_LSB(out, j + 2 + out_row_index);
|
||||
}
|
||||
|
||||
pos += step;
|
||||
if(pos >= nb_bits) {
|
||||
st_idx += step_v;
|
||||
if(st_idx >= step) {
|
||||
st_idx_init++;
|
||||
st_idx = st_idx_init;
|
||||
}
|
||||
pos = st_idx;
|
||||
}
|
||||
}
|
||||
|
||||
bits_left -= RADIOLIB_SX126X_LR_FHSS_FRAG_BITS;
|
||||
out_row_index += 2 + in_row_width;
|
||||
}
|
||||
|
||||
nb_bits = out_row_index - RADIOLIB_SX126X_LR_FHSS_HEADER_BITS * this->lrFhssHdrCount;
|
||||
|
||||
// build the header
|
||||
uint8_t raw_header[RADIOLIB_SX126X_LR_FHSS_HDR_BYTES/2];
|
||||
raw_header[0] = in_len;
|
||||
raw_header[1] = (this->lrFhssCr << 3) | ((uint8_t)this->lrFhssGridNonFcc << 2) |
|
||||
(RADIOLIB_SX126X_LR_FHSS_HOPPING_ENABLED << 1) | (this->lrFhssBw >> 3);
|
||||
raw_header[2] = ((this->lrFhssBw & 0x07) << 5) | (this->lrFhssHopSeqId >> 4);
|
||||
raw_header[3] = ((this->lrFhssHopSeqId & 0x000F) << 4);
|
||||
|
||||
// CRC-8 used seems to based on 8H2F, but without final XOR
|
||||
RadioLibCRCInstance.size = 8;
|
||||
RadioLibCRCInstance.poly = 0x2F;
|
||||
RadioLibCRCInstance.init = 0xFF;
|
||||
RadioLibCRCInstance.out = 0x00;
|
||||
|
||||
uint16_t header_offset = 0;
|
||||
for(size_t i = 0; i < this->lrFhssHdrCount; i++) {
|
||||
// insert index and calculate the header CRC
|
||||
raw_header[3] = (raw_header[3] & ~0x0C) | ((this->lrFhssHdrCount - i - 1) << 2);
|
||||
raw_header[4] = RadioLibCRCInstance.checksum(raw_header, (RADIOLIB_SX126X_LR_FHSS_HDR_BYTES/2 - 1));
|
||||
|
||||
// convolutional encode
|
||||
uint8_t coded_header[RADIOLIB_SX126X_LR_FHSS_HDR_BYTES] = { 0 };
|
||||
RadioLibConvCodeInstance.begin(2);
|
||||
RadioLibConvCodeInstance.encode(raw_header, 8*RADIOLIB_SX126X_LR_FHSS_HDR_BYTES/2, coded_header);
|
||||
// tail-biting seems to just do this twice ...?
|
||||
RadioLibConvCodeInstance.encode(raw_header, 8*RADIOLIB_SX126X_LR_FHSS_HDR_BYTES/2, coded_header);
|
||||
|
||||
// clear guard bits
|
||||
CLEAR_BIT_IN_ARRAY_LSB(out, header_offset);
|
||||
CLEAR_BIT_IN_ARRAY_LSB(out, header_offset + 1);
|
||||
|
||||
// interleave the header directly to the physical payload buffer
|
||||
for(size_t j = 0; j < (8*RADIOLIB_SX126X_LR_FHSS_HDR_BYTES/2); j++) {
|
||||
if(TEST_BIT_IN_ARRAY_LSB(coded_header, LrFhssHeaderInterleaver[j])) {
|
||||
SET_BIT_IN_ARRAY_LSB(out, header_offset + 2 + j);
|
||||
} else {
|
||||
CLEAR_BIT_IN_ARRAY_LSB(out, header_offset + 2 + j);
|
||||
}
|
||||
}
|
||||
for(size_t j = 0; j < (8*RADIOLIB_SX126X_LR_FHSS_HDR_BYTES/2); j++) {
|
||||
if(TEST_BIT_IN_ARRAY_LSB(coded_header, LrFhssHeaderInterleaver[(8*RADIOLIB_SX126X_LR_FHSS_HDR_BYTES/2) + j])) {
|
||||
SET_BIT_IN_ARRAY_LSB(out, header_offset + 2 + (8*RADIOLIB_SX126X_LR_FHSS_HDR_BYTES/2) + (8*RADIOLIB_SX126X_LR_FHSS_SYNC_WORD_BYTES) + j);
|
||||
} else {
|
||||
CLEAR_BIT_IN_ARRAY_LSB(out, header_offset + 2 + (8*RADIOLIB_SX126X_LR_FHSS_HDR_BYTES/2) + (8*RADIOLIB_SX126X_LR_FHSS_SYNC_WORD_BYTES) + j);
|
||||
}
|
||||
}
|
||||
|
||||
// copy the sync word to the physical payload buffer
|
||||
for(size_t j = 0; j < (8*RADIOLIB_SX126X_LR_FHSS_SYNC_WORD_BYTES); j++) {
|
||||
if(TEST_BIT_IN_ARRAY_LSB(this->lrFhssSyncWord, j)) {
|
||||
SET_BIT_IN_ARRAY_LSB(out, header_offset + 2 + (8*RADIOLIB_SX126X_LR_FHSS_HDR_BYTES/2) + j);
|
||||
} else {
|
||||
CLEAR_BIT_IN_ARRAY_LSB(out, header_offset + 2 + (8*RADIOLIB_SX126X_LR_FHSS_HDR_BYTES/2) + j);
|
||||
}
|
||||
}
|
||||
|
||||
header_offset += RADIOLIB_SX126X_LR_FHSS_HEADER_BITS;
|
||||
}
|
||||
|
||||
// calculate the number of hops and total number of bits
|
||||
uint16_t length_bits = (in_len + 2) * 8 + 6;
|
||||
switch(this->lrFhssCr) {
|
||||
case RADIOLIB_SX126X_LR_FHSS_CR_5_6:
|
||||
length_bits = ( ( length_bits * 6 ) + 4 ) / 5;
|
||||
break;
|
||||
case RADIOLIB_SX126X_LR_FHSS_CR_2_3:
|
||||
length_bits = length_bits * 3 / 2;
|
||||
break;
|
||||
case RADIOLIB_SX126X_LR_FHSS_CR_1_2:
|
||||
length_bits = length_bits * 2;
|
||||
break;
|
||||
case RADIOLIB_SX126X_LR_FHSS_CR_1_3:
|
||||
length_bits = length_bits * 3;
|
||||
break;
|
||||
}
|
||||
|
||||
*out_hops = (length_bits + 47) / 48 + this->lrFhssHdrCount;
|
||||
|
||||
// calculate total number of payload bits, after breaking into blocks
|
||||
uint16_t payload_bits = length_bits / RADIOLIB_SX126X_LR_FHSS_FRAG_BITS * RADIOLIB_SX126X_LR_FHSS_BLOCK_BITS;
|
||||
uint16_t last_block_bits = length_bits % RADIOLIB_SX126X_LR_FHSS_FRAG_BITS;
|
||||
if(last_block_bits > 0) {
|
||||
// add the 2 guard bits for the last block + the actual remaining payload bits
|
||||
payload_bits += last_block_bits + 2;
|
||||
}
|
||||
|
||||
*out_bits = (RADIOLIB_SX126X_LR_FHSS_HEADER_BITS * this->lrFhssHdrCount) + payload_bits;
|
||||
*out_len = (*out_bits + 7) / 8;
|
||||
|
||||
return(RADIOLIB_ERR_NONE);
|
||||
}
|
||||
|
||||
int16_t SX126x::resetLRFHSS() {
|
||||
// initialize hopping configuration
|
||||
const uint16_t numChan[] = { 80, 176, 280, 376, 688, 792, 1480, 1584, 3120, 3224 };
|
||||
|
||||
// LFSR polynomials for different ranges of lrFhssNgrid
|
||||
const uint8_t lfsrPoly1[] = { 33, 45, 48, 51, 54, 57 };
|
||||
const uint8_t lfsrPoly2[] = { 65, 68, 71, 72 };
|
||||
const uint8_t lfsrPoly3[] = { 142, 149 };
|
||||
|
||||
uint32_t nb_channel_in_grid = this->lrFhssGridNonFcc ? 8 : 52;
|
||||
this->lrFhssNgrid = numChan[this->lrFhssBw] / nb_channel_in_grid;
|
||||
this->lrFhssLfsrState = 6;
|
||||
switch(this->lrFhssNgrid) {
|
||||
case 10:
|
||||
case 22:
|
||||
case 28:
|
||||
case 30:
|
||||
case 35:
|
||||
case 47:
|
||||
this->lrFhssPoly = lfsrPoly1[this->lrFhssHopSeqId >> 6];
|
||||
this->lrFhssSeed = this->lrFhssHopSeqId & 0x3F;
|
||||
if(this->lrFhssHopSeqId >= 384) {
|
||||
return(RADIOLIB_ERR_INVALID_DATA_SHAPING);
|
||||
}
|
||||
break;
|
||||
|
||||
case 60:
|
||||
case 62:
|
||||
this->lrFhssLfsrState = 56;
|
||||
this->lrFhssPoly = lfsrPoly1[this->lrFhssHopSeqId >> 6];
|
||||
this->lrFhssSeed = this->lrFhssHopSeqId & 0x3F;
|
||||
if(this->lrFhssHopSeqId >= 384) {
|
||||
return(RADIOLIB_ERR_INVALID_DATA_SHAPING);
|
||||
}
|
||||
break;
|
||||
|
||||
case 86:
|
||||
case 99:
|
||||
this->lrFhssPoly = lfsrPoly2[this->lrFhssHopSeqId >> 7];
|
||||
this->lrFhssSeed = this->lrFhssHopSeqId & 0x7F;
|
||||
break;
|
||||
|
||||
case 185:
|
||||
case 198:
|
||||
this->lrFhssPoly = lfsrPoly3[this->lrFhssHopSeqId >> 8];
|
||||
this->lrFhssSeed = this->lrFhssHopSeqId & 0xFF;
|
||||
break;
|
||||
|
||||
case 390:
|
||||
case 403:
|
||||
this->lrFhssPoly = 264;
|
||||
this->lrFhssSeed = this->lrFhssHopSeqId;
|
||||
break;
|
||||
|
||||
default:
|
||||
return(RADIOLIB_ERR_INVALID_DATA_SHAPING);
|
||||
}
|
||||
|
||||
return(RADIOLIB_ERR_NONE);
|
||||
}
|
||||
|
||||
uint16_t SX126x::stepLRFHSS() {
|
||||
uint16_t hop;
|
||||
do {
|
||||
uint16_t lsb = this->lrFhssLfsrState & 1;
|
||||
this->lrFhssLfsrState >>= 1;
|
||||
if(lsb) {
|
||||
this->lrFhssLfsrState ^= this->lrFhssPoly;
|
||||
}
|
||||
hop = this->lrFhssSeed;
|
||||
if(hop != this->lrFhssLfsrState) {
|
||||
hop ^= this->lrFhssLfsrState;
|
||||
}
|
||||
} while(hop > this->lrFhssNgrid);
|
||||
return(hop);
|
||||
}
|
||||
|
||||
int16_t SX126x::setLRFHSSHop(uint8_t index) {
|
||||
if(!this->lrFhssFrameHopsRem) {
|
||||
return(RADIOLIB_ERR_NONE);
|
||||
}
|
||||
|
||||
uint16_t hop = stepLRFHSS();
|
||||
int16_t freq_table = hop - 1;
|
||||
if(freq_table >= (int16_t)(this->lrFhssNgrid >> 1)) {
|
||||
freq_table -= this->lrFhssNgrid;
|
||||
}
|
||||
|
||||
uint32_t nb_channel_in_grid = this->lrFhssGridNonFcc ? 8 : 52;
|
||||
uint32_t grid_offset = (1 + (this->lrFhssNgrid % 2)) * (nb_channel_in_grid / 2);
|
||||
uint32_t grid_in_pll_steps = this->lrFhssGridNonFcc ? 4096 : 26624;
|
||||
uint32_t freq_raw = this->frf - freq_table * grid_in_pll_steps - grid_offset * 512;
|
||||
|
||||
if((this->lrFhssHopNum < this->lrFhssHdrCount)) {
|
||||
if((((this->lrFhssHdrCount - this->lrFhssHopNum) % 2) == 0)) {
|
||||
freq_raw += 256;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t frq[4] = { (uint8_t)((freq_raw >> 24) & 0xFF), (uint8_t)((freq_raw >> 16) & 0xFF), (uint8_t)((freq_raw >> 8) & 0xFF), (uint8_t)(freq_raw & 0xFF) };
|
||||
int16_t state = writeRegister(RADIOLIB_SX126X_REG_LR_FHSS_FREQX_0(index), frq, sizeof(freq_raw));
|
||||
RADIOLIB_ASSERT(state);
|
||||
|
||||
// (LR_FHSS_HEADER_BITS + pulse_shape_compensation) symbols on first sync_word, LR_FHSS_HEADER_BITS on
|
||||
// next sync_words, LR_FHSS_BLOCK_BITS on payload
|
||||
uint16_t numSymbols = RADIOLIB_SX126X_LR_FHSS_BLOCK_BITS;
|
||||
if(index == 0) {
|
||||
numSymbols = RADIOLIB_SX126X_LR_FHSS_HEADER_BITS + 1; // the +1 is "pulse_shape_compensation", but it's constant in the demo
|
||||
} else if(index < this->lrFhssHdrCount) {
|
||||
numSymbols = RADIOLIB_SX126X_LR_FHSS_HEADER_BITS;
|
||||
} else if(this->lrFhssFrameBitsRem < RADIOLIB_SX126X_LR_FHSS_BLOCK_BITS) {
|
||||
numSymbols = this->lrFhssFrameBitsRem;
|
||||
}
|
||||
|
||||
// write hop length in symbols
|
||||
uint8_t sym[2] = { (uint8_t)((numSymbols >> 8) & 0xFF), (uint8_t)(numSymbols & 0xFF) };
|
||||
state = writeRegister(RADIOLIB_SX126X_REG_LR_FHSS_NUM_SYMBOLS_FREQX_MSB(index), sym, sizeof(uint16_t));
|
||||
RADIOLIB_ASSERT(state);
|
||||
|
||||
this->lrFhssFrameBitsRem -= numSymbols;
|
||||
this->lrFhssFrameHopsRem--;
|
||||
this->lrFhssHopNum++;
|
||||
return(RADIOLIB_ERR_NONE);
|
||||
}
|
||||
|
||||
#endif
|
|
@ -307,3 +307,59 @@ uint32_t RadioLibBCH::encode(uint32_t dataword) {
|
|||
}
|
||||
|
||||
RadioLibBCH RadioLibBCHInstance;
|
||||
|
||||
RadioLibConvCode::RadioLibConvCode() {
|
||||
|
||||
}
|
||||
|
||||
void RadioLibConvCode::begin(uint8_t rt) {
|
||||
this->enc_state = 0;
|
||||
this->rate = rt;
|
||||
}
|
||||
|
||||
int16_t RadioLibConvCode::encode(const uint8_t* in, size_t in_bits, uint8_t* out, size_t* out_bits) {
|
||||
if(!in || !out) {
|
||||
return(RADIOLIB_ERR_UNKNOWN);
|
||||
}
|
||||
|
||||
size_t ind_bit;
|
||||
uint16_t data_out_bitcount = 0;
|
||||
uint32_t bin_out_word = 0;
|
||||
|
||||
// iterate over the provided bits
|
||||
for(ind_bit = 0; ind_bit < in_bits; ind_bit++) {
|
||||
uint8_t cur_bit = GET_BIT_IN_ARRAY_LSB(in, ind_bit);
|
||||
const uint32_t* lut_ptr = (this->rate == 2) ? ConvCodeTable1_2 : ConvCodeTable1_3;
|
||||
uint8_t word_pos = this->enc_state / 4;
|
||||
uint8_t byte_pos = (3 - (this->enc_state % 4)) * 8;
|
||||
uint8_t nibble_pos = (1 - cur_bit) * 4;
|
||||
uint8_t g1g0 = (lut_ptr[word_pos] >> (byte_pos + nibble_pos)) & 0x0F;
|
||||
|
||||
uint8_t mod = this->rate == 2 ? 16 : 64;
|
||||
this->enc_state = (this->enc_state * 2 + cur_bit) % mod;
|
||||
bin_out_word |= (g1g0 << ((7 - (ind_bit % 8)) * this->rate));
|
||||
if(ind_bit % 8 == 7) {
|
||||
if(this->rate == 3) {
|
||||
*out++ = (uint8_t)(bin_out_word >> 16);
|
||||
}
|
||||
*out++ = (uint8_t)(bin_out_word >> 8);
|
||||
*out++ = (uint8_t)bin_out_word;
|
||||
bin_out_word = 0;
|
||||
}
|
||||
data_out_bitcount += this->rate;
|
||||
}
|
||||
|
||||
if(ind_bit % 8) {
|
||||
if(this->rate == 3) {
|
||||
*out++ = (uint8_t)(bin_out_word >> 16);
|
||||
}
|
||||
*out++ = (uint8_t)(bin_out_word >> 8);
|
||||
*out++ = (uint8_t)bin_out_word;
|
||||
}
|
||||
|
||||
if(out_bits) { *out_bits = data_out_bitcount; }
|
||||
|
||||
return(RADIOLIB_ERR_NONE);
|
||||
}
|
||||
|
||||
RadioLibConvCode RadioLibConvCodeInstance;
|
||||
|
|
|
@ -31,7 +31,7 @@ class RadioLibBCH {
|
|||
RadioLibBCH();
|
||||
|
||||
/*!
|
||||
\brief Default detructor.
|
||||
\brief Default destructor.
|
||||
*/
|
||||
~RadioLibBCH();
|
||||
|
||||
|
@ -76,6 +76,92 @@ extern RadioLibBCH RadioLibBCHInstance;
|
|||
#define CLEAR_BIT_IN_ARRAY_MSB(A, k) ( A[((k)/8)] &= ~(1 << ((k)%8)) )
|
||||
#define TEST_BIT_IN_ARRAY_MSB(A, k) ( A[((k)/8)] & (1 << ((k)%8)) )
|
||||
#define GET_BIT_IN_ARRAY_MSB(A, k) ( (A[((k)/8)] & (1 << ((k)%8))) ? 1 : 0 )
|
||||
#define SET_BIT_IN_ARRAY_LSB(A, k) ( A[((k)/8)] |= (1 << (7 - ((k)%8))) )
|
||||
#define CLEAR_BIT_IN_ARRAY_LSB(A, k) ( A[((k)/8)] &= ~(1 << (7 - ((k)%8))) )
|
||||
#define TEST_BIT_IN_ARRAY_LSB(A, k) ( A[((k)/8)] & (1 << (7 - ((k)%8))) )
|
||||
#define GET_BIT_IN_ARRAY_LSB(A, k) ( (A[((k)/8)] & (1 << (7 - ((k)%8)))) ? 1 : 0 )
|
||||
|
||||
/*!
|
||||
\class RadioLibConvCode
|
||||
\brief Class to perform convolutional coding wtih variable rates.
|
||||
Only 1/2 and 1/3 rate is currently supported.
|
||||
|
||||
Copnvolutional coder implementation in this class is adapted from Setmech's LR-FHSS demo:
|
||||
https://github.com/Lora-net/SWDM001/tree/master/lib/sx126x_driver
|
||||
|
||||
Its SX126x driver is distributed under the Clear BSD License,
|
||||
and to comply with its terms, it is reproduced below.
|
||||
|
||||
The Clear BSD License
|
||||
Copyright Semtech Corporation 2021. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted (subject to the limitations in the disclaimer
|
||||
below) provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the Semtech corporation nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY
|
||||
THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
|
||||
NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SEMTECH CORPORATION BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
class RadioLibConvCode {
|
||||
public:
|
||||
/*!
|
||||
\brief Default constructor.
|
||||
*/
|
||||
RadioLibConvCode();
|
||||
|
||||
/*!
|
||||
\brief Initialization method.
|
||||
\param rt Encoding rate denominator (1/x). Only 1/2 and 1/3 encoding is currently supported.
|
||||
*/
|
||||
void begin(uint8_t rt);
|
||||
|
||||
/*!
|
||||
\brief Encoding method.
|
||||
\param in Input buffer (a byte array).
|
||||
\param in_bits Input length in bits.
|
||||
\param out Output buffer (a byte array). It is up to the caller
|
||||
to ensure the buffer is large enough to fit the encoded data!
|
||||
\param out_bits Pointer to a variable to save the number of encoded bits.
|
||||
Ignored if set to NULL.
|
||||
\returns \ref status_codes
|
||||
*/
|
||||
int16_t encode(const uint8_t* in, size_t in_bits, uint8_t* out, size_t* out_bits = NULL);
|
||||
|
||||
private:
|
||||
uint8_t enc_state = 0;
|
||||
uint8_t rate = 0;
|
||||
};
|
||||
|
||||
// each 32-bit word stores 8 values, one per each nibble
|
||||
static const uint32_t ConvCodeTable1_3[16] = {
|
||||
0x07347043, 0x61521625, 0x16256152, 0x70430734,
|
||||
0x43703407, 0x25165261, 0x52612516, 0x34074370,
|
||||
0x70430734, 0x16256152, 0x61521625, 0x07347043,
|
||||
0x34074370, 0x52612516, 0x25165261, 0x43703407,
|
||||
};
|
||||
|
||||
static const uint32_t ConvCodeTable1_2[4] = {
|
||||
0x03122130, 0x21300312, 0x30211203, 0x12033021,
|
||||
};
|
||||
|
||||
extern RadioLibConvCode RadioLibConvCodeInstance;
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Add table
Reference in a new issue