[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:
Jan Gromeš 2024-10-07 20:00:19 +02:00 committed by GitHub
parent 1f11cd1dd5
commit dc77e6e662
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 933 additions and 11 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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

View file

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

View file

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