Line data Source code
1 : #include "SX126x.h"
2 : #include <string.h>
3 : #include <math.h>
4 : #if !RADIOLIB_EXCLUDE_SX126X
5 :
6 : /*
7 : LR-FHSS implementation in this file is adapted from Setmech's LR-FHSS demo:
8 : https://github.com/Lora-net/SWDM001/tree/master/lib/sx126x_driver
9 :
10 : Its SX126x driver is distributed under the Clear BSD License,
11 : and to comply with its terms, it is reproduced below.
12 :
13 : The Clear BSD License
14 : Copyright Semtech Corporation 2021. All rights reserved.
15 :
16 : Redistribution and use in source and binary forms, with or without
17 : modification, are permitted (subject to the limitations in the disclaimer
18 : below) provided that the following conditions are met:
19 : * Redistributions of source code must retain the above copyright
20 : notice, this list of conditions and the following disclaimer.
21 : * Redistributions in binary form must reproduce the above copyright
22 : notice, this list of conditions and the following disclaimer in the
23 : documentation and/or other materials provided with the distribution.
24 : * Neither the name of the Semtech corporation nor the
25 : names of its contributors may be used to endorse or promote products
26 : derived from this software without specific prior written permission.
27 :
28 : NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY
29 : THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30 : CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
31 : NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
32 : PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SEMTECH CORPORATION BE
33 : LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
34 : CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
35 : SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
36 : INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
37 : CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
38 : ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
39 : POSSIBILITY OF SUCH DAMAGE.
40 : */
41 :
42 : // header interleaver
43 : static const uint8_t LrFhssHeaderInterleaver[80] = {
44 : 0, 18, 36, 54, 72, 4, 22, 40,
45 : 58, 76, 8, 26, 44, 62, 12, 30,
46 : 48, 66, 16, 34, 52, 70, 1, 19,
47 : 37, 55, 73, 5, 23, 41, 59, 77,
48 : 9, 27, 45, 63, 13, 31, 49, 67,
49 : 17, 35, 53, 71, 2, 20, 38, 56,
50 : 74, 6, 24, 42, 60, 78, 10, 28,
51 : 46, 64, 14, 32, 50, 68, 3, 21,
52 : 39, 57, 75, 7, 25, 43, 61, 79,
53 : 11, 29, 47, 65, 15, 33, 51, 69,
54 : };
55 :
56 0 : 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) {
57 : // perform payload whitening
58 0 : uint8_t lfsr = 0xFF;
59 0 : for(size_t i = 0; i < in_len; i++) {
60 0 : uint8_t u = in[i] ^ lfsr;
61 :
62 : // we really shouldn't reuse the caller's memory in this way ...
63 : // but since this is a private method it should be at least controlled, if not safe
64 0 : out[i] = ((u & 0x0F) << 4 ) | ((u & 0xF0) >> 4);
65 0 : lfsr = (lfsr << 1) | (((lfsr & 0x80) >> 7) ^ (((lfsr & 0x20) >> 5) ^ (((lfsr & 0x10) >> 4) ^ ((lfsr & 0x08) >> 3))));
66 : }
67 :
68 : // calculate the CRC-16 over the whitened data, looks like something custom
69 0 : RadioLibCRCInstance.size = 16;
70 0 : RadioLibCRCInstance.poly = 0x755B;
71 0 : RadioLibCRCInstance.init = 0xFFFF;
72 0 : RadioLibCRCInstance.out = 0x0000;
73 0 : uint16_t crc16 = RadioLibCRCInstance.checksum(out, in_len);
74 :
75 : // add payload CRC
76 0 : out[in_len] = (crc16 >> 8) & 0xFF;
77 0 : out[in_len + 1] = crc16 & 0xFF;
78 0 : out[in_len + 2] = 0;
79 :
80 : // encode the payload with CRC using convolutional coding with 1/3 rate into a temporary buffer
81 0 : uint8_t tmp[RADIOLIB_SX126X_LR_FHSS_MAX_ENC_SIZE] = { 0 };
82 0 : size_t nb_bits = 0;
83 0 : RadioLibConvCodeInstance.begin(3);
84 0 : RadioLibConvCodeInstance.encode(out, 8 * (in_len + 2) + 6, tmp, &nb_bits);
85 0 : memset(out, 0, RADIOLIB_SX126X_MAX_PACKET_LENGTH);
86 :
87 : // for rates other than the 1/3 base, puncture the code
88 0 : if(this->lrFhssCr != RADIOLIB_SX126X_LR_FHSS_CR_1_3) {
89 0 : uint32_t matrix_index = 0;
90 0 : const uint8_t matrix[15] = { 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0 };
91 0 : uint8_t matrix_len = 0;
92 0 : switch(this->lrFhssCr) {
93 0 : case RADIOLIB_SX126X_LR_FHSS_CR_5_6:
94 0 : matrix_len = 15;
95 0 : break;
96 0 : case RADIOLIB_SX126X_LR_FHSS_CR_2_3:
97 0 : matrix_len = 6;
98 0 : break;
99 0 : case RADIOLIB_SX126X_LR_FHSS_CR_1_2:
100 0 : matrix_len = 3;
101 0 : break;
102 : }
103 :
104 0 : uint32_t j = 0;
105 0 : for(uint32_t i = 0; i < nb_bits; i++) {
106 0 : if(matrix[matrix_index]) {
107 0 : if(TEST_BIT_IN_ARRAY_LSB(tmp, i)) {
108 0 : SET_BIT_IN_ARRAY_LSB(out, j);
109 : } else {
110 0 : CLEAR_BIT_IN_ARRAY_LSB(out, j);
111 : }
112 0 : j++;
113 : }
114 :
115 0 : if(++matrix_index == matrix_len) {
116 0 : matrix_index = 0;
117 : }
118 : }
119 :
120 0 : nb_bits = j;
121 0 : memcpy(tmp, out, (nb_bits + 7) / 8);
122 : }
123 :
124 : // interleave the payload into output buffer
125 0 : uint16_t step = 0;
126 0 : while((size_t)(step * step) < nb_bits) {
127 : // probably the silliest sqrt() I ever saw
128 0 : step++;
129 : }
130 :
131 0 : const uint16_t step_v = step >> 1;
132 0 : step <<= 1;
133 :
134 0 : uint16_t pos = 0;
135 0 : uint16_t st_idx = 0;
136 0 : uint16_t st_idx_init = 0;
137 0 : int16_t bits_left = nb_bits;
138 0 : uint16_t out_row_index = RADIOLIB_SX126X_LR_FHSS_HEADER_BITS * this->lrFhssHdrCount;
139 :
140 0 : while(bits_left > 0) {
141 0 : int16_t in_row_width = bits_left;
142 0 : if(in_row_width > RADIOLIB_SX126X_LR_FHSS_FRAG_BITS) {
143 0 : in_row_width = RADIOLIB_SX126X_LR_FHSS_FRAG_BITS;
144 : }
145 :
146 : // guard bits
147 0 : CLEAR_BIT_IN_ARRAY_LSB(out, out_row_index);
148 0 : CLEAR_BIT_IN_ARRAY_LSB(out, out_row_index + 1);
149 :
150 0 : for(int16_t j = 0; j < in_row_width; j++) {
151 : // guard bit
152 0 : if(TEST_BIT_IN_ARRAY_LSB(tmp, pos)) {
153 0 : SET_BIT_IN_ARRAY_LSB(out, j + 2 + out_row_index);
154 : } else {
155 0 : CLEAR_BIT_IN_ARRAY_LSB(out, j + 2 + out_row_index);
156 : }
157 :
158 0 : pos += step;
159 0 : if(pos >= nb_bits) {
160 0 : st_idx += step_v;
161 0 : if(st_idx >= step) {
162 0 : st_idx_init++;
163 0 : st_idx = st_idx_init;
164 : }
165 0 : pos = st_idx;
166 : }
167 : }
168 :
169 0 : bits_left -= RADIOLIB_SX126X_LR_FHSS_FRAG_BITS;
170 0 : out_row_index += 2 + in_row_width;
171 : }
172 :
173 0 : nb_bits = out_row_index - RADIOLIB_SX126X_LR_FHSS_HEADER_BITS * this->lrFhssHdrCount;
174 :
175 : // build the header
176 : uint8_t raw_header[RADIOLIB_SX126X_LR_FHSS_HDR_BYTES/2];
177 0 : raw_header[0] = in_len;
178 0 : raw_header[1] = (this->lrFhssCr << 3) | ((uint8_t)this->lrFhssGridNonFcc << 2) |
179 0 : (RADIOLIB_SX126X_LR_FHSS_HOPPING_ENABLED << 1) | (this->lrFhssBw >> 3);
180 0 : raw_header[2] = ((this->lrFhssBw & 0x07) << 5) | (this->lrFhssHopSeqId >> 4);
181 0 : raw_header[3] = ((this->lrFhssHopSeqId & 0x000F) << 4);
182 :
183 : // CRC-8 used seems to based on 8H2F, but without final XOR
184 0 : RadioLibCRCInstance.size = 8;
185 0 : RadioLibCRCInstance.poly = 0x2F;
186 0 : RadioLibCRCInstance.init = 0xFF;
187 0 : RadioLibCRCInstance.out = 0x00;
188 :
189 0 : uint16_t header_offset = 0;
190 0 : for(size_t i = 0; i < this->lrFhssHdrCount; i++) {
191 : // insert index and calculate the header CRC
192 0 : raw_header[3] = (raw_header[3] & ~0x0C) | ((this->lrFhssHdrCount - i - 1) << 2);
193 0 : raw_header[4] = RadioLibCRCInstance.checksum(raw_header, (RADIOLIB_SX126X_LR_FHSS_HDR_BYTES/2 - 1));
194 :
195 : // convolutional encode
196 0 : uint8_t coded_header[RADIOLIB_SX126X_LR_FHSS_HDR_BYTES] = { 0 };
197 0 : RadioLibConvCodeInstance.begin(2);
198 0 : RadioLibConvCodeInstance.encode(raw_header, 8*RADIOLIB_SX126X_LR_FHSS_HDR_BYTES/2, coded_header);
199 : // tail-biting seems to just do this twice ...?
200 0 : RadioLibConvCodeInstance.encode(raw_header, 8*RADIOLIB_SX126X_LR_FHSS_HDR_BYTES/2, coded_header);
201 :
202 : // clear guard bits
203 0 : CLEAR_BIT_IN_ARRAY_LSB(out, header_offset);
204 0 : CLEAR_BIT_IN_ARRAY_LSB(out, header_offset + 1);
205 :
206 : // interleave the header directly to the physical payload buffer
207 0 : for(size_t j = 0; j < (8*RADIOLIB_SX126X_LR_FHSS_HDR_BYTES/2); j++) {
208 0 : if(TEST_BIT_IN_ARRAY_LSB(coded_header, LrFhssHeaderInterleaver[j])) {
209 0 : SET_BIT_IN_ARRAY_LSB(out, header_offset + 2 + j);
210 : } else {
211 0 : CLEAR_BIT_IN_ARRAY_LSB(out, header_offset + 2 + j);
212 : }
213 : }
214 0 : for(size_t j = 0; j < (8*RADIOLIB_SX126X_LR_FHSS_HDR_BYTES/2); j++) {
215 0 : if(TEST_BIT_IN_ARRAY_LSB(coded_header, LrFhssHeaderInterleaver[(8*RADIOLIB_SX126X_LR_FHSS_HDR_BYTES/2) + j])) {
216 0 : 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);
217 : } else {
218 0 : 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);
219 : }
220 : }
221 :
222 : // copy the sync word to the physical payload buffer
223 0 : for(size_t j = 0; j < (8*RADIOLIB_SX126X_LR_FHSS_SYNC_WORD_BYTES); j++) {
224 0 : if(TEST_BIT_IN_ARRAY_LSB(this->lrFhssSyncWord, j)) {
225 0 : SET_BIT_IN_ARRAY_LSB(out, header_offset + 2 + (8*RADIOLIB_SX126X_LR_FHSS_HDR_BYTES/2) + j);
226 : } else {
227 0 : CLEAR_BIT_IN_ARRAY_LSB(out, header_offset + 2 + (8*RADIOLIB_SX126X_LR_FHSS_HDR_BYTES/2) + j);
228 : }
229 : }
230 :
231 0 : header_offset += RADIOLIB_SX126X_LR_FHSS_HEADER_BITS;
232 : }
233 :
234 : // calculate the number of hops and total number of bits
235 0 : uint16_t length_bits = (in_len + 2) * 8 + 6;
236 0 : switch(this->lrFhssCr) {
237 0 : case RADIOLIB_SX126X_LR_FHSS_CR_5_6:
238 0 : length_bits = ( ( length_bits * 6 ) + 4 ) / 5;
239 0 : break;
240 0 : case RADIOLIB_SX126X_LR_FHSS_CR_2_3:
241 0 : length_bits = length_bits * 3 / 2;
242 0 : break;
243 0 : case RADIOLIB_SX126X_LR_FHSS_CR_1_2:
244 0 : length_bits = length_bits * 2;
245 0 : break;
246 0 : case RADIOLIB_SX126X_LR_FHSS_CR_1_3:
247 0 : length_bits = length_bits * 3;
248 0 : break;
249 : }
250 :
251 0 : *out_hops = (length_bits + 47) / 48 + this->lrFhssHdrCount;
252 :
253 : // calculate total number of payload bits, after breaking into blocks
254 0 : uint16_t payload_bits = length_bits / RADIOLIB_SX126X_LR_FHSS_FRAG_BITS * RADIOLIB_SX126X_LR_FHSS_BLOCK_BITS;
255 0 : uint16_t last_block_bits = length_bits % RADIOLIB_SX126X_LR_FHSS_FRAG_BITS;
256 0 : if(last_block_bits > 0) {
257 : // add the 2 guard bits for the last block + the actual remaining payload bits
258 0 : payload_bits += last_block_bits + 2;
259 : }
260 :
261 0 : *out_bits = (RADIOLIB_SX126X_LR_FHSS_HEADER_BITS * this->lrFhssHdrCount) + payload_bits;
262 0 : *out_len = (*out_bits + 7) / 8;
263 :
264 0 : return(RADIOLIB_ERR_NONE);
265 : }
266 :
267 0 : int16_t SX126x::resetLRFHSS() {
268 : // initialize hopping configuration
269 0 : const uint16_t numChan[] = { 80, 176, 280, 376, 688, 792, 1480, 1584, 3120, 3224 };
270 :
271 : // LFSR polynomials for different ranges of lrFhssNgrid
272 0 : const uint8_t lfsrPoly1[] = { 33, 45, 48, 51, 54, 57 };
273 0 : const uint8_t lfsrPoly2[] = { 65, 68, 71, 72 };
274 0 : const uint8_t lfsrPoly3[] = { 142, 149 };
275 :
276 0 : uint32_t nb_channel_in_grid = this->lrFhssGridNonFcc ? 8 : 52;
277 0 : this->lrFhssNgrid = numChan[this->lrFhssBw] / nb_channel_in_grid;
278 0 : this->lrFhssLfsrState = 6;
279 0 : switch(this->lrFhssNgrid) {
280 0 : case 10:
281 : case 22:
282 : case 28:
283 : case 30:
284 : case 35:
285 : case 47:
286 0 : this->lrFhssPoly = lfsrPoly1[this->lrFhssHopSeqId >> 6];
287 0 : this->lrFhssSeed = this->lrFhssHopSeqId & 0x3F;
288 0 : if(this->lrFhssHopSeqId >= 384) {
289 0 : return(RADIOLIB_ERR_INVALID_DATA_SHAPING);
290 : }
291 0 : break;
292 :
293 0 : case 60:
294 : case 62:
295 0 : this->lrFhssLfsrState = 56;
296 0 : this->lrFhssPoly = lfsrPoly1[this->lrFhssHopSeqId >> 6];
297 0 : this->lrFhssSeed = this->lrFhssHopSeqId & 0x3F;
298 0 : if(this->lrFhssHopSeqId >= 384) {
299 0 : return(RADIOLIB_ERR_INVALID_DATA_SHAPING);
300 : }
301 0 : break;
302 :
303 0 : case 86:
304 : case 99:
305 0 : this->lrFhssPoly = lfsrPoly2[this->lrFhssHopSeqId >> 7];
306 0 : this->lrFhssSeed = this->lrFhssHopSeqId & 0x7F;
307 0 : break;
308 :
309 0 : case 185:
310 : case 198:
311 0 : this->lrFhssPoly = lfsrPoly3[this->lrFhssHopSeqId >> 8];
312 0 : this->lrFhssSeed = this->lrFhssHopSeqId & 0xFF;
313 0 : break;
314 :
315 0 : case 390:
316 : case 403:
317 0 : this->lrFhssPoly = 264;
318 0 : this->lrFhssSeed = this->lrFhssHopSeqId;
319 0 : break;
320 :
321 0 : default:
322 0 : return(RADIOLIB_ERR_INVALID_DATA_SHAPING);
323 : }
324 :
325 0 : return(RADIOLIB_ERR_NONE);
326 : }
327 :
328 0 : uint16_t SX126x::stepLRFHSS() {
329 : uint16_t hop;
330 : do {
331 0 : uint16_t lsb = this->lrFhssLfsrState & 1;
332 0 : this->lrFhssLfsrState >>= 1;
333 0 : if(lsb) {
334 0 : this->lrFhssLfsrState ^= this->lrFhssPoly;
335 : }
336 0 : hop = this->lrFhssSeed;
337 0 : if(hop != this->lrFhssLfsrState) {
338 0 : hop ^= this->lrFhssLfsrState;
339 : }
340 0 : } while(hop > this->lrFhssNgrid);
341 0 : return(hop);
342 : }
343 :
344 0 : int16_t SX126x::setLRFHSSHop(uint8_t index) {
345 0 : if(!this->lrFhssFrameHopsRem) {
346 0 : return(RADIOLIB_ERR_NONE);
347 : }
348 :
349 0 : uint16_t hop = stepLRFHSS();
350 0 : int16_t freq_table = hop - 1;
351 0 : if(freq_table >= (int16_t)(this->lrFhssNgrid >> 1)) {
352 0 : freq_table -= this->lrFhssNgrid;
353 : }
354 :
355 0 : uint32_t nb_channel_in_grid = this->lrFhssGridNonFcc ? 8 : 52;
356 0 : uint32_t grid_offset = (1 + (this->lrFhssNgrid % 2)) * (nb_channel_in_grid / 2);
357 0 : uint32_t grid_in_pll_steps = this->lrFhssGridNonFcc ? 4096 : 26624;
358 0 : uint32_t frf = (this->freqMHz * (uint32_t(1) << RADIOLIB_SX126X_DIV_EXPONENT)) / RADIOLIB_SX126X_CRYSTAL_FREQ;
359 0 : uint32_t freq_raw = frf - freq_table * grid_in_pll_steps - grid_offset * 512;
360 :
361 0 : if((this->lrFhssHopNum < this->lrFhssHdrCount)) {
362 0 : if((((this->lrFhssHdrCount - this->lrFhssHopNum) % 2) == 0)) {
363 0 : freq_raw += 256;
364 : }
365 : }
366 :
367 0 : const 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) };
368 0 : int16_t state = writeRegister(RADIOLIB_SX126X_REG_LR_FHSS_FREQX_0(index), frq, sizeof(freq_raw));
369 0 : RADIOLIB_ASSERT(state);
370 :
371 : // (LR_FHSS_HEADER_BITS + pulse_shape_compensation) symbols on first sync_word, LR_FHSS_HEADER_BITS on
372 : // next sync_words, LR_FHSS_BLOCK_BITS on payload
373 0 : uint16_t numSymbols = RADIOLIB_SX126X_LR_FHSS_BLOCK_BITS;
374 0 : if(index == 0) {
375 0 : numSymbols = RADIOLIB_SX126X_LR_FHSS_HEADER_BITS + 1; // the +1 is "pulse_shape_compensation", but it's constant in the demo
376 0 : } else if(index < this->lrFhssHdrCount) {
377 0 : numSymbols = RADIOLIB_SX126X_LR_FHSS_HEADER_BITS;
378 0 : } else if(this->lrFhssFrameBitsRem < RADIOLIB_SX126X_LR_FHSS_BLOCK_BITS) {
379 0 : numSymbols = this->lrFhssFrameBitsRem;
380 : }
381 :
382 : // write hop length in symbols
383 0 : const uint8_t sym[2] = { (uint8_t)((numSymbols >> 8) & 0xFF), (uint8_t)(numSymbols & 0xFF) };
384 0 : state = writeRegister(RADIOLIB_SX126X_REG_LR_FHSS_NUM_SYMBOLS_FREQX_MSB(index), sym, sizeof(uint16_t));
385 0 : RADIOLIB_ASSERT(state);
386 :
387 0 : this->lrFhssFrameBitsRem -= numSymbols;
388 0 : this->lrFhssFrameHopsRem--;
389 0 : this->lrFhssHopNum++;
390 0 : return(RADIOLIB_ERR_NONE);
391 : }
392 :
393 : #endif
|